Select Statement to show missing records (Easy Question) - sql-server

I need some T-SQL that will show missing records.
Here is some sample data:
Emp 1
01/01/2010
02/01/2010
04/01/2010
06/01/2010
Emp 2
02/01/2010
04/01/2010
05/01/2010
etc...
I need to know
Emp 1 is missing
03/01/2010
05/01/2010
Emp 2 is missing
01/01/2010
03/01/2010
06/01/2010
The range to check will start with todays date and go back 6 months.
In this example, lets say today's date is 06/12/2010 so the range is going to be 01/01/2010 thru 06/01/2010.
The day is always going to be the 1st in the data.
Thanks a bunch. :)
Gerhard Weiss
Secretary of Great Lakes Area .NET Users Group
GANG Upcoming Meetings | GANG LinkedIn Group

Try This:
DECLARE #Employees table (DateOf datetime, EmployeeID int)
INSERT #Employees VALUES ('01/01/2010',1)
INSERT #Employees VALUES ('02/01/2010',1)
INSERT #Employees VALUES ('04/01/2010',1)
INSERT #Employees VALUES ('06/01/2010',1)
INSERT #Employees VALUES ('02/01/2010',2)
INSERT #Employees VALUES ('04/01/2010',2)
INSERT #Employees VALUES ('05/01/2010',2)
--I was unsure of the data in the question
--this gives first day of each month for last six months
DECLARE #StartDate datetime
,#EndDate datetime
SELECT #StartDate=DATEADD(month,-6,DATEADD(month,DATEDIFF(month,0,GETDATE()),0) )
,#EndDate=GETDATE()
;with AllDates AS
(
SELECT #StartDate AS DateOf
UNION ALL
SELECT DateAdd(month,1,DateOf)
FROM AllDates
WHERE DateOf<#EndDate
)
SELECT
dt.DateOf,dt.EmployeeID
FROM (SELECT DISTINCT
a.DateOf,e.EmployeeID
FROM AllDates a
CROSS JOIN (SELECT DISTINCT EmployeeID FROM #Employees) e
) dt
LEFT OUTER JOIN #Employees ee ON dt.EmployeeID=ee.EmployeeID AND dt.DateOf=ee.DateOf
WHERE ee.EmployeeID IS NULL
ORDER BY dt.EmployeeID,dt.DateOf
OUTPUT:
DateOf EmployeeID
----------------------- -----------
2009-10-01 00:00:00.000 1
2009-11-01 00:00:00.000 1
2009-12-01 00:00:00.000 1
2010-03-01 00:00:00.000 1
2010-05-01 00:00:00.000 1
2009-10-01 00:00:00.000 2
2009-11-01 00:00:00.000 2
2009-12-01 00:00:00.000 2
2010-01-01 00:00:00.000 2
2010-03-01 00:00:00.000 2
(10 row(s) affected)
this will do every day for last six months, just incorporate this in the above if that is what you want:
DECLARE #StartDate datetime
,#EndDate datetime
SELECT #StartDate=DATEADD(month,-6,GETDATE())
,#EndDate=GETDATE()
;with AllDates AS
(
SELECT #StartDate AS DateOf
UNION ALL
SELECT DateOf+1
FROM AllDates
WHERE DateOf<#EndDate
)
SELECT * FROM AllDates
--OPTION (MAXRECURSION 500) --uncomment and increase if the date range needs more rows

fill a temp table with the date ranges and outer join the temp table to your Emp* table and only return records from your temp table that have null in the corresponding row of the Emp* table

If you're only going back a fixed # of months, you can precalc those "first of month" dates and left join to your employee data:
SELECT d.DT, CASE WHEN e.DT IS NULL THEN 1 ELSE 0 END AS IsMissing
FROM (
SELECT DATEADD(m, DATEDIFF(m, 0, CURRENT_TIMESTAMP), 0) AS DT
UNION
SELECT DATEADD(m, DATEDIFF(m, 0, CURRENT_TIMESTAMP) - 1, 0)
UNION
SELECT DATEADD(m, DATEDIFF(m, 0, CURRENT_TIMESTAMP) - 2, 0)
UNION
SELECT DATEADD(m, DATEDIFF(m, 0, CURRENT_TIMESTAMP) - 3, 0)
UNION
SELECT DATEADD(m, DATEDIFF(m, 0, CURRENT_TIMESTAMP) - 4, 0)
UNION
SELECT DATEADD(m, DATEDIFF(m, 0, CURRENT_TIMESTAMP) - 5, 0)
) AS d
LEFT JOIN EmployeeDates e ON d.DT = e.DT AND e.EmpID = 1

Related

want to show record of same date previous and next time slot for a given date

I have a table with multiple record against userid and datetime fields. A user have availability for same date everyhour.
UserId DateTime
1 2018-08-13 08:30:00 +05:30
1 2018-08-13 09:30:00 +05:30
1 2018-08-13 10:30:00 +05:30
1 2018-08-13 15:00:00 +05:30
1 2018-08-13 17:00:00 +05:30
1 2018-08-13 18:00:00 +05:30
Now If I search for date suppose 2018-08-13 11:30:00 +05:30
then I want in
Previous slot = 2018-08-13 **10:30:00** +05:30
and next slot = 2018-08-13 **15:00:00** +05:30
Update
Quick update I need smaller time for the same date not the previous day. Ex. For
2018-08-13 08:30:00 +05:30 it should show null as no small time available.
One more try with CTEs, edited to match the changed question:
DECLARE #SearchDate datetime = '2018-08-13 11:30:00',
#StartDate datetime,
#EndDate datetime,
#UserId int = 1;
SET #StartDate = DATETIMEFROMPARTS(YEAR(#SearchDate), MONTH(#SearchDate), DAY(#SearchDate), 0, 0, 0, 0);
SET #EndDate = DATEADD(day, 1, #StartDate);
WITH
ctePrevious AS
(
SELECT MAX([DateTime]) AS [DateTime]
FROM YourTable
WHERE UserId = #UserId AND [DateTime] BETWEEN #StartDate AND #SearchDate
),
cteNext AS
(
SELECT MIN([DateTime]) AS [DateTime]
FROM YourTable
WHERE UserId = #UserId AND [DateTime] BETWEEN #SearchDate AND #EndDate
)
SELECT ctePrevious.[DateTime] AS prevDate, cteNext.[DateTime] AS nextDate
FROM ctePrevious, cteNext
It should return NULL values if no entry is found for the given date.
You could add a rownumber and a self join. The following example will demonstrate how it works:
CREATE TABLE #T (ID INT, DateT DATETIME)
INSERT INTO #T VALUES (1, GETDATE() - .2)
INSERT INTO #T VALUES (1, GETDATE() - .1)
INSERT INTO #T VALUES (1, GETDATE() - .05)
INSERT INTO #T VALUES (1, GETDATE())
INSERT INTO #T VALUES (1, GETDATE() + .1);
WITH CTE AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS RowNumb
FROM #T AS T
)
SELECT C.ID, C.DateT AS StartDate, C2.DateT AS EndDate
FROM CTE AS C
LEFT JOIN CTE AS C2 ON C.RowNumb = C2.RowNumb - 1
WHERE GETDATE() BETWEEN C.DateT AND C2.DateT -- your date input here
Make use of LEAD and LAG. This is Pseudo SQL, however:
SELECT DateColumn,
LAG(DateColumn) OVER (ORDER BY DateColumn) AS Previousslot,
LEAD(DateColumn) OVER (ORDER BY DateColumn) AS NextSlot
FROM YourTable;
This, unlike SQL_M's answer, means you don't need to do 2/3 scans of the table.
declare #wantedDate datetime = '2018-09-04 12:27:16.570'
select top 1 * from #t tP -- Top of each min and max time
join #T tn
on tp.ID = tn.ID
and tp.DateT <= #wantedDate -- all previous times
and tn.DateT >= #wantedDate -- all next times
order by tp.ID, tp.DateT desc, tn.DateT
Revert me, if query needs updates.

