How to find missing dates by ID - sql-server

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;

Related

SSRS Report/SQL Server: Display distinct column values even if there is no data on particular dates

I want to solve this problem for SSRS reports. I want to display all dates between #startdate and #enddate. I have accomplished this part. Next,I want to display distinct column values (for column named "name") for dates for which data doesn't exist. (i.e. aa,bb for all dates that I am generating)
What I am getting :
Date
name
value
01/01/2021 00:00:00
no data
no data
02/01/2021 00:00:00
no data
no data
03/01/2021 00:00:00
aa
23
03/01/2021 00:01:00
bb
32
04/01/2021 00:00:00
aa
54
04/01/2021 00:01:00
bb
90
What I want :
Date
name
value
01/01/2021 00:00:00
aa
no data
01/01/2021 00:00:00
bb
no data
02/01/2021 00:00:00
aa
no data
02/01/2021 00:00:00
bb
no data
03/01/2021 00:00:00
aa
23
03/01/2021 00:01:00
bb
32
04/01/2021 00:00:00
aa
54
04/01/2021 00:01:00
bb
90
Here is my attempt:
DECLARE #STARTDATE DateTime
DECLARE #ENDDATE DateTime
SET #STARTDATE = '2021-01-01'
SET #ENDDATE = '2021-01-04'
;with dates ([Date]) as (
Select convert(date,#STARTDate) as [Date]
union all
Select dateadd(day, 1, [Date])
from dates
where [Date] <= #ENDDATE
)
select
isnull(n.Created_Date,d.date) Date,
isnull(n.[NAME],'No Data') [NAME],
isnull(n.[VALUE],'No Data') [VALUE]
from dates d
left join Table_A n on convert(varchar, n.Created_Date, 101) = d.Date
where
([Created_Date] between (#StartDate) and (#EndDate) or
[Date] between (#StartDate-1) and (#EndDate) )
option (maxrecursion 32767)
any help will be much appreciated.
I call this pattern the "cross-join, left-join". You use a cross join to create the matrix of all possible values, and then a left join to fill in the available values.
So you'll need a table of all the possible Names to cross-join. eg
DECLARE #STARTDATE DateTime
DECLARE #ENDDATE DateTime
SET #STARTDATE = '2021-01-01'
SET #ENDDATE = '2021-01-04'
;with dates ([Date]) as (
Select convert(date,#STARTDate) as [Date]
union all
Select dateadd(day, 1, [Date])
from dates
where [Date] <= #ENDDATE
), names as
(
select distinct Name
from Table_A
)
select
isnull(n.Created_Date,d.date) Date,
names.Name [NAME],
isnull(n.[VALUE],'No Data') [VALUE]
from dates
cross join names
left join Table_A n
on n.Created_Date = dates.Date
and n.Name = names.Name
where dates.[Date] between dateadd(day,-1,#StartDate) and #EndDate
option (maxrecursion 32767)

Determine Days Not Covered Based on Fields Containing "From" and "To" Dates

Server: Microsoft SQL Server
SQLFiddle: http://www.sqlfiddle.com/#!18/cdfa3/1/0
If I have rows containing a "start" date and an "end date", how can write a SQL query that will list the days that are not contained between those dates.
Example (see SQLFiddle link above for a playable demo):
startdate enddate
2019-06-06 00:00:00.000 2019-06-08 00:00:00.000
2019-06-10 00:00:00.000 2019-06-11 00:00:00.000
2019-06-12 00:00:00.000 2019-06-13 00:00:00.000
We have a coverage gap on June 9th, because we have coverage from June 6th-June 8th, then on June 10th-June 13th.
How is it possible to identify the date of June 9th as having no coverage based on rows that have date ranges?
You could use generated calendar table and LEFT JOIN:
DECLARE #min DATE, #max DATE;
SELECT #min = MIN(workingdatestart), #max = MAX(workingdateend) FROM workingdates;
WITH cte AS (
SELECT DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY 1/0), #min) AS d
FROM sys.objects s, sys.objects s2
)
SELECT c.d AS gap
FROM cte c
LEFT JOIN workingdates w ON c.d BETWEEN w.workingdatestart and w.workingdateend
WHERE c.d < #max AND w.workingDateId IS NULL;
db<>fiddle demo
#Lukasz Szozda stole my thunder. My answer is similar but does not use variables (I'm not suggesting that's good or bad.. just calling it out).
You can create a calendar table function (see example below) then perform a LEFT ANTI SEMI JOIN against your working days table. The benefit to this solution is the calendar table generates 0 IO.
Solution:
WITH r(L,H) AS
(
SELECT CAST(MIN(w.workingdatestart) AS DATE), CAST(MAX(w.workingdateend) AS DATE)
FROM dbo.workingdates AS w
),
cal AS
(
SELECT c.Dt
FROM r
CROSS APPLY dbo.calendar(r.L,r.H) AS c
)
SELECT c.Dt
FROM cal AS c
EXCEPT
SELECT c.Dt
FROM cal AS c
JOIN dbo.workingdates AS w
ON c.Dt BETWEEN w.workingdatestart AND w.workingdateend;
.. and the function:
CREATE FUNCTION dbo.calendar(#startdate DATE, #enddate DATE)
RETURNS TABLE WITH SCHEMABINDING AS RETURN
WITH E1(N) AS (SELECT 1 FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS x(x)),
iTally(N) AS
(
SELECT 0 UNION ALL
SELECT TOP (DATEDIFF(DAY,#startDate,#endDate)) ROW_NUMBER() OVER (ORDER BY (SELECT 1))
FROM E1 a, E1 b, E1 c
)
SELECT sortKey = i.N, Dt = DATEADD(DAY, i.N, #startDate)
FROM iTally AS i;
To achieve this you need a table that contains all dates between your minimal and maximal dates. Then just filter rows that don't exist in workingdates using LEFT JOIN.
declare #minDate date = (select min([workingdatestart]) from [workingdates])
declare #maxDate date = (select max([workingdateend]) from [workingdates])
declare #Date date = #minDate
create table #rangeOfDates (dat date)
while #Date <= #maxDate
begin
insert into #rangeOfDates values (#Date)
set #Date = dateadd(day , 1, #Date)
end
select r.dat
from #rangeOfDates as r
left join workingdates as w
on r.dat between w.workingdatestart and w.workingdateend
where w.workingdateID is null
Result:
dat
2019-06-09

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.

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

Select Statement to show missing records (Easy Question)

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

Resources