I am very new in SQL (started just 3 days back) and I got an issue and would hope someone is able to assist me.
I have a query that creates date loops to return me the week number based on the passed in dates. The idea here is to pass loop through the days, months and years and return me a week value. But because of these, I have been getting multiple entries of the same result.
For eg. I am getting:
period_wk_key period_yr_key period_week period_week_day period_week_full_desc
--------------- -------------- ------------ -------------- -----------------------
200001 2000 1 6 2000 WEEK 1
200001 2000 1 6 2000 WEEK 1
200001 2000 1 6 2000 WEEK 1
. . . . .
200002 2000 2 6 2000 WEEK 2
200002 2000 2 6 2000 WEEK 2
200002 2000 2 6 2000 WEEK 2
. . . . .
200003 2000 3 6 2000 WEEK 3
each period_wk_key is returning me 7 similar rows which is not what I wanted.
The ideal situation should be:
period_wk_key period_yr_key period_week period_week_day period_week_full_desc
--------------- -------------- ------------ -------------- -----------------------
200001 2000 1 6 2000 WEEK 1
200002 2000 2 6 2000 WEEK 2
200003 2000 3 6 2000 WEEK 3
200004 2000 4 6 2000 WEEK 4
I need to know a way to limit the number of output from a loop so that I can have distinct records instead of multiple similar rows. I have attached a code below. It maybe crap to many of you but this is just my 3rd day beginning scripting. Much thanks for any help rendered.
DECLARE #iStartYear INT
SET #iStartYear = 2000
DECLARE #iEndYear INT
SET #iEndYear = 2030
DECLARE #iMth INT
SET #iMth = 1
DECLARE #iDay INT
SET #iDay = 1
DECLARE #iWeek INT
`DECLARE #StartDate DATETIME
WHILE (#iStartYear <= #iEndYear)
BEGIN
WHILE (#iMth <= 12)
BEGIN
WHILE (#iDay <= DATEDIFF(DAY,DATEADD(DAY, 0, DATEADD(m, ((#iStartYear - 1900) * 12) + #iMth - 1, 0)),DATEADD(DAY, 0, DATEADD(m, ((#iStartYear - 1900) * 12) + #iMth, 0)))))
BEGIN `
SET #iWeek = (DATEPART(dy, CONVERT(DATETIME, CONVERT(VARCHAR(4), #iStartYear) + '/' + CONVERT(VARCHAR(2), #iMth) + '/' + CONVERT(VARCHAR(2), #iDay))) - 1) / 7 + 1;
INSERT INTO dim_period_week (period_wk_key, period_yr_key, period_week, period_week_full_desc,start_date, end_date, period_week_day)
VALUES (
(SELECT CASE WHEN #iWeek < 10 THEN
CAST((#iStartYear) AS VARCHAR) + '0' + CAST((#iWeek) AS VARCHAR)
ELSE
CAST((#iStartYear) AS VARCHAR)+ CAST((#iWeek) AS VARCHAR)
END),
#iStartYear,
(SELECT (DATEPART(dy, CONVERT(DATETIME, CONVERT(VARCHAR(4), #iStartYear) + '/' + CONVERT(VARCHAR(2), #iMth) + '/' + CONVERT(VARCHAR(2), #iDay))) - 1) / 7 + 1),
(SELECT CAST((#iStartYear) AS VARCHAR) + ' ' + 'WEEK' + ' ' + CAST((#iWeek) AS VARCHAR)),
(SELECT DATEADD(wk,#iWeek-1, DATEADD(yy,#iStartYear-1900,0))), --START DATE
(SELECT DATEADD(wk,#iWeek, DATEADD(yy,#iStartYear-1900,0)) -1), --END DATE
(SELECT DATEDIFF(DAY, DATEADD(wk,#iWeek-1, DATEADD(yy,#iStartYear-1900,0)), DATEADD(wk,#iWeek, DATEADD(yy,#iStartYear-1900,0)) -1))
)
`SET #iDay = #iDay + 1
END
SET #iDay = 1
SET #iMth = #iMth + 1
END
SET #iMth = 1
SET #iStartYear = #iStartYear + 1
END
`
You are looping every day, but never storing at the day level, so you are doing 7 times as many INSERTs as needed. Add the following just above your INSERT statement
IF DATEPART(weekday, CAST(CAST(#iStartYear * 10000 + #iMth * 100 + #iDay AS VARCHAR(8)) AS datetime)) = 1
INSERT INTO dim_period_week ...
Below is a set based approach using a numbers table.
IF OBJECT_ID('tempdb.dbo.#Numbers') IS NOT NULL DROP TABLE #Numbers
GO
DECLARE #startDate DATETIME = '20000101'
SELECT
(a.Number * 256) + b.Number AS Number
INTO #Numbers
FROM
(SELECT number FROM master..spt_values WHERE type = 'P' AND number <= 255) a (Number),
(SELECT number FROM master..spt_values WHERE type = 'P' AND number <= 255) b (Number)
WHERE
(a.Number * 256) + b.Number BETWEEN 0 AND 1617
SELECT
WeekKey = CAST(YEAR(DATEADD(day, N.Number * 7, #startDate)) * 100 + DATEPART(week, DATEADD(day, N.Number * 7, #startDate)) AS VARCHAR(10)),
TheYear = YEAR(DATEADD(day, N.Number * 7, #startDate)),
TheWeek = DATEPART(week, DATEADD(day, N.Number * 7, #startDate)),
TheWeekDay= DATEPART(weekday, DATEADD(day, N.Number * 7, #startDate)),
WeekDetail= DATENAME(year, DATEADD(day, N.Number * 7, #startDate)) + ' WEEK ' + DATENAME(week, DATEADD(day, N.Number * 7, #startDate)),
StartDate = DATEADD(day, N.Number * 7, #startDate),
EndDate = DATEADD(day, 6, DATEADD(day, N.Number * 7, #startDate))
FROM #Numbers N
ORDER BY 1
Related
I have the following table:
id
StartDate
EndDate
1
01/03/2021
24/09/2022
2
11/06/2021
19/12/2022
3
17/09/2021
22/03/2022
4
21/05/2021
30/05/2022
and I have 2 inputs, month and year, what I need is to get the dates where the selected month/year is within the start and end date, for example, the input is 07/2022, the results I need are:
id
StartDate
EndDate
1
01/03/2021
24/09/2022
2
11/06/2021
19/12/2022
what I tried:
select
*
from
contracts
where
(year(startdate) >= #year
and
month(startdate) >=#month)
and
(year(enddate) <= #year
and
month(enddate) <=#month)
ClumZZZey's approach is correct. Seems like there is a small mistake on the query. Try this instead.
SELECT *
FROM contracts
WHERE (year(startdate) * 100 + month(startdate)) <= #year * 100 + #month
AND (year(enddate) * 100 + month(enddate)) >= #year * 100 + #month
Decoupling the month and the year makes it tricky. You want to keep the together.
For example
SELECT *
FROM contracts
WHERE (year(startdate) * 100 + month(startdate)) <= #year * 100 + #month
AND (year(enddate) * 100 + month(enddate)) >= #year * 100 + #month
EDIT: switched the < and > to the correct places.
This question already has answers here:
SQL Server dynamic PIVOT query?
(9 answers)
Closed 12 months ago.
So I have 36 month worth of data in my CTE, and the database is still active storing new data daily.
In my analysis, I just need 6 months worth of data from getdate()
So my question is that how do I make the month derived from calendar month the column of my table?
So for end of March 2022, this is the view that I should see:
id
name
10/01/21
11/01/21
12/01/21
01/01/22
02/01/22
03/01/2022
1
John
3
0
1
0
0
2
2
Mary
6
1
2
1
1
2
3
Angelo
1
5
3
2
2
0
4
Diane
3
2
0
1
0
6
So for the end of April 2022, this is the view that I should see:
id
name
11/01/21
12/01/21
01/01/22
02/01/22
03/01/2022
04/01/22
1
John
0
1
0
0
2
7
2
Mary
1
2
1
1
2
2
3
Angelo
5
0
0
0
0
3
4
Diane
2
0
1
0
6
4
You can't have a dynamic PIVOT without dynamic SQL. I find it's easiest to break it up into parts.
Get the last 6 months
DECLARE #thisMonth date, #firstMonth date;
SET #thisMonth = DATEFROMPARTS(YEAR(getdate()), MONTH(getdate()), 1);
SET #firstMonth = DATEADD(MONTH, -5, #thisMonth);
;WITH m(m) AS
(
SELECT #firstMonth
UNION ALL
SELECT DATEADD(MONTH, 1, m) FROM m
WHERE m < #thisMonth
)
SELECT m FROM m ORDER BY m DESC;
Output:
m
2022-03-01
2022-02-01
2022-01-01
2021-12-01
2021-11-01
2021-10-01
Figure out what manual query you need. Given this sample data:
CREATE TABLE dbo.JetSales
(
ID int,
Name nvarchar(32),
SalesDate date
);
INSERT dbo.JetSales(ID, Name, SalesDate) VALUES
(1,N'John','20211005'),(1,N'John','20211016'),(1,N'John','20211031'),
(2,N'Mary','20211007'),(2,N'Mary','20211013'),
(3,N'Tank','20211009');
I think you want a query like this (yes, you can accomplish this specific task with PIVOT too, but PIVOT doesn't cover some other scenarios, and it also requires pre-aggregation in this case... so I think conditional aggregation is better):
SELECT ID, Name,
[10/01/2021] = SUM(CASE WHEN SalesDate >= '20211001'
AND SalesDate < '20211101' THEN 1 ELSE 0 END),
[11/01/2021] = SUM(CASE WHEN SalesDate >= '20211101'
AND SalesDate < '20211201' THEN 1 ELSE 0 END),
[12/01/2021] = SUM(CASE WHEN SalesDate >= '20211201'
AND SalesDate < '20220101' THEN 1 ELSE 0 END),
[01/01/2022] = SUM(CASE WHEN SalesDate >= '20220101'
AND SalesDate < '20220201' THEN 1 ELSE 0 END),
[02/01/2022] = SUM(CASE WHEN SalesDate >= '20220201'
AND SalesDate < '20220301' THEN 1 ELSE 0 END),
[03/01/2022] = SUM(CASE WHEN SalesDate >= '20220301'
AND SalesDate < '20220401' THEN 1 ELSE 0 END)
FROM dbo.JetSales AS js GROUP BY ID, Name;
Which you can build as follows:
DECLARE #thisMonth date, #firstMonth date;
SET #thisMonth = DATEFROMPARTS(YEAR(getdate()), MONTH(getdate()), 1);
SET #firstMonth = DATEADD(MONTH, -5, #thisMonth);
DECLARE #sql nvarchar(max) = N'SELECT ID, Name';
;WITH m(m) AS
(
SELECT #firstMonth
UNION ALL
SELECT DATEADD(MONTH, 1, m) FROM m
WHERE m < #thisMonth
)
SELECT #sql += N',
' + QUOTENAME(CONVERT(char(10), m, 101))
+ N' = SUM(CASE WHEN SalesDate >= '
+ QUOTENAME(CONVERT(char(8), m, 112), char(39)) + N'
AND SalesDate < '
+ QUOTENAME(CONVERT(char(8), DATEADD(MONTH, 1, m), 112), char(39))
+ N' THEN 1 ELSE 0 END)'
FROM m;
SET #sql += N'
FROM dbo.JetSales AS js GROUP BY ID, Name;';
SELECT #sql;
EXEC sys.sp_executesql #sql;
Working example: db<>fiddle
If you really want to use PIVOT explicitly, you can, it's just a lot more cumbersome. Here's the query you want to end up with:
;WITH src AS
(
SELECT ID, Name, m = CONVERT(char(10),
DATEFROMPARTS(YEAR(SalesDate), Month(SalesDate), 1), 101)
FROM dbo.JetSales
WHERE SalesDate >= #firstMonth
),
agg AS
(
SELECT ID, Name, m, c = COUNT(*)
FROM src GROUP BY ID, Name, m
)
SELECT ID, Name,
[10/01/2021] = COALESCE([10/01/2021], 0),
[11/01/2021] = COALESCE([11/01/2021], 0),
[12/01/2021] = COALESCE([12/01/2021], 0),
[01/01/2022] = COALESCE([01/01/2022], 0),
[02/01/2022] = COALESCE([02/01/2022], 0),
[03/01/2022] = COALESCE([03/01/2022], 0)
FROM agg PIVOT (SUM(c) FOR m IN (
[10/01/2021],[11/01/2021],[12/01/2021],
[01/01/2022],[02/01/2022],[03/01/2022]
)) AS p;
To get there:
DECLARE #thisMonth date, #firstMonth date;
SET #thisMonth = DATEFROMPARTS(YEAR(getdate()), MONTH(getdate()), 1);
SET #firstMonth = DATEADD(MONTH, -5, #thisMonth);
DECLARE #col1 nvarchar(max) = N'',
#col2 nvarchar(max) = N'',
#sql nvarchar(max) = N';WITH src AS
(
SELECT ID, Name, m = CONVERT(char(10),
DATEFROMPARTS(YEAR(SalesDate), Month(SalesDate), 1), 101)
FROM dbo.JetSales
WHERE SalesDate >= #firstMonth
),
agg AS
(
SELECT ID, Name, m, c = COUNT(*)
FROM src GROUP BY ID, Name, m
)
SELECT ID, Name,';
;WITH m(m) AS
(
SELECT #firstMonth
UNION ALL
SELECT DATEADD(MONTH, 1, m) FROM m
WHERE m < #thisMonth
),
x(x) AS
(
SELECT QUOTENAME(CONVERT(char(10), m, 101)) FROM m
)
SELECT
#col1 += STRING_AGG(CONCAT(N'
', x, N' = COALESCE(', x, ',0)'),N','),
#col2 += STRING_AGG(x, N',
')
FROM x;
SET #sql += #col1 + N'
FROM agg PIVOT (SUM(c) FOR m IN ('
+ #col2 + N'
)) AS p;';
SELECT #sql;
EXEC sys.sp_executesql #sql, N'#firstMonth date', #firstMonth;
Another fiddle here: db<>fiddle
I have a script that generates a calendar table and fills in the public holidays and weekends for England and Wales. I would like to have the non working days as start and end periods, so taking the input
1980-04-01 00:00:00.000 Tuesday 0
1980-04-02 00:00:00.000 Wednesday 0
1980-04-03 00:00:00.000 Thursday 0
1980-04-04 00:00:00.000 Friday 1
1980-04-05 00:00:00.000 Saturday 1
1980-04-06 00:00:00.000 Sunday 1
1980-04-07 00:00:00.000 Monday 1
1980-04-08 00:00:00.000 Tuesday 0
1980-04-09 00:00:00.000 Wednesday 0
It would give the output
Start end
1980-04-03 1980-04-08
in other words, the day before the first non working day and the day after the last non working day of the group. To do this I would presumably use a min and max with a group and dateadd, but how do I go about obtaining a unique number to group each set of days by? This is using 2008 R2.
IF OBJECT_ID('tempdb.dbo.#calendar', 'U') IS NOT NULL
DROP TABLE #calendar
IF OBJECT_ID('tempdb.dbo.#easter', 'U') IS NOT NULL
DROP TABLE #easter;
-- CREATE TABLE CALENDAR
CREATE TABLE #calendar
(
[CalendarDate] DATETIME,
dayofwk varchar(20),
)
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
--INPUTS GO HERE
SET #StartDate = '01-01-1980'
SET #EndDate = '31-12-2018'
DECLARE #Startyear int
DECLARE #endyear int
set #startyear = YEAR(#StartDate)
set #endyear = YEAR(#EndDate)
WHILE #StartDate <= #EndDate
BEGIN
INSERT INTO #calendar
(
CalendarDate, dayofwk
)
SELECT
#StartDate, datename(dw,#startdate)
SET #StartDate = DATEADD(dd, 1, #StartDate)
END
---CREATE LIST OF EASTER MONDAY & GOOD FRIDAYS
create table #easter(
eastersunday_goodfriday date)
DECLARE #y int,
#EpactCalc INT,
#PaschalDaysCalc INT,
#NumOfDaysToSunday INT,
#EasterMonth INT,
#EasterDay INT
WHILE #Startyear <= #endyear
BEGIN
SET #y = #startyear
SET #EpactCalc = (24 + 19 * (#Y % 19)) % 30
SET #PaschalDaysCalc = #EpactCalc - (#EpactCalc / 28)
SET #NumOfDaysToSunday = #PaschalDaysCalc - (
(#Y + #Y / 4 + #PaschalDaysCalc - 13) % 7
)
SET #EasterMonth = 3 + (#NumOfDaysToSunday + 40) / 44
SET #EasterDay = #NumOfDaysToSunday + 28 - (
31 * (#EasterMonth / 4)
)
insert into #easter
SELECT dateadd(d,-2, CONVERT
( SMALLDATETIME,
RTRIM(#Y)
+ RIGHT('0'+RTRIM(#EasterMonth), 2)
+ RIGHT('0'+RTRIM(#EasterDay), 2) ))
insert into #easter
SELECT dateadd(d,1, CONVERT
( SMALLDATETIME,
RTRIM(#Y)
+ RIGHT('0'+RTRIM(#EasterMonth), 2)
+ RIGHT('0'+RTRIM(#EasterDay), 2) ))
SET #Startyear =#Startyear +1
end
select calendardate, dayofwk,
--NEW YEAR'S DAY
case
when day(calendardate) = 1 and month(calendardate) = 1 and dayofwk not in ('Saturday', 'sunday') then 1
when day(calendardate) between 2 and 3 and month(calendardate) = 1 and dayofwk = 'Monday' then 1
--GOOD FRIDAY, EASTER MONDAY
WHEN eastersunday_goodfriday IS NOT NULL THEN 1
--EARLY MAY BANK HOLIDAY
WHEN (datepart(weekday, CALENDARDATE) + ##DATEFIRST - 2) % 7 + 1 = 1
and (datepart(day, CALENDARDATE) - 1) / 7 + 1 = 1
AND MONTH(CALENDARDATE) = 5
THEN 1
--LATE MAY BANK HOLIDAY
WHEN (datepart(weekday, CALENDARDATE) + ##DATEFIRST - 2) % 7 + 1 = 1
and (datepart(day, CALENDARDATE) - 1) / 7 + 1 = 4
AND MONTH(CALENDARDATE) = 5
THEN 1
--LATE AUGUST BANK HOLIDAY
WHEN (datepart(weekday, CALENDARDATE) + ##DATEFIRST - 2) % 7 + 1 = 1
and (datepart(day, CALENDARDATE) - 1) / 7 + 1 = 4
AND MONTH(CALENDARDATE) = 8
THEN 1
--CHRISTMAS DAY
WHEN DAY(CalendarDate) = 25 AND MONTH(CALENDARDATE) = 12 AND dayofwk NOT IN ('SATURDAY', 'SUNDAY') THEN 1
--BOXING DAY
WHEN DAY(CalendarDate) = 26 AND MONTH(CALENDARDATE) = 12 AND dayofwk NOT IN ('SATURDAY', 'SUNDAY') THEN 1
WHEN DAY(CalendarDate) between 27 and 28 AND MONTH(CALENDARDATE) = 12 AND DAYOFWK IN ('MONDAY','TUESDAY') THEN 1
--SAT&SUN
WHEN DATENAME(DW,CALENDARDATE) IN ('SATURDAY','SUNDAY') THEN 1
ELSE 0
end as ISNONWORKINGDAY
from #calendar c
left join #easter e
on c.calendardate = e.eastersunday_goodfriday
ORDER BY C.CALENDARDATE
This seams like a typical island problem. Given what you posted you could do this:
DECLARE #table TABLE (dt DATE, isNonWorkDay BIT);
INSERT #table(dt,isNonWorkDay)
VALUES
('1980-04-01', 0),
('1980-04-02', 0),
('1980-04-03', 0),
('1980-04-04', 1),
('1980-04-05', 1),
('1980-04-06', 1),
('1980-04-07', 1),
('1980-04-08', 0),
('1980-04-09', 0);
SELECT DATEADD(DAY,-1,MIN(t.dt)), DATEADD(DAY,1,MAX(t.dt))
FROM #table t
WHERE t.isNonWorkDay = 1;
Returns: 1980-04-03, 1980-04-08
For multiple islands you could do this:
DECLARE #table TABLE (dt DATE, isNonWorkDay BIT);
INSERT #table(dt,isNonWorkDay)
VALUES
('1980-04-01', 0),
('1980-04-02', 0),
('1980-04-03', 0),
('1980-04-04', 1),
('1980-04-05', 1),
('1980-04-06', 1),
('1980-04-07', 1),
('1980-04-08', 0),
('1980-04-09', 0),
('1980-04-10', 0),
('1980-04-11', 1),
('1980-04-12', 1),
('1980-04-13', 1),
('1980-04-14', 0),
('1980-04-15', 0);
WITH x AS
(
SELECT *, rn = ROW_NUMBER() OVER (ORDER BY t.dt)
FROM #table t
),
xx AS
(
SELECT *, grouper = x.rn - ROW_NUMBER() OVER (ORDER BY x.dt)
FROM x
WHERE x.isNonWorkDay = 1
)
SELECT dtStart = DATEADD(DAY,-1,MIN(xx.dt)), dtend = DATEADD(DAY,1,MAX(xx.dt))
FROM xx
GROUP BY xx.grouper;
Returns:
dtStart dtend
---------- ----------
1980-04-03 1980-04-08
1980-04-10 1980-04-14
You can use the difference of a consecutive numbering of all days and a consecutive numbering of all non-workingdays to use for the grouping.
For a consecutive group of non-workingdays, these differences will all have the same value, and because of the gaps (workingsdays) between the groups, the next group will have a different (higher) value.
For numbering all days, you can use DATEDIFF, for the numbering of non-workingdays you can use ROW_NUMBER. The query could look like this:
WITH
numbering (calendardate, grp_num) AS (
SELECT
calendardate,
DATEDIFF(day, 0, calendardate) - ROW_NUMBER() OVER (ORDER BY calendardate)
FROM YourTable WHERE ISNONWORKINGDAY = 1
)
SELECT
DATEADD(day, -1, MIN(calendardate)) AS StartDate,
DATEADD(day, +1, MAX(calendardate)) AS EndDate
FROM numbering GROUP BY grp_num;
I'm building hourly report(last 8 hour production count) from SQL Table CONFIRMATION via SQL Query. Query absolutely runs fine and gives proper results as follow:
SELECT
(DATENAME(hour, C.DT_CONFIRMED) + ' - ' + DATENAME(hour, DATEADD(hour, 1, C.DT_CONFIRMED))) as PERIOD,
SUM(C.QT_CONFIRMED) as QT_CONFIRMED
FROM
CONFIRMATION C
WHERE C.DT_CONFIRMED >= DATEADD(hh, -8 , '2015-12-03T11:00:00')
GROUP BY (DATENAME(hour, C.DT_CONFIRMED) + ' - ' +
DATENAME(hour, DATEADD(hour, 1, C.DT_CONFIRMED)))
ORDER BY PERIOD
I get following result:
Period QT_CONFIRMED
8 - 9 4
10 - 11 8
But instead of that, i want result in following forma:
Period QT_CONFIRMED
2 - 3 0
3 - 4 0
4 - 5 0
5 - 6 0
7 - 8 0
8 - 9 4
9- 10 0
10 - 11 8
Basically for all those hours where QT_CONFIRMED is zero, I want to show that in the report.
How can I achieve that?
CONFIRMATION Table looks like following:
DT_CONFIRMED QT_CONFIRMED ID_CONFIRMATION
2015-12-03T10:40:43 5 1
2015-12-03T10:48:33 3 2
2015-12-03T11:03:03 12 3
Thanks
The below should work.
WITH Periods AS (
SELECT 8 AS num, (DATENAME(hour, DATEADD(hour, -9, getDate())) + ' - ' + DATENAME(hour, DATEADD(hour, -8, getDate()))) as PERIOD
UNION ALL
SELECT num - 1, (DATENAME(hour, DATEADD(hour, -num, getDate())) + ' - ' + DATENAME(hour, DATEADD(hour, -num + 1, getDate()))) as PERIOD
FROM Periods WHERE num> 0
) ,
Confrim as (SELECT
(DATENAME(hour, C.DT_CONFIRMED) + ' - ' + DATENAME(hour, DATEADD(hour, 1, C.DT_CONFIRMED))) as PERIOD,
SUM(C.QT_CONFIRMED) as QT_CONFIRMED
FROM
CONFIRMATION C
WHERE C.DT_CONFIRMED >= DATEADD(hh, -8 , '2015-12-03T11:00:00')
GROUP BY (DATENAME(hour, C.DT_CONFIRMED) + ' - ' +
DATENAME(hour, DATEADD(hour, 1, C.DT_CONFIRMED))) )
select P.PERIOD, isnull(C.QT_CONFIRMED, 0) from Periods P
left join Confrim C
on P.PERIOD = C.PERIOD
ORDER BY PERIOD
It simply generates periods and later it left joins periods to your query.
You can find example here:
SQL Fiddle sample
Additionally you can change order by to ORDER BY num desc. After that your periods will be sorted correctly.
You could use a recursive CTE:
https://technet.microsoft.com/en-us/library/ms186243(v=sql.105).aspx
https://dba.stackexchange.com/questions/14661/do-you-know-an-easy-way-to-generate-one-record-for-each-hour-of-the-past-12-hour
We are running reports for a seasonal business, with expected lulls during the summer months. For some metrics, we'd essentially like to pretend that those months don't even exist.
Thus consider the default behavior of:
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-06-01') -- answer = 1
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-07-01') -- 2
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-08-01') -- 3
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-09-01') -- 4
We want to ignore June and July, so we would like those answers to look like this:
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-06-01') -- answer = 1
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-07-01') -- 1
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-08-01') -- 1
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-09-01') -- 2
What is the easiest way to accomplish this? I'd like a pure SQL solution, rather than something using TSQL, but writing a custom function such as NOSUMMER_DATEDIFF could also work.
Also, keep in mind the reports will span multiple years, so the solution should be able to handle that.
If you are only interested month differences, then I would suggest a trick here. Count the number of months since some date 0, but ignore the summer months. For example:
'2015-05-01' --> 2015 * 10 + 5 = 20155
'2015-06-01' --> 2015 * 10 + 6 = 20156
'2015-07-01' --> 2015 * 10 + 6 = 20156
'2015-08-01' --> 2015 * 10 + 6 = 20156
'2015-09-01' --> 2015 * 10 + 7 = 20157
This is a fairly easy calculation:
select (case when month(date2) <= 6 then year(date2) * 10 + month(date2)
when month(date2) in (7, 8) then year(date2) * 10 + 6
else year(date2) * 10 + (month(date2) - 2)
end)
For the difference:
select ((case when month(date2) <= 6 then year(date2) * 10 + month(date2)
when month(date2) in (7, 8) then year(date2) * 10 + 6
else year(date2) * 10 + (month(date2) - 2)
end) -
(case when month(date1) <= 6 then year(date1) * 10 + month(date1)
when month(date1) in (7, 8) then year(date1) * 10 + 6
else year(date1) * 10 + (month(date1) - 2)
end)
)
To able to achieve that, you have to "split" dates ranges to an "array" of dates for every single range of dates. CTE might be helpful in this case.
See:
--your table which holds dates ranges
DECLARE #dates TABLE(id INT IDENTITY(1,1), dFrom DATE, dTo DATE)
INSERT INTO #dates (dFrom, dTo)
VALUES('2015-05-01', '2015-06-01'),
('2015-05-01', '2015-07-01'),
('2015-05-01', '2015-08-01'),
('2015-05-01', '2015-09-01')
--summer month table
DECLARE #summermonths TABLE(summMonth INT)
INSERT INTO #summermonths(summMonth)
VALUES(6), (7)
--here Common Table Expressions is in action to "split" dates ranges to an array of dates for every single date range
;WITH CTE AS
(
SELECT id, DATEADD(MM, 0, dFrom) AS ndFrom, dTo, CASE WHEN MONTH(DATEADD(MM, 0, dFrom)) = 6 OR MONTH(DATEADD(MM, 0, dFrom)) = 7 THEN 0 ELSE 1 END AS COfMonth
FROM #dates
WHERE DATEADD(MM, 1, dFrom)<=dTo
UNION ALL
SELECT id, DATEADD(MM, 1, ndFrom) AS ndFrom, dTo, CASE WHEN MONTH(DATEADD(MM, 1, ndFrom)) = 6 OR MONTH(DATEADD(MM, 1, ndFrom)) = 7 THEN 0 ELSE 1 END AS COfMonth
FROM CTE
WHERE DATEADD(MM, 1, ndFrom)<=dTo
)
SELECT t1.id, t2.dFrom, t2.dTo, SUM(t1.COfMonth) AS MyDateDiff
FROM CTE AS t1 INNER JOIN #dates AS t2 ON t1.id = t2.id
GROUP BY t1.id, t2.dFrom , t2.dTo
Result:
id dFrom dTo MyDateDiff
1 2015-05-01 2015-06-01 1
2 2015-05-01 2015-07-01 1
3 2015-05-01 2015-08-01 2
4 2015-05-01 2015-09-01 3 --not 2, because of 5, 8, 9
Got it?
Note: a solution might be differ in case of dFrom and dTo is not the first date of month.