TSQL for Repeating rows within table - sql-server

I have single table with below fields.
id name startdate enddate
1 u1 2013-01-15 00:00:00.000 2013-01-17 00:00:00.000
2 u2 2013-01-22 00:00:00.000 2013-01-23 00:00:00.000
3 u3 2013-01-23 00:00:00.000 2013-01-23 00:00:00.000
Now, I want multiple rows depends on start and end dates. So as per above rows.. It returns with three rows for first record.. date 15 to 17 which returns 3 rows (3 days).
I am bit confused for query. Is there any better way or any sample to achieve?
Thanks.

You could use a CTE to solve that:
DECLARE #Id int
SELECT #Id = 1
;
WITH Multiple AS
(
SELECT 1 Sequence, Id, Name, StartDate, EndDate
FROM ( VALUES
(1, 'u1', '2013-01-15', '2013-01-17'),
(2, 'u2', '2013-01-22', '2013-01-23'),
(3, 'u3', '2013-01-23', '2013-01-23')
) AS Sample(Id, Name, StartDate, EndDate)
WHERE Id = #Id
UNION ALL
SELECT Sequence + 1, Id, Name, StartDate, EndDate
FROM Multiple
WHERE Id = #Id AND DATEADD(d, Sequence, StartDate) <= EndDate
)
SELECT *
FROM Multiple

If you have a 'Dates' table with a 'Date' column in there, just join your table to the 'Dates' on 'Dates.Date BETWEEN startdate AND enddate'.

I am not pretty sure but below is what I have tried..
SELECT c1.*
FROM master..spt_values p
INNER JOIN tempQuery c1
ON RIGHT(CAST(c1.stdate AS DATE),2) <= (CASE WHEN p.number = 0 THEN 1 ELSE p.number END)
WHERE TYPE='p'
AND p.number BETWEEN RIGHT(CAST(c1.stdate AS DATE),2) AND RIGHT(CAST(c1.endate AS DATE),2)

Related

Create data ranges based on list of dates

I have a list of dates like this (no gaps, each calendar date):
DateKey
Valid
2021-01-01
1
2021-01-02
1
2021-01-03
1
2021-01-04
0
2021-01-05
0
2021-01-06
1
2021-01-07
1
I would like to convert them using T-SQL to date ranges considering valid dates only.
So the results would be:
ValidFrom
ValidTo
2021-01-01
2021-01-03
2021-01-06
2021-01-07
Grouping simply by Valid flag rtutns wrong results:
select min(dateKey),max(dateKey)
from #t
group by Valid
If I knew how to assign a unique value for each continuous segment of valid dates, that would solve my problem. Is there anyone that can help me with this?
Just another option using the window function sum() over()
Select ValidFrom = min(DateKey)
,ValidTo = max(DateKey)
From (
Select *
,Grp = sum(case when Valid=0 then 1 else 0 end) over (order by DateKey)
from YourTable
) A
Where Valid=1
Group By Grp
Returns
ValidFrom ValidTo
2021-01-01 2021-01-03
2021-01-06 2021-01-07
Something like the following may work for you:
DECLARE #Dates TABLE (Dt DATE, Valid BIT)
INSERT #Dates
VALUES('2021-01-01', 1),
('2021-01-02', 1),
('2021-01-03', 1),
('2021-01-04', 0),
('2021-01-05', 0),
('2021-01-06', 1),
('2021-01-07', 1)
SELECT MIN(dt.Dt) AS BeginRange,
MAX(dt.Dt) AS EndRange
FROM (
SELECT d.Dt,
DATEDIFF(D, ROW_NUMBER() OVER(ORDER BY d.Dt), d.Dt) AS DtRange
FROM #Dates d
WHERE Valid = 1
) AS dt
GROUP BY dt.DtRange;
I think I've just found the solution of my problem:
https://dba.stackexchange.com/questions/197972/convert-list-of-dates-in-a-date-range-in-sql-server
DECLARE #t TABLE (dt DATE);
INSERT INTO #t (dt)
VALUES ('20180202')
,('20180203')
,('20180204')
,('20180205')
,('20180209')
,('20180212')
,('20180213');
WITH c
AS (
SELECT dt
,dateadd(day, - 1 * dense_rank() OVER (orderby dt), dt) AS grp
FROM #t
)
SELECT min(dt) AS start_range
,max(dt) AS end_range
FROM c
GROUP BY grp;

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;

Complex SQL Query advise : Reservation allotment logic

