How do you group by any time based interval? - sql-server

In SQL SERVER How do you group by any time based interval?
To save someone time I have come up with this solution, For me it works very well. You can generate any time base then group by any interval. Great for doing time weighted averages. If someone has a better way of doing this I would love to hear from you.
Hours
declare #startdate datetime2
declare #enddate datetime2
declare #interval int
set #startdate = '2017-01-01 00:00:00'
set #enddate = '2017-01-31 00:00:00'
set #interval = 4 --Group by Every 4 hours
;with
ALL_INTERVALS
AS (
SELECT TOP (DATEDIFF(HOUR,#startdate,#enddate))
TIMES = DATEADD(HOUR,CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[object_id])),#startdate),
1 AS VALUE
FROM sys.all_objects AS s1
CROSS JOIN
sys.all_objects AS s2
)
select DATEADD(HOUR,((DATEDIFF(HOUR, #startdate,TIMES)/#interval)*#interval),#startdate) AS TIMES,SUM(VALUE) AS TESTDATA
from ALL_INTERVALS
group by DATEADD(HOUR,((DATEDIFF(HOUR, #startdate,TIMES)/#interval)*#interval),#startdate)
order by DATEADD(HOUR,((DATEDIFF(HOUR, #startdate,TIMES)/#interval)*#interval),#startdate)
Minutes
Note.
you can set your interval to 60 to achieve hours, 1440 to achieve days....
declare #startdate datetime2
declare #enddate datetime2
declare #interval int
set #startdate = '2017-01-01 00:00:00'
set #enddate = '2017-01-31 00:00:00'
set #interval = 7
;with
ALL_INTERVALS
AS (
SELECT TOP (DATEDIFF(MINUTE,#startdate,#enddate))
TIMES = DATEADD(MINUTE,CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[object_id])),#startdate),
1 AS VALUE
FROM sys.all_objects AS s1
CROSS JOIN
sys.all_objects AS s2
)
select DATEADD(MINUTE,((DATEDIFF(MINUTE, #startdate,TIMES)/#interval)*#interval),#startdate) AS TIMES,SUM(VALUE) AS TESTDATA
from ALL_INTERVALS
group by DATEADD(MINUTE,((DATEDIFF(MINUTE, #startdate,TIMES)/#interval)*#interval),#startdate)
order by DATEADD(MINUTE,((DATEDIFF(MINUTE, #startdate,TIMES)/#interval)*#interval),#startdate)

I think you are over complicating thins.
You can use GROUP BY (DATEDIFF(MINUTE, '2017-01-01', TheDateTime) / 30 for grouping by every 30 minutes. Of course, the date I've chosen is a just a random date. You can choose, if you want, the first (or last) date in your sample data.
And you can also use this technique to get every interval of any time part - just change the keyword MINUTE to any date part you want to use, and the intreval 30 to any interval you want.
Consider the following sample data:
;WITH CTE AS
(
SELECT CAST('2017-01-01T00:00:00' as datetime) As TheDateTime, 0 as rn
UNION ALL
SELECT DATEADD(MINUTE, 1, TheDateTime), rn + 1
FROM CTE
WHERE rn < 60
)
SELECT TheDateTime, rn INTO #T
FROM CTE
OPTION(MAXRECURSION 0)
#T now contains the following data:
TheDateTime rn
2017-01-01 00:00:00.000 0
2017-01-01 00:01:00.000 1
2017-01-01 00:02:00.000 2
2017-01-01 00:03:00.000 3
...
2017-01-01 00:59:00.000 59
2017-01-01 01:00:00.000 60
To get the maximum rn grouped by 30 minutes you just need this:
SELECT DATEDIFF(MINUTE, '2017-01-01', TheDateTime) / 30, MAX(rn)
FROM #T
GROUP BY DATEDIFF(MINUTE, '2017-01-01', TheDateTime) / 30
Results:
interval max_rn
0 29
1 59
2 60

Related

Add a row for every day between two dates with number of hours in SQL Server

Starting data:
Desired results something like this:
So it calculated the number of hours until the end of StartDateTime, if the EndDateTime is greater than end of day for StartDateTime. Then for every full day in between, it calculates 24 hours (this could stretch numerous days). And then when it gets to the EndDateTime - it calculates time from midnight (morning) to EndDateTime
I'm reading that I will probably need to use a recursive CTE, but I don't have any experience with recursions and am struggling.
this might get tricky, but I guess it can be solved using so called number table - i.e. table which has only one column populated with number sequence. In our case 0 based sequence.
The trick here is to get the number of days between start and end datetime. This value used in join between the data table and the numbers table will create the needed extra rows for each per day interval.
Of course we also have to setup properly start and end datetime of each day interval (CASE terms in the CTE)
Then we get for each per day interval number of minutes and divide by 60 to get proper decimal value.
Hope this helps.
Lets see the code:
-- input data
DECLARE #v_Dates TABLE
(
id varchar(20),
StartDateTime SMALLDATETIME,
EndDateTime SMALLDATETIME
)
INSERT INTO #v_Dates (id, StartDateTime, EndDateTime)
VALUES ('example 1', '02-17-2019 0:45', '02-19-19 12:30'),
('example 2', '02-21-2019 18:00', '02-22-19 12:15'),
('example 3', '02-22-2019 20:15', '02-22-19 20:30');
-- so called Number table which holds numbers 0 - 9999 in this case
DECLARE #v_Numbers TABLE
(
Number INT
);
-- populating the number table
INSERT INTO #v_Numbers
SELECT TOP 10000 ROW_NUMBER() OVER(ORDER by t1.number) - 1 as Number
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
-- we parse the dates into the per day intervals
;WITH IntervalsParsed(id, StartDateTime, EndDateTime, Number, IntervalStartDateTime, IntervalEndDateTime) AS
(
SELECT id
,StartDateTime
,EndDateTime
,Number
, InervalStartDateTime = CASE
WHEN D.StartDateTime > DATEADD(day, DATEDIFF(day, 0, D.StartDateTime), N.Number) THEN D.StartDateTime
ELSE DATEADD(day, DATEDIFF(day, 0, D.StartDateTime), N.Number)
END
, IntervalEndDateTime = CASE
WHEN D.EndDateTime < DATEADD(day, DATEDIFF(day, 0, D.StartDateTime), N.Number + 1) THEN D.EndDateTime
ELSE DATEADD(day, DATEDIFF(day, 0, D.StartDateTime), N.Number + 1)
END
FROM #v_Dates D
--this join basically creates the needed number of rows
INNER JOIN #v_Numbers N ON DATEDIFF(day, D.StartDateTime, D.EndDateTime) + 1 > N.Number
)
-- final select
SELECT id
, StartDateTime
, EndDateTime
, IntervalStartDateTime
, IntervalEndDateTime
, Number
, DecimalValue = CAST( DATEDIFF(minute, IntervalStartDateTime, IntervalEndDateTime) AS DECIMAL)/60
FROM IntervalsParsed
ORDER BY id, Number
Just another option is an ad-hoc tally table in concert with a CROSS APPLY
Example
Select A.[column1]
,A.[StartDateTime]
,A.[EndDateTime]
,Hours = sum(1) / 60.0
From #YourTable A
Cross Apply (
Select Top (DateDiff(MINUTE,[StartDateTime],[EndDateTime])+1)
D=DateAdd(MINUTE,-1+Row_Number() Over (Order By (Select Null)),[StartDateTime])
From master..spt_values n1,master..spt_values n2
) B
Group By [column1],[StartDateTime],[EndDateTime],cast(D as Date)
Returns
This may be little complicated, but here is one way to use recursive cte to get the output. You can add the start date with one day as long as it is less than end date of your column. Also declared a Static value to make sure we can get difference of 24 hours.
--Create a table
Select 'example1' exm, '2019-02-17 00:45:00' startdate, '2019-02-19 12:30:00' Enddate into #temp union all
Select 'example2' exm, '2019-02-21 18:00:00' startdate, '2019-02-22 12:15:00' Enddate union all
Select 'example3' exm, '2019-02-22 20:15:00' startdate, '2019-02-22 20:30:00' Enddate
Declare #datevalue time = '23:59:59'
;with cte as (select exm, startdate, enddate, case when datediff(day, startdate, enddate) = 0 then datediff(SECOND, startdate, enddate)
when datediff(day, startdate, enddate)>0 then
datediff(SECOND, cast(startdate as time), #datevalue)
end as Hoursn, cast(dateadd(day, 1,cast(startdate as date)) as smalldatetime) valueforhours from #temp
union all
select exm, startdate, enddate, case when datediff(day, valueforhours, enddate) = 0 then datediff(SECOND, valueforhours, enddate)
when datediff(day, valueforhours, enddate)>0 then datediff(SECOND, cast(valueforhours as time), #datevalue) end as Hoursn, case when datediff(day,valueforhours, enddate) > 0 then dateadd(day,1,valueforhours) end as valueforhours
from cte
where
valueforhours <= cast(enddate as date)
)
select exm, startdate, Enddate, round(Hoursn*1.0/3600,2) as [hours] from cte
order by exm
Output:
exm startdate Enddate hours
example1 2019-02-17 00:45:00 2019-02-19 12:30:00 23.250000
example1 2019-02-17 00:45:00 2019-02-19 12:30:00 24.000000
example1 2019-02-17 00:45:00 2019-02-19 12:30:00 12.500000
example2 2019-02-21 18:00:00 2019-02-22 12:15:00 6.000000
example2 2019-02-21 18:00:00 2019-02-22 12:15:00 12.250000
example3 2019-02-22 20:15:00 2019-02-22 20:30:00 0.250000

SQL - Populate temp table with date intervals with minimum impact on performance

Inside a stored procedure, I need to populate my temp table with 7-day date intervals, between some #StartDate and #EndDate, like so:
CREATE TABLE #DateIntervals (
PeriodStartDate date,
PeriodEndDate date
)
DECLARE #StartDate datetime = '1/1/2017';
DECLARE #EndDate datetime = '1/1/2018';
WHILE #StartDate <= #EndDate
BEGIN
INSERT INTO #DateIntervals (PeriodStartDate, PeriodEndDate)
SELECT #StartDate, DATEADD(day, 6, #StartDate)
SET #StartDate = DATEADD(day, 7, #StartDate)
END
It works fine, but it takes me 1483 milliseconds to execute. If I then join this table with more data it will take even more time to execute. I need to somehow reduce this time to improve overall performance of the SP.
Any ideas how to do that?
You can do this with a single statement as below
WITH
E1(N) AS
(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
) -- 1*10^1 or 10 rows
, E2(N) AS (SELECT 1 FROM E1 a, E1 b) -- 1*10^2 or 100 rows
, Nums(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY ##SPID) FROM E2 a, E2 b) -- 1*10^4 or 10,000 rows
INSERT INTO #DateIntervals
(PeriodStartDate,
PeriodEndDate)
SELECT DATEADD(DAY, N * 7 - 7, #StartDate),
DATEADD(DAY, N * 7 - 1, #StartDate)
FROM Nums
WHERE N <= CEILING(DATEDIFF(DAY, #StartDate, #EndDate) / 7.0);
This should be faster than 53 individual statements/transactions but 1.5 seconds sounds astonishingly slow for that against a temp table.

Getting 30 minute slots between time

I have a Start Datetime and a End Datetime.
Eg: 10:00 am - 12:00 pm
I have to create 4 slots between the, each ranging for 30 minutes.
Eg:
10:00 am-10:30 am
10:30 am-11:00 am
11:00 am-11:30 am
11:30 am-12:00 pm
This 30 minutes can vary and is not a constant. I have tried a few things but they don't seem to work. Can someone please help. Thank you.
I have tried this but i only get the slot difference not the slotfrom-slotto
SELECT
from_dt,to_dt,
DATEDIFF(mi,DATEADD(dd,DATEDIFF(dd,0,from_dt ),0),to_dt )/60 as SlotNumber
FROM
d
group by from_dt,to_dt, DATEDIFF(mi,DATEADD(dd,DATEDIFF(dd,0,from_dt ),0),to_dt )/60
thanks #dwain.c to get Reference
--Declare table
DECLARE #t TABLE
(StartTime TIME, EndTime TIME)
INSERT INTO #t
SELECT '10:00', '12:00'
-- Make CTE
;WITH CTE (n) AS (
SELECT TOP (SELECT DATEDIFF(MINUTE,StartTime,EndTime)/30
FROM #t) 30*(ROW_NUMBER() OVER (ORDER BY (SELECT NULL))-1)
FROM sys.all_columns
)
-- QUERY
SELECT
TSStart=DATEADD(minute, n, StartTime)
,TSEnd=DATEADD(minute, n + 30, StartTime)
,Timeslot=CONVERT(VARCHAR(100), DATEADD(minute, n, StartTime), 0) + ' - ' +
CONVERT(VARCHAR(100), DATEADD(minute, n + 30, StartTime), 0)
FROM #t
CROSS APPLY (
SELECT n
FROM CTE
WHERE n BETWEEN 0 AND DATEDIFF(minute, StartTime, DATEADD(minute, -30, EndTime))) a
ORDER BY TSStart

SQL stament to determine the number of Mondays in a month

Is there a way to get the number of Mondays in a given month (and year) without using T-SQL?
Thanks
I'm not sure what you mean by saying:
Is there a way to get the number of Mondays in a given month (and
year) without using T-SQL?
If you are hoping for a universal code fragment that will do this across all databases, forget it. I doubt that you'll even be able to get a version to run on two different databases. Dates and things like weekdays tend to be be implemented differently across database vendors.
Here is the TSQL way (Monday Month Count):
;with AllDates AS
(SELECT CONVERT(datetime,CONVERT(varchar(6),GETDATE(),112)+'01') AS DateOf
UNION ALL
SELECT DateOf+1
FROM AllDates
WHERE
MONTH(DateOf+1)=MONTH(CONVERT(datetime,CONVERT(varchar(6),GETDATE(),112)+'01'))
)
SELECT COUNT(DateOf) AS MondayCountMonth
FROM AllDates
WHERE DATENAME(weekday,DateOf)='Monday'
Here is the TSQL way (Monday Year Count):
;with AllDates AS
(SELECT CONVERT(datetime,CONVERT(varchar(4),GETDATE(),112)+'0101') AS DateOf
UNION ALL
SELECT DateOf+1
FROM AllDates
WHERE
YEAR(DateOf+1)=Year(CONVERT(datetime,CONVERT(varchar(4),GETDATE(),112)+'0101'))
)
SELECT COUNT(DateOf) AS MondayCountYear
FROM AllDates
WHERE DATENAME(weekday,DateOf)='Monday'
OPTION (MAXRECURSION 367)
EDIT based on OP comment, here is a version which finds the monthly and yearly Monday counts as sub-queries within another query:
DECLARE #YourTable table (Col1 int, Col2 varchar(5))
INSERT #YourTable VALUES (1,'aaa')
INSERT #YourTable VALUES (2,'bbb')
INSERT #YourTable VALUES (3,'ccc')
;with MonthMondayCount AS
(SELECT CONVERT(datetime,CONVERT(varchar(6),GETDATE(),112)+'01') AS DateOf
UNION ALL
SELECT DateOf+1
FROM MonthMondayCount
WHERE
MONTH(DateOf+1)=MONTH(CONVERT(datetime,CONVERT(varchar(6),GETDATE(),112)+'01'))
)
,YearMondayCount AS
(SELECT CONVERT(datetime,CONVERT(varchar(4),GETDATE(),112)+'0101') AS DateOf
UNION ALL
SELECT DateOf+1
FROM YearMondayCount
WHERE
YEAR(DateOf+1)=Year(CONVERT(datetime,CONVERT(varchar(4),GETDATE(),112)+'0101'))
)
SELECT
y.*
,(SELECT COUNT(DateOf) AS MondayCountMonth FROM MonthMondayCount WHERE DATENAME(weekday,DateOf)='Monday') AS MondayCountMonth
,(SELECT COUNT(DateOf) AS MondayCountYear FROM YearMondayCount WHERE DATENAME(weekday,DateOf)='Monday') AS MondayCountYear
FROM #YourTable y
OPTION (MAXRECURSION 367)
OUTPUT:
Col1 Col2 MondayCountMonth MondayCountYear
----------- ----- ---------------- ---------------
1 aaa 5 52
2 bbb 5 52
3 ccc 5 52
(3 row(s) affected)
Try this : )
DECLARE #tmpDate as date
set #tmpDate = getdate(); --you can add any date
DECLARE #Startdate as varchar( 8)
DECLARE #Enddate as varchar( 8)
SELECT #Startdate = replace(convert(varchar,cast(DATEADD(month, DATEDIFF(month, 0, #tmpDate), 0) as date) , 111), '/', '');
SELECT #Enddate = replace(convert(varchar, cast(DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,#tmpDate)+1,0)) as date), 111), '/', '');
with [dates] as (
select convert(date , #Startdate ) as [date] --start
union all
select dateadd(day , 1 , [date])
from [dates]
where [date] < #Enddate )
Select X.WeekDayNumber, count(X.WeekDayNumber) as NumberOfDays
from (
SELECT [date] , DATEPART(weekday,[date] ) as WeekDayNumber
from [dates]
WHERE [date] IS NOT NULL)X
Group by X.WeekDayNumber
option (maxrecursion 0 )
( DATEADD( DAY, -1, DATEADD( MONTH, 1 , month + '-01' ) -
DATEADD( DAY, 7 - DATEPART( WEEKDAY, month + '-01' ), month + '-01' )
) DIV 7 + 1
I don't know how much ANSI SQL compatible this is but it works in MySql (not after the changes, it should work in SQL Server now).
month should be in 'yyyy-mm' format

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

Resources