FiscalYear Calendar in SQL Server - sql-server

I have a requirement to create a Financial year Calendar with
start date = 2019-03-31 and end_date = 2020-03-28
declare #startdate date = '2019-03-30'
declare #enddate date = '2020-03-28'
declare #dates table (date date, MonthNo int)
while #startdate < #enddate
BEGIN
set #startdate = dateadd(dd,1,#startdate)
insert #dates (date)
select #startdate
END
select * from #dates
I will need to populate month number column
MonthNumber : In this case, It can not be obtained through typical query using
DATEPART(MONTH, [date])
Requirement : Month number should be starting at 1 and should be assigned as shown below
NoOfDays trading_month
28 1
28 2
35 3
28 4
28 5
35 6
28 7
28 8
35 9
28 10
28 11
35 12
Ex: Trading month = 1 is between 2019-03-31 and 2019-04-28 and next 28 days = month 2 and next 35 days month = 3 etc..
ps: I do not want to use case statement 12 times to populate month number, is there any alternate way to achieve this
can achieve this way
select *, case when ROW_NUMBER() over (order by date) between 1 and 28 then 1
when ROW_NUMBER() over (order by date) between 29 and 28+28 then 2
when ROW_NUMBER() over (order by date) between 28+28+1 and 28+29+35 then 3
----so on
end MonthNo
from #dates