I am trying to do a complex query (at least, it is complex for me) on SQL Server 2008 and so far I can come this far. Here is the code;
DECLARE #Hotels AS TABLE(
HotelID INT,
HotelName NVARCHAR(100)
);
DECLARE #HotelAllotments AS TABLE(
HotelID INT,
StartDate DATETIME,
EndDate DATETIME,
Allotment INT
);
DECLARE #Reservations AS TABLE(
ReservationID INT,
HotelID INT,
CheckIn DATETIME,
CheckOut DATETIME,
IsCanceled BIT
);
INSERT #Hotels VALUES(1,'Foo Hotel');
INSERT #Hotels VALUES(2,'Poo Hotel');
INSERT #HotelAllotments VALUES(1,'2011-01-01', '2011-02-01', 10);
INSERT #HotelAllotments VALUES(1,'2011-02-02', '2011-02-18', 7);
INSERT #HotelAllotments VALUES(1,'2011-02-19', '2011-05-18', 19);
INSERT #HotelAllotments VALUES(1,'2011-05-19', '2011-10-18', 30);
INSERT #HotelAllotments VALUES(2,'2011-05-19', '2011-10-18', 30);
INSERT #Reservations VALUES(100, 1, '2011-05-10','2011-05-24',0);
INSERT #Reservations VALUES(101, 1, '2011-05-18','2011-05-28',0);
INSERT #Reservations VALUES(102, 1, '2011-03-07','2011-03-19',0);
INSERT #Reservations VALUES(103, 1, '2011-08-29','2011-09-07',0);
INSERT #Reservations VALUES(104, 1, '2011-09-01','2011-09-07',1);
INSERT #Reservations VALUES(105, 1, '2011-09-01','2011-09-07',1);
with e as(
SELECT ReservationID as resid1, CheckIn as chin1, 1 as lvl
FROM #Reservations res1
WHERE res1.HotelID = 1
UNION ALL
SELECT ReservationID as resid2, DATEADD(DAY,1,stall.chin1) as chin2, 1
FROM #Reservations res2
INNER JOIN e stall ON stall.chin1 < res2.CheckOut
WHERE stall.resid1 = res2.ReservationID
)
SELECT tb.chin1, SUM(lvl)
FROM e tb
GROUP BY tb.chin1
ORDER BY tb.chin1 DESC
On #HotelAllotments section, there are start and end dates as you can see. The allotment is for daily basis. I mean if row is like below;
INSERT #HotelAllotments VALUES(1,'2011-01-01', '2011-01-03', 10);
It means this;
The Hotel whose id is 1 has 10 allotment on 2011-01-01
The Hotel whose id is 1 has 10 allotment on 2011-01-02
The Hotel whose id is 1 has 10 allotment on 2011-01-03
Then, after that if we receive a reservation between 2011-01-01 and 2011-01-03, like below;
INSERT #Reservations VALUES(106, 1, '2011-01-01','2011-01-03',0);
The situation will be as below;
The Hotel whose id is 1 has 9 allotment left after the reservation on 2011-01-01
The Hotel whose id is 1 has 9 allotment left after the reservation on 2011-01-02
The Hotel whose id is 1 has 10 allotment left after the reservation on 2011-01-03
Above, I have created some temp tables and inserted some fake values and I tried a query. It gets me somewhere (I don't know how to call it. So if you have a
chance to run the query, you would see where it has gotten me so far) but not the place I need. What I need here is that;
I need to list all the dates which a hotel has an agreement and its left allotments after received reservations. here is an example;
HotelID Date Allotment
------- ---------- ---------
1 2011-01-01 9
1 2011-01-02 9
1 2011-01-03 10
1 2011-01-04 10
1 2011-01-05 10
So how can I achieve this?
EDIT
Some them should wonder why an allotment is taken away for the first two days of the reservation, but not the last one. It is because the guest wouldn't be staying all day at the hotel at the last day. S/he should empty the room until 12:00 am. So there won't be any allotment usage on the last date.
;WITH expanded AS (
SELECT
a.HotelID,
Date = DATEADD(DAY, v.number, a.StartDate),
a.Allotment
FROM #HotelAllotments a
INNER JOIN master..spt_values v ON v.type = 'P'
AND v.number BETWEEN 0 AND DATEDIFF(DAY, a.StartDate, a.EndDate)
),
filtered AS (
SELECT
e.HotelID,
e.Date,
Allotment = e.Allotment - COUNT(r.ReservationID)
FROM expanded e
LEFT JOIN #Reservations r ON e.HotelID = r.HotelID
AND e.Date >= r.CheckIn AND e.Date < r.CheckOut
AND r.IsCanceled = 0
GROUP BY e.HotelID, e.Date, e.Allotment
)
SELECT *
FROM filtered;
This solution uses a system table, master..spt_values, as a tally table to obtain the lists of dates instead of the date ranges. Next, the expanded allotment list is joined with the #Resevations table. For every date in the list, the correpsonding allotment is decreased by the number of reservations whose ranges match the given date.
I was a bit hasty on writing my where clause. I didnt know if you wanted to sort out the blank days. here is what i came up with after setting the where clause. The reason i have the datejumps is to compensate for the limitation of 100 recusive calls in sql. So I join with 10 rows from a system table make better use of the 100 recusive, that way i can get 1000 rows instead of 100.
WITH cte(HOTELID, STARTDATE, ENDDATE, Allotment)
as
(
SELECT H.HOTELID, A.STARTDATE + RN STARTDATE, (SELECT MAX(ENDDATE) FROM #HotelAllotments) ENDDATE, (select Allotment from #HotelAllotments where A.STARTDATE + RN between StartDate and enddate and H.HOTELID = HOTELID) Allotment
FROM (
SELECT MIN(STARTDATE) STARTDATE from #HotelAllotments c
) A,
(SELECT TOP 10 rn = ROW_NUMBER() OVER (ORDER BY (SELECT 1))-1 FROM INFORMATION_SCHEMA.COLUMNS) B,
#Hotels H
UNION ALL
SELECT ch.HOTELID, ch.STARTDATE + 10, ENDDATE, (select Allotment from #HotelAllotments where CH.STARTDATE + 10 between StartDate and enddate and CH.HOTELID = HOTELID)
FROM cte ch
WHERE CH.STARTDATE< ENDDATE
AND CH.HOTELID = HOTELID
)
SELECT HotelID, StartDate Date , Allotment - (select count(*) from #Reservations where cte.STARTDATE between CheckIn and CheckOut and cte.HOTELID = HOTELID) Allotment
FROM CTE where allotment is not null
ORDER BY STARTDATE, HOTELID

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