T-SQL: Create record for each day in datediff

I have the following table structure (myTable):
EmployeeID, StartDate, EndDate, Hours
---------------------------------------
1 1/1/2016 1/8/2016 20
2 1/4/2016 1/6/2016 10
3 1/2/2016 1/3/2016 13
I need to divide hours by datediff of start and end dates and display a record for each day like so:
1 1/1/2016 1/2/2016 2.85
1 1/2/2016 1/3/2016 2.85
...
1 1/7/2016 1/8/2016 2.85
2 1/4/2016 1/5/2016 5
2 1/5/2016 1/6/2016 5
3 1/2/2016 1/3/2016 13
Hours should be rounded to two decimal places. Assume that start and end dates are never the same.
How can I do this with T-SQL?
EDIT: I'm not a SQL guru, so I haven't tried much to do this as it didn't look like a simple select. I'm thinking I need to use 'partition by'? For the hours, 2.86 is fine too. Rounding up or down doesn't matter so long as it's consistent.
Also, just for clarification, I don't need 3 rows. I need 10 rows. I don't just need a simple
hours / datediff(day, startdate, enddate)
Unpivoting that daterange can be done by joining to list of numbers.
And master..spt_values can be used for that.
To divide the [hours] by the datediff, it's first casted to a float and then truncated via round to 2 decimals.
select t.EmployeeID,
dateadd(d, v.number, t.StartDate) as StartDate,
dateadd(d, v.number+1, t.StartDate) as EndDate,
t.DivHours as [Hours]
from (
select EmployeeID, StartDate, EndDate,
round(cast([Hours] as float)/datediff(d, StartDate, EndDate),2,1) as DivHours
from myTable
where EndDate > StartDate
) t
join master..spt_values v
on (v.type='P' and v.number >= 0 and v.number < datediff(d, t.StartDate, t.EndDate));
Gives:
EmployeeID StartDate EndDate Hours
1 2016-07-01 2016-07-02 2,85
1 2016-07-02 2016-07-03 2,85
1 2016-07-03 2016-07-04 2,85
1 2016-07-04 2016-07-05 2,85
1 2016-07-05 2016-07-06 2,85
1 2016-07-06 2016-07-07 2,85
1 2016-07-07 2016-07-08 2,85
2 2016-07-04 2016-07-05 5
2 2016-07-05 2016-07-06 5
3 2016-07-02 2016-07-03 13
However, that will only work as long the datediff is below 2047.
Because 2047 is the maximum number you get from that system table.
But that's still a daterange of more than 5 years though.
But if you have bigger ranges in that table.
Then you could just generate a table with lots more numbers.
This example puts 1000000 numbers into a table variable:
DECLARE #Numbers TABLE (num int primary key);
-- Who dares to claim that cross joins are always useless?
WITH d AS (select n from (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9))q(n))
insert into #Numbers (num)
select (d6.n*100000+d5.n*10000+d4.n*1000+d3.n*100+d2.n*10+d1.n) as num
from d d1, d d2, d d3, d d4, d d5, d d6;
select count(*) as total, min(num) as min_num, max(num) as max_num from #Numbers;
You could also use recursion for this.
But that method is a bit of pain if you want to add additional columns.
To add more columns from myTable you could left join myTable to R on the EmployeeID.
WITH R (EmployeeID, StartDate, EndDate, FinalDate, [Hours]) AS
(
SELECT EmployeeID, StartDate, dateadd(d, 1, StartDate),
EndDate as FinalDate,
round(cast([Hours] as float)/datediff(d, StartDate, EndDate),2,1)
from myTable
where StartDate < EndDate
UNION ALL
SELECT EmployeeID, dateadd(d, 1, StartDate), dateadd(d, 2, StartDate),
FinalDate, [Hours]
FROM R WHERE dateadd(d, 1, StartDate) < FinalDate
)
SELECT EmployeeID, StartDate, EndDate, [Hours]
FROM R
ORDER BY EmployeeID, StartDate, EndDate;
If the total sum of the split hours still needs to equal the original hours?
Then it becomes slighty more complicated.
declare #myTable TABLE (EmployeeID int, StartDate date, EndDate date, [Hours] int);
insert into #myTable values
(0,'2016-1-1','2016-1-4',10),
(1,'2016-1-1','2016-1-8',20);
WITH R (EmployeeID, StartDate, EndDate, FinalDate, [Hours], RemainingHours) AS
(
SELECT EmployeeID,
StartDate,
dateadd(d, 1, StartDate),
EndDate,
round(cast([Hours] as float)/datediff(d, StartDate, EndDate),2,1),
round(cast([Hours] as float),2,1)
from #myTable
where StartDate < EndDate
UNION ALL
SELECT EmployeeID,
dateadd(d, 1, StartDate),
dateadd(d, 1, EndDate),
FinalDate,
(case when dateadd(d, 1, EndDate) < FinalDate then [Hours] else (RemainingHours - [Hours]) end),
(RemainingHours - [Hours])
FROM R WHERE EndDate < FinalDate
)
SELECT EmployeeID, StartDate, EndDate, [Hours]
FROM R
ORDER BY EmployeeID, StartDate, EndDate;
You have to use a tally table for this:
;WITH Tally AS (
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS n
FROM (VALUES (0), (0), (0), (0), (0), (0)) t1(v)
CROSS JOIN (VALUES (0), (0), (0), (0), (0), (0)) t2(v)
)
SELECT t1.EmployeeID,
DATEADD(d, t3.n-1, t1.StartDate),
DATEADD(d, t3.n, t1.StartDate),
ROUND([Hours] * 1.0 / t2.days, 2)
FROM mytable AS t1
CROSS APPLY (SELECT DATEDIFF(d, t1.StartDate, t1.EndDate)) As t2(days)
JOIN Tally AS t3 ON t3.n <= t2.days
ORDER BY t1.EmployeeID, n, t1.StartDate
The above query uses a CTE in order to create a tally table with 36 rows. It can easily be extended to create more rows.

