Select Start date that is 60 business days old - sql

I need the ability to select a start date that is 60business days prior to the current date. I have a calendar built with dates and business dates, etc. My current (and unsuccessful) method is below:
DECLARE #Mode int
SET #Mode = 0
SET #StartDate = CASE WHEN #Mode = 0
THEN (SELECT BusDate FROM Leads.dbo.Calendar
WHERE Date = DATEADD(DAY,DATEDIFF (DAY,0,GETDATE())-60,0))
WHEN #Mode = 1
THEN DATEADD(mm,DATEDIFF (mm,0,GETDATE())-2,0)
END
This method goes back 60 calendar days, not 60 business days. I'm having a challenge with getting this down to Business days. Disregard the Mode = 1 portion (this calculates by month).
Any help would be appreciated.
The Calendar table has the following fields:
SELECT [Date]
,[MMDDYYYY]
,[Year]
,[QTR]
,[Month]
,[Week]
,[YTDDay]
,[QTDDay]
,[MTDDay]
,[WeekDayNbr]
,[Quarter]
,[MonthLName]
,[MonthName]
,[DayOfWeekS]
,[DayOfWeek]
,[KindOfDay]
,[Description]
,[Period]
,[YrMo]
,[YrWk]
,[StartDate]
,[EndDate]
,[BusPeriod]
,[Holiday]
,[NonBus]
,[BusDaysInMonth]
,[BusDay]
,[BusDaysRemain]
,[BusDate]
,[YYYYMMDD]
BusDay is the business day for a month (1, 2, 3, etc). There currently is no column that says 1 for yes and 0 for No to indicate it is a business day, although I could add that. The NonBus and Holiday fields operate that way: 1 is Holiday or Non-business day (holidays and weekends) and 0 is not.

We don't know much about your calendar table but you could do something like this. Use a derived table that only consists of business days that are less than today's date and give each a row number ordered by date desc. Then select the date from 60th row.
select busdate
from
(
select *,
row_number() over (order by date desc) as dayNo
from leads.dbo.calendar
where nonBus = 0 -- Only business days
and date < cast(getdate() as date)
) busDays
where dayno = 60

Related

Split Hours into Multiple Months using SQL

CREATE TABLE EventLog
(
EventID INT
, EventName VARCHAR(50) NOT NULL
, EventStartDateTime DATETIME NOT NULL
, EventEndDateTime DATETIME NULL
)
INSERT INTO EventLog(EventID, EventName, EventStartDateTime, EventEndDateTime)
VALUES(100, 'Planting', '20210620 10:34:09 AM','20211018 10:54:49 PM')
,(200, 'Foundation', '20200420 10:34:09 AM','20211018 10:54:49 PM')
,(300, 'Seeding', '20210410 10:27:19 AM','')
,(400, 'Spreading', '20220310 10:24:09 PM','')
I have a requirement to split hours into multiple months and even years depending on the length of the event. In some cases, the event may have an end date but if the event is still ongoing, there will be no end date.
The result or output of the solution is to be held by another table:
CREATE TABLE EventSummary
(
EventID INT
, EventName VARCHAR(50) NOT NULL
, [Year] INT
, [MonthName] VARCHAR(25)
, [Hours] DECIMAL(12,2)
)
The image above is the output of the first row.
If the event runs over multiple years, the values should be spread across the multiple years and months likewise.
In cases where there is no end date I am to use GETUTCDATE() to do the calculation.
Some events span across months or event years. I would like to be able to break down the total duration into individual month's duration (or individual duration by month) in hours respectively and populate it into a table
Consider that I have an event with start and end date: '20210620 10:34:09 AM','20211018 10:54:49 PM'
For the first month which basically is not a full month, I am to calculate the remaining hours of that month and store it against that month.
I do the same for the next month. If the event runs for the entire month which is now the month of July, I store the entire hours for that month which is 744 hours against July. I keep doing that till the end of the event. But if the event is still open(blank or empty) I use
GETUTCDATE() as the end date
The sum of Hours is grouped by EventID, EventName, Year and Months
It is expected that the first and or last month may be decimals as they may not be fully formed.
I have tried to work this out but do not know how to get the best result with SQL Server.
I kindly will appreciate your help with this.
Thanks
If I understand correctly, you can try to use CTE recursive, get each month startdate then use antoher cte2 get the starttime and endtime between each month.
;WITH CTE AS (
SELECT EventID,EventName,EventStartDateTime,IIF(EventEndDateTime = '',GETUTCDATE(),EventEndDateTime) EventEndDateTime
FROM EventLog
UNION ALL
SELECT EventID,EventName, DATEADD(month, DATEDIFF(month, 0, DATEADD(month , 1 , EventStartDateTime)), 0) , EventEndDateTime
FROM CTE
WHERE DATEADD(month, DATEDIFF(month, 0, DATEADD(month , 1 , EventStartDateTime)), 0) <= EventEndDateTime
), CTE2 AS (
SELECT EventID,EventName,EventStartDateTime,LEAD(EventStartDateTime,1,EventEndDateTime) OVER(PARTITION BY EventID,EventName ORDER BY EventStartDateTime) n_EventStartDateTime
FROM CTE
)
INSERT INTO EventSummary(EventID,EventName,Year,MonthName,Hours)
SELECT EventID,EventName,YEAR(EventStartDateTime),DATENAME(MONTH,EventStartDateTime),DATEDIFF(second, EventStartDateTime, n_EventStartDateTime) / 3600.0
FROM CTE2
option (maxrecursion 0)
sqlfiddle