Here is one option.
Example
Declare #Date1 date = '2019-03-31'
Select [Date]=DateAdd(DAY,Row_Number() over (Order by M,D)-1,#Date1)
,[Year]=DatePart(YEAR,#Date1)
,[Month]=M
From (
Select *
From (Select Top (28) D=Row_Number() Over (Order By (Select Null)) From master..spt_values n1 ) A
Cross Join (Values (1),(2),(4),(5),(7),(8),(10),(11)) B(M)
Union All
Select *
From ( Select Top (35) D=Row_Number() Over (Order By (Select Null)) From master..spt_values n1 ) A
Cross Join (Values (3),(6),(9),(12)) B(M)
) A
Order by 1,2
Returns
Date Year Month
2019-03-31 2019 1
2019-04-01 2019 1
2019-04-02 2019 1
2019-04-03 2019 1
2019-04-04 2019 1
2019-04-05 2019 1
2019-04-06 2019 1
2019-04-07 2019 1
2019-04-08 2019 1
2019-04-09 2019 1
...
2020-03-21 2019 12
2020-03-22 2019 12
2020-03-23 2019 12
2020-03-24 2019 12
2020-03-25 2019 12
2020-03-26 2019 12
2020-03-27 2019 12
2020-03-28 2019 12

Lucky you ... Download and execute the code in my article SQL Server Calendar Table, then download and modify the code in SQL Server Calendar Table: Fiscal Years to fit your definition of a fiscal year and execute that.
I once had a client that ran on 'Crop year' where their fiscal years started the last week of May, and the only way to pull that off was to create a wompload of T-SQL to populate a Calendar table. Once that was done for any given dataset all I had to do was JOIN on the date column and then I could get all the fiscal-related column values I wanted without having to recalculate them every time.
{edit: Here's the relevant code that creates the 4-4-5 from the above Fiscal Weeks article}
USE calendar
GO
/*
Calendar table: Populate the six fiscal_ columns
2015-09-16
*/
Declare #dtYearStart date, #dtStart date, #dtEnd date, #dt date
Declare #fiscal_month tinyint = 1, #fiscal_year smallint , #fiscal_week_in_month tinyint, #fiscal_week_in_year tinyint, #fiscal_day_in_week tinyint, #fiscal_day_in_month tinyint
Declare #counter int = 1, #counter_year int = 1, #counter_month int = 1, #counter_week int = 1, #counter_day int = 1
-- Run this for 19 years from May 2000 to May 2020
WHILE #counter_year <= 19
begin
-- Per the article image, the last day of the year is the last Sunday in May.
SELECT #dtYearStart = MAX(PKDate), #dtEnd = MAX(PKDate)
FROM days
WHERE continuous_year = #counter_year AND calendar_month = 5 AND calendar_day_in_week = 1
-- YEARS and MONTHS
-- Set the year
SELECT #fiscal_year = YEAR(#dtYearStart) + 1, #fiscal_month = 1
SET #counter = 1
WHILE #counter <= 12
begin
SELECT #dtStart = DATEADD(day, 1, #dtEnd)
SELECT #dtEnd = DATEADD(day, CASE WHEN #fiscal_month IN (1, 4, 7, 10) THEN 34 ELSE 27 END, #dtStart)
UPDATE days
SET fiscal_year = #fiscal_year, fiscal_month = #fiscal_month
FROM days
WHERE PKDate >= #dtStart AND PKDate <= #dtEnd
;WITH ro AS (SELECT PKDate, RANK() OVER (ORDER BY PKDate) as row_order FROM days WHERE fiscal_year = #fiscal_year AND fiscal_month = #fiscal_month)
UPDATE days
SET fiscal_day_in_month = row_order
FROM days
JOIN ro ON days.PKDate = ro.PKDate
-- TESTING ONLY, comment the below line out in production
-- SELECT 'Year and Month' as label, PKDate, fiscal_year, fiscal_month, fiscal_day_in_month FROM days WHERE PKDate >= #dtStart AND PKDate <= #dtEnd
SELECT #counter = #counter + 1, #fiscal_month = #fiscal_month + 1
end
-- WEEKS
SELECT #counter = 1, #counter_week = 1, #dtEnd = #dtYearStart
WHILE #counter <= 52
begin
SELECT #dtStart = DATEADD(day, 1, #dtEnd)
SELECT #dtEnd = DATEADD(day, 6, #dtStart)
UPDATE days
SET fiscal_week_in_month = #counter_week, fiscal_week_in_year = #counter
FROM days
WHERE PKDate >= #dtStart AND PKDate <= #dtEnd
-- TESTING ONLY, comment the below line out in production
-- SELECT 'Week' as label, PKDate, fiscal_week_in_year, fiscal_week_in_month FROM days WHERE PKDate >= #dtStart AND PKDate <= #dtEnd
SELECT #counter = #counter + 1
-- Get the fiscal month of the row to determine if the month has 4 or 5 weeks.
SELECT #fiscal_month = fiscal_month FROM days WHERE PKDate = #dtStart
SELECT #counter_week = CASE
WHEN #fiscal_month IN (1, 4, 7, 10) AND #counter_week = 5 THEN 1
WHEN #fiscal_month IN (1, 4, 7, 10) AND #counter_week < 5 THEN #counter_week + 1
WHEN #fiscal_month NOT IN (1, 4, 7, 10) AND #counter_week = 4 THEN 1
WHEN #fiscal_month NOT IN (1, 4, 7, 10) AND #counter_week < 4 THEN #counter_week + 1 END
end
-- DAYS
;WITH ro AS (SELECT PKDate, RANK() OVER (ORDER BY PKDate) as row_order FROM days WHERE fiscal_year = #fiscal_year)
UPDATE days
SET fiscal_day_in_year = row_order
FROM days
JOIN ro ON days.PKDate = ro.PKDate
SELECT #counter_year = #counter_year + 1
end
Good luck.
Jim

I was able to get the expected results with the following query. The only addition to the original table was an identity column. Using most of your original code:
declare #startdate date = '2019-03-30'
declare #enddate date = '2020-03-28'
declare #dates table (pkindex int IDENTITY(1,1), [date] date, MonthNo tinyint)
while #startdate < #enddate
BEGIN
set #startdate = dateadd(dd,1,#startdate)
insert #dates ([date])
select #startdate
END
DECLARE #requirments TABLE (NoOfDays tinyint, trading_month tinyint)
INSERT INTO #requirments VALUES
(28, 1), (28, 2), (35, 3), (28, 4), (28, 5), (35, 6), (28, 7), (28, 8), (35, 9)
,(28, 10), (28, 11), (35, 12)
UPDATE #dates
SET MonthNo =
(SELECT MIN(R.trading_month)
FROM #requirments R
WHERE pkindex <
(SELECT SUM(R2.NoOfDays)
FROM #requirments R2
WHERE R2.trading_month < R.trading_month + 1
) + 1
)
SELECT * FROM #dates

Not sure if I understand your goal here exactly, why 1 = 28 specifically. However it seems like your datepart idea was inline with your desired solution if you just do this I get the layout you wanted.
declare #startdate date = '2019-03-30'
declare #enddate date = '2020-03-28'
declare #dates table (NoOfDays date, trading_mo int)
while #startdate < #enddate
BEGIN
set #startdate = dateadd(dd,1,#startdate)
insert #dates (NoOfDays)
select #startdate
END
select count(NoOfDays) noOfDays,
datepart(month,NoOfDays) trading_mo
from #dates
group by datepart(month,NoOfDays)
order by trading_mo

Related

SQL Server counting hours between dates excluding Fri 6pm - Mon 6am

I am looking for a SQL server function which can count the number of hours between 2 given datetime values, but excludes the hours between 6pm on Friday and 6am on Monday.
I'd like to be able to use this as a custom datediff, but also as a custom dateadd (eg adding 4 hours to 5pm a Friday, will return following Monday 9am)
I currently have something which excludes Sat/Sun based on the weekday number but this doesn't take the Fri/Mon hours into account.
This uses a number table. Adjust weekend start/end in parmeters:
declare #d1 as datetime = '2018-06-01 05:30:00'
, #d2 as datetime = '2018-06-18 19:45:00'
, #FridayWE as int = 18 --6pm
, #MondayWS as int = 6 --6am
;WITH x AS (SELECT n FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) v(n))
select count(*) as HoursBetweenDatetimes
from (
SELECT dateadd(hour, ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n, dateadd(hour, datediff(hour, 0, #d1), 0)) as [DateHour]
FROM x ones, x tens, x hundreds, x thousands
) a
where not ((DATEPART(dw,[DateHour]) = 6 and DATEPART(hour,[DateHour]) >= #FridayWE)
or (DATEPART(dw,[DateHour]) = 7 )
or (DATEPART(dw,[DateHour]) = 1 )
or (DATEPART(dw,[DateHour]) = 2 and DATEPART(hour,[DateHour]) < #MondayWS))
and [DateHour] < #d2
This is another option you can use, with a calendar table.
This is the generation of the calendar table. For this case it has just days from monday to friday and from 9am to 6pm, one day per row (this can be additional columns for your regular calendar table).
IF OBJECT_ID('tempdb..#WorkingCalendar') IS NOT NULL
DROP TABLE #WorkingCalendar
CREATE TABLE #WorkingCalendar (
StartDateTime DATETIME,
EndDateTime DATETIME)
SET DATEFIRST 1 -- 1: Monday, 7: Sunday
DECLARE #StartDate DATE = '2018-01-01'
DECLARE #EndDate DATE = '2025-01-01'
;WITH RecursiveDates AS
(
SELECT
GeneratedDate = #StartDate
UNION ALL
SELECT
GeneratedDate = DATEADD(DAY, 1, R.GeneratedDate)
FROM
RecursiveDates AS R
WHERE
R.GeneratedDate < #EndDate
)
INSERT INTO #WorkingCalendar (
StartDateTime,
EndDateTime)
SELECT
StartDateTime = CONVERT(DATETIME, R.GeneratedDate) + CONVERT(DATETIME, '09:00:00'),
EndDateTime = CONVERT(DATETIME, R.GeneratedDate) + CONVERT(DATETIME, '18:00:00')
FROM
RecursiveDates AS R
WHERE
DATEPART(WEEKDAY, R.GeneratedDate) BETWEEN 1 AND 5 -- From Monday to Friday
OPTION
(MAXRECURSION 0)
This is the query to calculate time differences between 2 datetimes. You can change the HOUR for anything you want, in all 3 places (MINUTE, SECOND, etc.) and the result will be displayed in that unit.
DECLARE #FromDate DATETIME = '2018-06-15 18:00:00'
DECLARE #ToDate DATETIME = '2018-06-18 10:00:00'
;WITH TimeDifferences AS
(
-- Date completely covered
SELECT
Difference = DATEDIFF(
HOUR,
W.StartDateTime,
W.EndDateTime)
FROM
#WorkingCalendar AS W
WHERE
W.StartDateTime >= #FromDate AND
W.EndDateTime <= #ToDate
UNION ALL
-- Filter start date partially covered
SELECT
Difference = DATEDIFF(
HOUR,
#FromDate,
CASE WHEN W.EndDateTime > #ToDate THEN #ToDate ELSE W.EndDateTime END)
FROM
#WorkingCalendar AS W
WHERE
#FromDate BETWEEN W.StartDateTime AND W.EndDateTime
UNION ALL
-- Filter end date partially covered
SELECT
Difference = DATEDIFF(
HOUR,
CASE WHEN W.StartDateTime > #FromDate THEN W.StartDateTime ELSE #FromDate END,
#ToDate)
FROM
#WorkingCalendar AS W
WHERE
#ToDate BETWEEN W.StartDateTime AND W.EndDateTime
)
SELECT
Total = SUM(T.Difference)
FROM
TimeDifferences AS T
This approach will consider each day from the calendar table, so if a particular day you have reduced hours (or maybe none from a Holiday) then the result will consider it.
You can use this query to add hours. Basically split each calendar range by hour, then use a row number to determine the amount of hours to add. Is this case you can't simply change the HOUR for MINUTE, it will require a few tweaks here and there if you need it.
DECLARE #FromDate DATETIME = '2018-06-14 12:23:12.661'
DECLARE #HoursToAdd INT = 15
;WITH RecursiveHourSplit AS
(
SELECT
StartDateTime = W.StartDateTime,
EndDateTime = W.EndDateTime,
HourSplitDateTime = W.StartDateTime
FROM
#WorkingCalendar AS W
UNION ALL
SELECT
StartDateTime = W.StartDateTime,
EndDateTime = W.EndDateTime,
HourSplitDateTime = DATEADD(HOUR, 1, W.HourSplitDateTime)
FROM
RecursiveHourSplit AS W
WHERE
DATEADD(HOUR, 1, W.HourSplitDateTime) < W.EndDateTime
),
HourRowNumber AS
(
SELECT
R.HourSplitDateTime,
RowNumber = ROW_NUMBER() OVER (ORDER BY R.HourSplitDateTime ASC)
FROM
RecursiveHourSplit AS R
WHERE
#FromDate < R.HourSplitDateTime
)
SELECT
DATETIMEFROMPARTS(
YEAR(R.HourSplitDateTime),
MONTH(R.HourSplitDateTime),
DAY(R.HourSplitDateTime),
DATEPART(HOUR, R.HourSplitDateTime),
DATEPART(MINUTE, #FromDate),
DATEPART(SECOND, #FromDate),
DATEPART(MILLISECOND, #FromDate))
FROM
HourRowNumber AS R
WHERE
R.RowNumber = #HoursToAdd
You can use similar logic to substract amount of hours by creating the row number with the rows that have datetimes before the supplied datetime (instead of after).

How to calculate Saturday in between two dates in SQL Server

Week ends on Saturday and new week starts on Sunday.
I would like to determine date which will be a Saturday and split dates in SQL Server. Example below:
Start_date - 09/11/2018 - Friday
End_date - 12/11/2018 - Monday
Total number of days = 4
I would like split the days and end results as
Start_date - 09/11/2018 - Friday
Date1 - 10/11/2018 - Saturday
Total number of days = 2
Date2 - 11/11/2018 - Sunday
End_date - 12/11/2018 - Monday
Total number of days = 2
Another example ( start_date and end_date if there are more number of Saturdays e.g. )
start-date - 04/05/2017
end_date - 31/05/2017
Then results should be like below :-
Date1 Date2 no. of days.
------------------------------------
04/05/2017 06/05/2017 3
07/05/2017 13/05/2017 7
14/05/2017 20/05/2017 7
21/05/2017 27/05/2017 7
28/05/2017 31/05/2017 4
Please help.
Thanks & Regards,
VG
If I am not mistaken you are looking for something like this:
DECLARE #StartDate DATE = '2017-05-04'
DECLARE #EndDate DATE = '2017-05-31'
DECLARE #OutputTABLE AS TABLE
(
StartDate DATE NOT NULL,
EndDate DATE NOT NULL
)
DECLARE #NumberOfWeeks INT = DATEDIFF(week, #StartDate, #EndDate)
DECLARE #Counter INT = 0
DECLARE #TempDate DATE = DATEADD(week, #Counter, #StartDate)
WHILE #NumberOfWeeks >= 0
BEGIN
IF #NumberOfWeeks = 0
BEGIN
INSERT INTO #OutputTABLE VALUES (#TempDate, #EndDate);
END
ELSE
BEGIN
INSERT INTO #OutputTABLE VALUES (#TempDate, DATEADD(DAY, -1, DATEADD(week, DATEDIFF(week ,0 , #TempDate) + 1, -1)));
END
SET #TempDate = DATEADD(week, #Counter + 1, DATEADD(day, -1, DATEADD(week, DATEDIFF(week, 0, #StartDate), 0)))
SET #NumberOfWeeks = #NumberOfWeeks - 1
SET #Counter = #Counter + 1
END
SELECT StartDate,
EndDate,
DATEDIFF(day, StartDate, EndDate) + 1 AS NumberOfDays
FROM #OutputTABLE
Here is one way. I couldn't figure out a way with a recursive CTE since you can't aggregate in the recursive part of a recursive CTE.
DECLARE #MinDate DATE = '20170504',
#MaxDate DATE = '20170531'
DECLARE #StartDate datetime, #EndDate datetime
--DATE TABLE... ONE ROW FOR EVERY DAY IN RANGE
IF OBJECT_ID('tempdb..#DateTable') IS NOT NULL DROP TABLE #DateTable
SELECT TOP (DATEDIFF(DAY, #MinDate, #MaxDate) + 1)
Dates = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, #MinDate)
INTO #DateTable
FROM sys.all_objects a
CROSS JOIN sys.all_objects b
IF OBJECT_ID('tempdb..#Results') IS NOT NULL DROP TABLE #Results
CREATE TABLE #Results (Date1 DATETIME, Date2 DATETIME, NumOfDays INT)
--INSERT FIRST ROW IN CASE IT STARTS ON A DAY OTHER THAN SUNDAY
INSERT INTO #Results
SELECT
MIN(Dates) as Date1
,MIN(CASE WHEN DATEPART(WEEKDAY,Dates) = 7 THEN Dates END) as Date2
,DATEDIFF(DAY,MIN(Dates),MIN(CASE WHEN DATEPART(WEEKDAY,Dates) = 7 THEN Dates END)) + 1 as NoOfDays
FROM #DateTable
SET #StartDate = (SELECT MIN(CASE WHEN DATEPART(WEEKDAY,Dates) = 1 THEN Dates END) FROM #DateTable)
SET #EndDate = (SELECT MAX(CASE WHEN DATEPART(WEEKDAY,Dates) = 1 THEN Dates END) FROM #DateTable)
--INSERT ALL FULL WEEKS
WHILE #StartDate < #EndDate
BEGIN
INSERT INTO #Results
SELECT
MIN(Dates) as Date1
,MIN(CASE WHEN DATEPART(WEEKDAY,Dates) = 7 THEN Dates END) as Date2
,DATEDIFF(DAY,MIN(Dates),MIN(CASE WHEN DATEPART(WEEKDAY,Dates) = 7 THEN Dates END)) + 1 as NoOfDays
FROM #DateTable
WHERE Dates >= #StartDate
SET #StartDate = DATEADD(DAY,7,#StartDate)
END
--INSERT LAST ROW IF IT ISN'T A FULL WEEK
IF (SELECT MAX(Date2) FROM #Results) <> #MaxDate
BEGIN
INSERT INTO #Results
SELECT
MIN(Dates) as Date1
,MAX(Dates) as Date2
,DATEDIFF(DAY,MIN(Dates),MAX(Dates)) + 1 as NoOfDays
FROM #DateTable
WHERE Dates > (SELECT MAX(Date2) FROM #Results)
END
SELECT * FROM #Results
DROP TABLE #Results
DROP TABLE #DateTable
RETURNS
+-------------------------+-------------------------+-----------+
| Date1 | Date2 | NumOfDays |
+-------------------------+-------------------------+-----------+
| 2017-05-04 00:00:00.000 | 2017-05-06 00:00:00.000 | 3 |
| 2017-05-07 00:00:00.000 | 2017-05-13 00:00:00.000 | 7 |
| 2017-05-14 00:00:00.000 | 2017-05-20 00:00:00.000 | 7 |
| 2017-05-21 00:00:00.000 | 2017-05-27 00:00:00.000 | 7 |
| 2017-05-28 00:00:00.000 | 2017-05-31 00:00:00.000 | 4 |
+-------------------------+-------------------------+-----------+
This will compute your Date2 from your Date1
print dateadd(day,-1,dateadd(wk,datepart(wk,'4/5/2017'),'1/1/2017'))

SQL Server: How to select all days in a date range even if no data exists for some days

I have an app that needs to show a bar graph for activity over the last 30 days. The graph needs to show all days even if there is no activity for the day.
for example:
DATE COUNT
==================
1/1/2011 5
1/2/2011 3
1/3/2011 0
1/4/2011 4
1/5/2011 0
etc....
I could do post processing after the query to figure out what dates are missing and add them but was wondering if there is an easier way to do it in SQL Server. Thanks much
You can use a recursive CTE to build your list of 30 days, then join that to your data
--test
select cast('05 jan 2011' as datetime) as DT, 1 as val into #t
union all select CAST('05 jan 2011' as datetime), 1
union all select CAST('29 jan 2011' as datetime), 1
declare #start datetime = '01 jan 2011'
declare #end datetime = dateadd(day, 29, #start)
;with amonth(day) as
(
select #start as day
union all
select day + 1
from amonth
where day < #end
)
select amonth.day, count(val)
from amonth
left join #t on #t.DT = amonth.day
group by amonth.day
>>
2011-01-04 00:00:00.000 0
2011-01-05 00:00:00.000 2
2011-01-06 00:00:00.000 0
2011-01-07 00:00:00.000 0
2011-01-08 00:00:00.000 0
2011-01-09 00:00:00.000 0
...
Using CTE:
WITH DateTable
AS
(
SELECT CAST('20110101' AS Date) AS [DATE]
UNION ALL
SELECT DATEADD(dd, 1, [DATE])
FROM DateTable
WHERE DATEADD(dd, 1, [DATE]) < cast('20110201' as Date)
)
SELECT dt.[DATE], ISNULL(md.[COUNT], 0) as [COUNT]
FROM [DateTable] dt
LEFT JOIN [MyData] md
ON md.[DATE] = dt.[DATE]
This is assuming everything's a Date; if it's DateTime, you'll have to truncate (with DATEADD(dd, 0, DATEDIFF(dd, 0, [DATE]))).
#Alex K.'s answer is completely correct, but it doesn't work for versions that do not support Recursive common table expressions (like the version I'm working with). In this case the following would do the job.
DECLARE #StartDate datetime = '2015-01-01'
DECLARE #EndDate datetime = SYSDATETIME()
;WITH days AS
(
SELECT DATEADD(DAY, n, DATEADD(DAY, DATEDIFF(DAY, 0, #StartDate), 0)) as d
FROM ( SELECT TOP (DATEDIFF(DAY, #StartDate, #EndDate) + 1)
n = ROW_NUMBER() OVER (ORDER BY [object_id]) - 1
FROM sys.all_objects ORDER BY [object_id] ) AS n
)
select days.d, count(t.val)
FROM days LEFT OUTER JOIN yourTable as t
ON t.dateColumn >= days.d AND t.dateColumn < DATEADD(DAY, 1, days.d)
GROUP BY days.d
ORDER BY days.d;
My scenario was a bit more complex than the OP example, so thought I'd share to help others who have similar issues. I needed to group sales orders by date taken, whereas the orders are stored with datetime.
So in the "days" lookup table I could not really store as a date time with the time being '00:00:00.000' and get any matches. Therefore I stored as a string and I tried to join on the converted value directly.
That did not return any zero rows, and the solution was to do a sub-query returning the date already converted to a string.
Sample code as follows:
declare #startDate datetime = convert(datetime,'09/02/2016')
declare #curDate datetime = #startDate
declare #endDate datetime = convert(datetime,'09/09/2016')
declare #dtFormat int = 102;
DECLARE #null_Date varchar(24) = '1970-01-01 00:00:00.000'
/* Initialize #days table */
select CONVERT(VARCHAR(24),#curDate, #dtFormat) as [Period] into #days
/* Populate dates into #days table */
while (#curDate < #endDate )
begin
set #curDate = dateadd(d, 1, #curDate)
insert into #days values (CONVERT(VARCHAR(24),#curDate, #dtFormat))
end
/* Outer aggregation query to group by order numbers */
select [Period], count(c)-case when sum(c)=0 then 1 else 0 end as [Orders],
sum(c) as [Lines] from
(
/* Inner aggregation query to sum by order lines */
select
[Period], sol.t_orno, count(*)-1 as c
from (
/* Inner query against source table with date converted */
select convert(varchar(24),t_dldt, #dtFormat) as [shipdt], t_orno
from salesorderlines where t_dldt > #startDate
) sol
right join #days on shipdt = #days.[Period]
group by [Period], sol.t_orno
) as t
group by Period
order by Period desc
drop table #days
Sample Results:
Period Orders Lines
2016.09.09 388 422
2016.09.08 169 229
2016.09.07 1 1
2016.09.06 0 0
2016.09.05 0 0
2016.09.04 165 241
2016.09.03 0 0
2016.09.02 0 0
Either define a static table containing dates or create a temp table \ table variable on the fly to store each date between (and including) the min and max dates in the activity table you're working with.
Use an outer join between the two tables to make sure that each date in your dates table is reflected in the output.
If you use a static dates table you will likely want to limit the date range that is output to only the range needed in the graph.
Without Transact-SQL: MS SQL 2005 - Get a list of all days of a Month:
In my case '20121201' is a predefined value.
SELECT TOp (Select Day(DateAdd(day, -Day(DateAdd(month, 1,
'20121201')),
DateAdd(month, 1, '20121201')))) DayDate FROM ( SELECT DATEADD(DAY,ROW_NUMBER() OVER (ORDER BY (SELECT
NULL))-1,'20121201') as DayDate FROM sys.objects s1 CROSS JOIN
sys.objects s2 ) q
Recursive CTE works for max 80 years which is good enough:
DECLARE #dStart DATE,
#dEnd DATE
SET #dStart = GETDATE ()
SET #dEnd = DATEADD (YEAR, 80, #dStart)
;WITH CTE AS
(
SELECT #dStart AS dDay
UNION ALL
SELECT DATEADD (DAY, 1, dDay)
FROM CTE
WHERE dDay < #dEnd
)
SELECT * FROM CTE
OPTION (MaxRecursion 32767)
create a numbers table and use it like:
declare #DataTable table (DateColumn datetime)
insert #DataTable values ('2011-01-09')
insert #DataTable values ('2011-01-10')
insert #DataTable values ('2011-01-10')
insert #DataTable values ('2011-01-11')
insert #DataTable values ('2011-01-11')
insert #DataTable values ('2011-01-11')
declare #StartDate datetime
SET #StartDate='1/1/2011'
select
#StartDate+Number,SUM(CASE WHEN DateColumn IS NULL THEN 0 ELSE 1 END)
FROM Numbers
LEFT OUTER JOIN #DataTable ON DateColumn=#StartDate+Number
WHERE Number>=1 AND Number<=15
GROUP BY #StartDate+Number
OUTPUT:
----------------------- -----------
2011-01-02 00:00:00.000 0
2011-01-03 00:00:00.000 0
2011-01-04 00:00:00.000 0
2011-01-05 00:00:00.000 0
2011-01-06 00:00:00.000 0
2011-01-07 00:00:00.000 0
2011-01-08 00:00:00.000 0
2011-01-09 00:00:00.000 1
2011-01-10 00:00:00.000 2
2011-01-11 00:00:00.000 3
2011-01-12 00:00:00.000 0
2011-01-13 00:00:00.000 0
2011-01-14 00:00:00.000 0
2011-01-15 00:00:00.000 0
2011-01-16 00:00:00.000 0
(15 row(s) affected)
Maybe something like this:
Create DaysTable countaining the 30 days.
And DataTable containing "day" column and "count" column.
And then left join them.
WITH DaysTable (name) AS (
SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 -- .. And so on to 30
),
DataTable (name, value) AS (
SELECT DATEPART(DAY, [Date]), [Count]
FROM YourExampleTable
WHERE [Date] < DATEADD (day , -30 , getdate())
)
SELECT DaysTable.name, DataTable.value
FROM DaysTable LEFT JOIN
DataTable ON DaysTable.name = DataTable.name
ORDER BY DaysTable.name
For those with a recursion allergy
select SubQ.TheDate
from
(
select DATEADD(day, a.a + (10 * b.a) + (100 * c.a), DATEADD(day, DATEDIFF(day, 0, GETDATE()), 0) - 30) AS TheDate
from
(
(select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as a
cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as b
cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as c
)
WHERE a.a + (10 * b.a) + (100 * c.a) < 30
) AS SubQ
ORDER BY TheDate
Try it.
DECLARE #currentDate DATETIME = CONVERT(DATE, GetDate())
DECLARE #startDate DATETIME = DATEADD(DAY, -DAY(#currentDate)+1, #currentDate)
;WITH fnDateNow(DayOfDate) AS
(
SELECT #startDate AS DayOfDate
UNION ALL
SELECT DayOfDate + 1 FROM fnDateNow WHERE DayOfDate < #currentDate
) SELECT fnDateNow.DayOfDate FROM fnDateNow
DECLARE #StartDate DATE = '20110101', #NumberOfYears INT = 1;
DECLARE #CutoffDate DATE = DATEADD(YEAR, #NumberOfYears, #StartDate);
CREATE TABLE Calender
(
[date] DATE
);
INSERT Calender([date])
SELECT d
FROM
(
SELECT d = DATEADD(DAY, rn - 1, #StartDate)
FROM
(
SELECT TOP (DATEDIFF(DAY, '2011-01-01', '2011-12-31'))
rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
ORDER BY s1.[object_id]
) AS x
) AS y;
create table test(a date)
insert into test values('1/1/2011')
insert into test values('1/1/2011')
insert into test values('1/1/2011')
insert into test values('1/1/2011')
insert into test values('1/1/2011')
insert into test values('1/2/2011')
insert into test values('1/2/2011')
insert into test values('1/2/2011')
insert into test values('1/4/2011')
insert into test values('1/4/2011')
insert into test values('1/4/2011')
insert into test values('1/4/2011')
select c.date as DATE,count(t.a) as COUNT from calender c left join test t on c.date = t.a group by c.date

Grouping date periods (by number of days) but exclude the weekends?

I have a table with start and end dates in. My goal is to have a table that has grouped these dates into how many days the period spans. I thought I had the solution with a simple SQL statement (MS SQL Server 2005) but I want to exclude weekends.
SELECT DATEDIFF(D, StartDate, EndDate)+1 AS Days,
COUNT(ID) as Count
FROM myDateTable
GROUP BY DATEDIFF(D, StartDate, EndDate)
This gives a record set of:
Days Count
1 4
2 2
4 1
7 2
Is this possible to exclude the weekends in the SQL statement and if not can it be done using ASP and a array perhaps?
Well then, using Sql Server 2005, you can try something like
DECLARE #Table TABLE(
ID INT,
StartDate DATETIME,
EndDate DATETIME
)
INSERT INTO #Table (ID,StartDate,EndDate) SELECT 1, '25 Jan 2009', '31 Jan 2009'
INSERT INTO #Table (ID,StartDate,EndDate) SELECT 2, '01 Jan 2009', '07 Jan 2009'
INSERT INTO #Table (ID,StartDate,EndDate) SELECT 3, '01 Jan 2009', '14 Jan 2009'
DECLARE #MinDate DATETIME,
#MaxDate DATETIME
SELECT #MinDate = MIN(StartDate) ,
#MaxDate = MAX(EndDate)
FROM #Table
--Create a temp result set between the Min and Max dates, with all dates, and their weekday names
;WITH DayValues AS(
SELECT #MinDate DateVal,
DATENAME(dw, #MinDate) DateValName
UNION ALL
SELECT DateVal + 1,
DATENAME(dw, DateVal + 1) DateValName
FROM DayValues
WHERE DateVal + 1 <= #MaxDate
),
--select the count of days for each StartDate and EndDate pair, excluding Saturdays and Sundays
DateCounts AS(
SELECT ID,
(
SELECT COUNT(1)
FROM DayValues
WHERE DateVal BETWEEN StartDate AND EndDate
AND DateValName NOT IN ('Saturday', 'Sunday')
) DateCount
FROM #Table
)
--Now group and count
SELECT DateCount,
COUNT(ID) TotalCount
FROM DateCounts
GROUP BY DateCount
OPTION (MAXRECURSION 0)
Output
DateCount TotalCount
----------- -----------
5 2
10 1
EDIT: Brief Explenation
You need to determine the number of days between (and including) 2 dates, that are not weekends.
So using a CTE, I create a temporary result set of dates ebwteen the Min and Max dates, and their Weekday Name (eg Monday, Tuesday... Sunday).
Then, for each of your date pairs, I count the number of entries that does not correspond to Saturday and Sunday.
Here's an ASP function that counts days weekdays between two dates.
<%
Dim StartDate, EndDate
StartDate = CDate("1/1/2010")
EndDate = CDate("2/1/2010")
Response.Write(WeekDayCount(StartDate, EndDate))
Function WeekDayCount(StartDate, EndDate)
dim tempDate, dayCount
tempDate = StartDate
dayCount = 0
'Step forward one day, counting non-week days
While tempDate <> EndDate
'The 1 and 7 might need to be tweaked depending on the locale of your
'server. 1 = Sunday, 7 = Saturday
If DatePart("w", tempDate) <> 1 And DatePart("w", tempDate) <> 7 Then
dayCount = dayCount + 1
End If
tempDate = DateAdd("d", 1, tempDate)
Wend
WeekDayCount = dayCount
End Function
%>
Have a look at ##DATEFIRST,
and look a this;
SELECT DATEPART(DW,GETDATE()).
You should be able to run a query WHERE the 'DW' is not equal to the weekend numbers.

TSQL: How to Split dates and count months in between

I need to split these dates by FY and get the number of months between the dates. I have the FY split answered previously at How to duplicate Rows with new entries link.
Having the following dates:-
ID Start dt End dt
2550 10/1/2010 9/30/2011
2551 8/1/2014 7/31/2015
2552 6/1/2013 5/31/2015
2553 5/10/2012 6/11/2014
I would like the following result set:
ID FY # of Months Start Dt End Dt
2550 2011 12 10/1/2010 9/30/2011
2551 2014 2 8/1/2014 9/30/2014
2551 2015 10 10/1/2014 7/31/2015
2552 2013 4 6/1/2013 9/30/2013
2552 2014 12 10/1/2013 9/30/2014
2552 2015 8 10/1/2014 5/31/2015
2553 2012 5 5/10/2012 9/30/2012
2553 2013 12 10/1/2012 9/30/2013
2553 2014 9 10/1/2013 6/11/2014
An FY is considered from Oct to Sept of the next year.
Thanks in advance!
It was a fun query to build:
declare #t table(id int, start_dt datetime, end_dt datetime)
Insert into #t(id, start_dt, end_dt) Values
(2550, '2010/10/1', '2011/9/30')
, (2551, '2014/8/1', '2015/7/31')
, (2552, '2013/6/1', '2015/5/31')
, (2553, '2012/5/10', '2014/6/11')
, (2554, '2012/5/10', '2012/11/1')
, (2555, '2012/5/10', '2012/8/11')
; with data as(
Select id, start_dt, end_dt, next_y = DATEADD(month, 9, DATEADD(year, DATEDIFF(year, 0, DATEADD(month, 3, start_dt)), 0))
From #t
), split as (
Select id, start_dt
, end_dt = case when next_y > end_dt then end_dt else DATEADD(day, -1, next_y) end
, next_y = DATEADD(year, 1, next_y)
From data as d
Union All
Select s.id
, DATEADD(day, 1, s.end_dt)
, case when DATEADD(day, -1, next_y) < t.end_dt then DATEADD(day, -1, next_y) else t.end_dt end
, next_y = DATEADD(year, 1, s.next_y)
From split as s
Inner Join #t as t on t.id = s.id
Where s.end_dt < t.end_dt
)
Select ID, FY = year(end_dt), '# of Months' = DATEDIFF(month, start_dt, end_dt)+1, 'Start Dt' = start_dt, 'End Dt' = end_dt
From split
Order by id, start_dt, end_dt

Resources