Count the number of times a date is contained between 2 date columns

I have a table that looks like this
ID start_dt end_dt
--------------------------
1 1951-12-05 1951-12-21
2 1951-12-19 1951-12-31
3 1957-12-05 1957-12-19
4 1995-12-06 1995-12-20
5 1996-06-24 1996-07-08
6 1997-05-12 1997-05-26
7 1997-10-07 1997-10-21
8 1997-12-25 1998-01-08
9 1998-01-19 1998-02-02
10 1998-08-05 1998-08-19
I'd like to know how many times each individual date is contained between start_dt and end_dt.
From my example, the result set should look something like this
date count
------------------
1951-12-05 1
1951-12-06 1
...
1951-12-19 2
1951-12-20 2
1951-12-21 2
...
1998-08-19 1
What would be the best way to do this?
EDIT: To clarify, I need each date that appears at least once in a date range (between start_dt and end_dt) to get a row in my result set and I want the number of ranges that this date fits in next to it
hope this helps
When you need to turn 2 values (a range) into a series of rows you can use a number table (see Aaron Bertrand's The SQL Server Numbers Table article if you aren't familiar with the idea).
I've used shorter and simpler data but you should get the idea.
declare #dates table (id int not null, start_dt date not null, end_dt date not null)
insert #dates values (1, '20160601', '20160603'),
(2, '20160603', '20160605'),
(3, '20160610', '20160612')
;with cte as (
select
row_number() over (order by so1.object_id) - 1 as n
from
sys.objects so1
cross join sys.objects so2
)
select
dateadd(d, c.n, d.start_dt) as [date],
count(*)
from
#dates d
join cte c on dateadd(d, c.n, d.start_dt) <= d.end_dt
group by
dateadd(d, c.n, d.start_dt)
order by
dateadd(d, c.n, d.start_dt)
If there are no more than a few days (< 80 or so, depending in your sys.objects table) between start_dt and end_dt, you can use this approach (inspired on Rhys').
DECLARE #dates TABLE (id int not null, start_dt date not null, end_dt date not null)
INSERT #dates VALUES
(1, '1951-12-05', '1951-12-21'),
(2, '1951-12-19', '1951-12-31'),
(3, '1957-12-05', '1957-12-19'),
(4, '1995-12-06', '1995-12-20'),
(5, '1996-06-24', '1996-07-08'),
(6, '1997-05-12', '1997-05-26'),
(7, '1997-10-07', '1997-10-21'),
(8, '1997-12-25', '1998-01-08'),
(9, '1998-01-19', '1998-02-02'),
(10, '1998-08-05', '1998-08-19');
WITH RawData AS (
SELECT
DATEADD(d, n.n, d.start_dt) AS [date]
FROM #dates d
INNER JOIN (
SELECT ROW_NUMBER() OVER (ORDER BY object_id) - 1 AS n FROM sys.objects
) n ON DATEADD(d, n.n, d.start_dt) <= d.end_dt
)
SELECT [date], COUNT(*) [count]
FROM RawData
GROUP BY [date]
ORDER BY [date]
I don't think this could take long even with 1000 date ranges. Perhaps you are using a table with more fields and even missing some index?
You could use a CTE
WITH CTE AS(SELECT start_dt AS dates FROM Table
UNION ALL
SELECT end_dt AS dates FROM Table)
SELECT CAST(dates as DATE) as Date, COUNT(dates) AS Count
FROM CTE c
GROUP BY c.dates
order by Count desc
Or perhaps you need something broader if your columns are of DATETIME data type. This way will GROUP BY the whole day:
WITH CTE AS(SELECT CAST(start_dt AS DATE) AS dates FROM Table
UNION ALL
SELECT CAST(end_dt AS DATE) AS dates FROM Table)
SELECT Dates as Date, COUNT(Dates) AS Count
FROM CTE c
GROUP BY c.dates
order by Count desc

How to find missing dates by ID

I need help modifying a script to find the missing RevenuePeriod by ID within a given time frame. I have found a similar script that finds the missing date for a given ID but I don't know how to modify the script where it will give the missing dates per ID.
create table #WorksheetHistory (WorksheetID [int] IDENTITY(1,1) ,ID varchar(6), RevenuePeriod datetime)
insert into #WorksheetHistory (ID,RevenuePeriod)
SELECT '000001','2015-06-01 00:00:00.00' Union All
SELECT '000001','2015-07-01 00:00:00.00' Union All
SELECT '000001','2015-11-01 00:00:00.00' Union All
SELECT '000001','2015-12-01 00:00:00.00' Union All
SELECT '000002','2015-06-01 00:00:00.00' Union All
SELECT '000002','2015-12-01 00:00:00.00'
DECLARE #EndDate datetime
DECLARE #StartDate datetime
SET #StartDate = '2015-06-01 00:00:00.00'
SET #EndDate = '2015-12-01 00:00:00.00'
;WITH Dates as
(
SELECT #StartDate AS dt
UNION ALL
SELECT DATEADD(month, 1, dt) as dt
FROM Dates
WHERE dt < (select dateadd(month,-1,#EndDate) enddate)
)
select Month(dt) as dtMonth, Year(dt) dtYear
from Dates d left outer join #WorksheetHistory w
on dateadd(month, datediff(month,0,d.dt),0) = dateadd(month, datediff(month,0,w.RevenuePeriod),0) Where RevenuePeriod is null
Drop Table #WorksheetHistory
The current output returns the following output. I realize in the script it doesnt have an ID returned, but even if I do, it would return null value as the script indicates return null dates. I don't know how to put the associated ID in it too.
dt
2015-08-01 00:00:00.000
2015-09-01 00:00:00.000
2015-10-01 00:00:00.000
My desired result would be return the Missing ID's with the respective missing dates.
ID dt
000001 2015-08-01 00:00:00.00
000001 2015-09-01 00:00:00.00
000001 2015-10-01 00:00:00.00
000002 2015-07-01 00:00:00.00
000002 2015-08-01 00:00:00.00
000002 2015-09-01 00:00:00.00
000002 2015-10-01 00:00:00.00
000002 2015-11-01 00:00:00.00
Use EXCEPT:
WITH Dates as
(
SELECT #StartDate AS dt
UNION ALL
SELECT DATEADD(month, 1, dt) as dt
FROM Dates
WHERE dt < DATEADD(m, -1, #EndDate)
)
-- all the possible combinations
SELECT w.ID, d.dt
FROM Dates d
CROSS JOIN (
SELECT ID
FROM #WorksheetHistory
GROUP BY ID
) w
EXCEPT
-- the combinations you actually have
SELECT w.ID, d.dt
FROM Dates d
JOIN #WorksheetHistory w
ON d.dt = w.RevenuePeriod;

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