Calculating daily average transaction balance of IDs

need to calculate daily average balance of ids. firstdate is when the ID was generated, currentdate is the dates with balance shown for the IDs.
I am expecting something like this [image 2](manually calculated) for each IDs. So basically need to calculate numbers of days between the firstdate when the id was generated and the last currentdate and the balance should be filled in the blank (between dates) to accurately calculating the daily average.
I created the calender table but not sure how I can get the balance for everyday in order to calculate the average.
CREATE TABLE #Calendar
(
[CalendarDate] DATE
)
DECLARE #StartDate DATE
DECLARE #EndDate DATE
SET #StartDate = '20000101'
SET #EndDate = GETDATE()
WHILE #StartDate <= #EndDate
BEGIN
INSERT INTO #Calendar
(
CalendarDate
)
SELECT
#StartDate
SET #StartDate = DATEADD(day, 1, #StartDate)
Thanks for any help.
Assuming the last observation is weighted to getdate()
There is no need for a calendar table, you can use the window function lead() over() to determine the number of days.
Example
Select ID
,ADB = sum(Balance*Days)/sum(Days)
From (
Select *
,Days = datediff(Day,CurrentDate,lead(CurrentDate,1,getdate() ) over (partition by ID order by CurrentDate ) )
from YourTable
) A
Group By ID
Results
ID ADB
110 109.5597
EDIT
I should add that the window functions can invaluable. They are well worth your time getting comfortable with them.

Count # of Saturdays given a date range

I have a datetime field and a net field. The Sat Count field is done by =IIf(DatePart("w",Fields!DespatchDate.Value)=7,1,0)
I want to total the count of the Saturdays given a starting date and end date (typically a month).
I tried =Sum(IIf(DatePart("w",Fields!DespatchDate.Value)=7,1,0) but the total is wrong.
I also want to count Saturdays for rest of the month, e.g there's a missing 3rd Saturday in the picture.
I also want to do a total of the Net for Saturdays.
Can you point me in the direction. I can do it in SQL or in SSRS
Considering that we do not have any Input or desired output provided, I am assuming that You just want to count Saturdays in a given range:
Select COUNT(*), SUM(Net)
FROM table
WHERE Day# = 7 AND Date BETWEEN '2021-02-16' AND '2021-02-23'
Assuming you want to count saturdays even if it is not part of your dataset, what you need to do is pad out all your dates for the given range and then join it to your base data set.
This would ensure that it accounts for ALL days of the week regardless of a dispatch event occuring on that date / day.
Below is some SQL code that might help you make a start.
declare #startdate date = '2021-02-01'
declare #enddate date = '2021-02-28'
if OBJECT_ID ('tempdb..#dates') is not null
drop table #dates
;WITH mycte AS
(
SELECT CAST(#startdate AS DATETIME) DateValue
UNION ALL
SELECT DateValue + 1
FROM mycte
WHERE DateValue + 1 < #enddate
)
SELECT DateValue into #dates
FROM mycte
OPTION (MAXRECURSION 0)
select
d.DateValue
, datepart(weekday,d.DateValue) as day_no
,case when datepart(weekday,d.DateValue) = 7 then isnull(t.net,0) else 0 end as sat_net
,case when datepart(weekday,d.DateValue) = 1 then isnull(t.net,0) else 0 end as sun_net
from #dates d
left join your_table t
on d.DateValue = t.some_date
drop table #dates
Since I don't know what your required output is, I cannot summarise this any further. But you get the idea!

Finding the first and last business day for every month sql

I am trying to find the first and last business day for every month since 1986.
Using this, I can find the first day of any given month using, but just that month and it does not take into consideration whether it is a business day or not. To make it easier for now, business day is simply weekdays and does not consider public holiday.
SELECT DATEADD(s,0,DATEADD(mm, DATEDIFF(m,0,getdate()),0))
But I am not able to get the correct business day, so I created a calendar table consisting of all the weekdays and thought that I can extract the min(date) from each month, but I am currently stuck.
Date
---------------
1986-01-01
1986-01-02
1986-01-03
1986-01-06
...and so on
I have tried to get the first day of every month instead, but it does not take into account whether the day is a weekend or not. It just simply give the first day of each month
declare #DatFirst date = '20000101', #DatLast date = getdate();
declare #DatFirstOfFirstMonth date = dateadd(day,1-day(#DatFirst),#DatFirst);
select DatFirstOfMonth = dateadd(month,n,#DatFirstOfFirstMonth)
from (select top (datediff(month,#DatFirstOfFirstMonth,#DatLast)+1)
n=row_number() over (order by (select 1))-1
from (values (1),(1),(1),(1),(1),(1),(1),(1)) a (n)
cross join (values (1),(1),(1),(1),(1),(1),(1),(1)) b (n)
cross join (values (1),(1),(1),(1),(1),(1),(1),(1)) c (n)
cross join (values (1),(1),(1),(1),(1),(1),(1),(1)) d (n)
) x
I am wondering if anyone can perhaps shed some light as to how can I best approach this issue.
If you already have your calendar table with all available dates, then you just need to filter by weekday.
SET DATEFIRST 1 -- 1: Monday, 7: Sunday
SELECT
Year = YEAR(T.Date),
Month = MONTH(T.Date),
FirstBusinessDay = MIN(T.Date),
LastBusinessDay = MAX(T.Date)
FROM
Calendar AS T
WHERE
DATEPART(WEEKDAY, T.Date) BETWEEN 1 AND 5 -- 1: Monday, 5: Friday
GROUP BY
YEAR(T.Date),
MONTH(T.Date)
You should use the query to mark these days on your calendar table, so it's easy to access them afterwards.
This is how you can mix it up with the generation of the calendar table (with recursion).
SET DATEFIRST 1 -- 1: Monday, 7: Sunday
declare
#DatFirst date = '20000101',
#DatLast date = getdate();
;WITH AllDays AS
(
SELECT
Date = #DatFirst
UNION ALL
SELECT
Date = DATEADD(DAY, 1, D.Date)
FROM
AllDays AS D
WHERE
D.Date < #DatLast
),
BusinessLimitsByMonth AS
(
SELECT
Year = YEAR(T.Date),
Month = MONTH(T.Date),
FirstBusinessDay = MIN(T.Date),
LastBusinessDay = MAX(T.Date)
FROM
AllDays AS T
WHERE
DATEPART(WEEKDAY, T.Date) BETWEEN 1 AND 5 -- 1: Monday, 5: Friday
GROUP BY
YEAR(T.Date),
MONTH(T.Date)
)
SELECT
*
FROM
BusinessLimitsByMonth AS B
ORDER BY
B.Year,
B.Month
OPTION
(MAXRECURSION 0) -- 0: Unlimited
If you got already a table with all the weekdays only:
select min(datecol), max(datecol)
from BusinessOnlyCalendar
group by year(datecol), month(datecol)
But you should expand your calendar to include all those calculations you might do on date, like FirstDayOfWeek/Month/Quarter/Year, WeekNumber, etc.
When you got a column in your calendar indicating business day yes/no, it's a simple:
select min(datecol), max(datecol)
from calendar
where businessday = 'y'
group by year(datecol), month(datecol)

How to count number of work days and hours extracting public holidays between two dates in SQL

I am new to SQL and stuck in some complex query.
What am I trying to achieve?
I want to calculate following two types of total days between two timestamp fields.
Number of Working Days (Excluding Weekend & Public Holidays)
Number of Total Days (Including Weekend & Public Holidays)
Calculation Condition
If OrderDate time is <= 12:00 PM then start count from 0
If OrderDate Time is > 12:00 PM then start count from -1
If Delivery Date is NULL then count different till Today's Date
Data Model
OrderDate & DeliveryDate resides in 'OrderTable'
PublicHolidayDate resides 'PublicHolidaysTable'
As with many tasks in SQL, this could be solved in multiple ways.
You can use COUNT aggregate operations on the date range with the BETWEEN operator to give you aggregate totals of the weekend days and holidays from a start date (OrderDate) to an end date (DeliveryDate).
This functionality can be combined with CTEs (Common Table Expressions) to give you the end result set you are looking for.
I've put together a query that illustrates one way you could go about doing it. I've also put together some test data and results to illustrate how the query works.
DECLARE #DateRangeBegin DATETIME = '2016-01-01'
, #DateRangeEnd DATETIME = '2016-07-01'
DECLARE #OrderTable TABLE
(OrderNum INT, OrderDate DATETIME, DeliveryDate DATETIME)
INSERT INTO #OrderTable VALUES
(1, '2016-02-12 09:30', '2016-03-01 13:00')
, (2, '2016-03-15 13:00', '2016-03-30 13:00')
, (3, '2016-03-22 14:00', NULL)
, (4, '2016-05-06 10:00', '2016-05-19 13:00')
DECLARE #PublicHolidaysTable TABLE
(PublicHolidayDate DATETIME, Description NVARCHAR(255))
INSERT INTO #PublicHolidaysTable VALUES
('2016-02-15', 'President''s Day')
, ('2016-03-17', 'St. Patrick''s Day')
, ('2016-03-25', 'Good Friday')
, ('2016-03-27', 'Easter Sunday')
, ('2016-05-05', 'Cinco de Mayo')
Some considerations you may of already thought of are that you don't want to count both weekend days and holidays that occur on a weekend, unless your company observes the holiday on the next Monday. For simplicity, I've excluded any holiday that occurs on a weekend day in the query.
You'll also want to limit this type of query to a specific date range.
The first CTE (cteAllDates) gets all dates between the start and end date range.
The second CTE (cteWeekendDates) gets all weekend dates from the first CTE (cteAllDates).
The third CTE (ctePublicHolidays) gets all holidays that occur on weekdays from your PublicHolidaysTable.
The last CTE (cteOrders) fulfills the requirement that the count of total days and working days must begin from the next day if the OrderDate is after 12:00PM and the requirement that the DeliveryDate should use today's date if it is null.
The select statement at the end of the CTE statements gets your total day count, weekend count, holiday count, and working days for each order.
;WITH cteAllDates AS (
SELECT 1 [DayID]
, #DateRangeBegin [CalendarDate]
, DATENAME(dw, #DateRangeBegin) [NameOfDay]
UNION ALL
SELECT cteAllDates.DayID + 1 [DayID]
, DATEADD(dd, 1 ,cteAllDates.CalendarDate) [CalenderDate]
, DATENAME(dw, DATEADD(d, 1 ,cteAllDates.CalendarDate)) [NameOfDay]
FROM cteAllDates
WHERE DATEADD(d,1,cteAllDates.CalendarDate) < #DateRangeEnd
)
, cteWeekendDates AS (
SELECT CalendarDate
FROM cteAllDates
WHERE NameOfDay IN ('Saturday','Sunday')
)
, ctePublicHolidays AS (
SELECT PublicHolidayDate
FROM #PublicHolidaysTable
WHERE DATENAME(dw, PublicHolidayDate) NOT IN ('Saturday', 'Sunday')
)
, cteOrders AS (
SELECT OrderNum
, OrderDate
, CASE WHEN DATEPART(hh, OrderDate) >= 12 THEN DATEADD(dd, 1, OrderDate)
ELSE OrderDate
END [AdjustedOrderDate]
, CASE WHEN DeliveryDate IS NOT NULL THEN DeliveryDate
ELSE GETDATE()
END [DeliveryDate]
FROM #OrderTable
)
SELECT o.OrderNum
, o.OrderDate
, o.DeliveryDate
, DATEDIFF(DAY, o.AdjustedOrderDate, o.DeliveryDate) [TotalDayCount]
, (SELECT COUNT(*) FROM cteWeekendDates w
WHERE w.CalendarDate BETWEEN o.AdjustedOrderDate AND o.DeliveryDate) [WeekendDayCount]
, (SELECT COUNT(*) FROM ctePublicHolidays h
WHERE h.PublicHolidayDate BETWEEN o.AdjustedOrderDate AND o.DeliveryDate) [HolidayCount]
, DATEDIFF(DAY, o.AdjustedOrderDate, o.DeliveryDate)
- (SELECT COUNT(*) FROM cteWeekendDays w
WHERE w.CalendarDate BETWEEN o.AdjustedOrderDate AND o.DeliveryDate)
- (SELECT COUNT(*) FROM ctePublicHolidays h
WHERE h.PublicHolidayDate BETWEEN o.AdjustedOrderDate AND o.DeliveryDate) [WorkingDays]
FROM cteOrders o
WHERE o.OrderDate BETWEEN #DateRangeBegin AND #DateRangeEnd
OPTION (MaxRecursion 500)
Results from the above query using the test data...
What I'd probably do is simplify the above by adding a Calendar table populated with sufficiently wide date ranges. Then I'd take some of the CTE statements and turn them into views.
I think specifically valuable to you would be a view that gets you the work days without weekends or holidays. Then you could just simply get the date difference between the two dates and count the work days in the same range.