I have a database that stores the log-in and log-out of the employees but we don't have work on weekends. My supervisor want the DTR report format(I'm using RDLC report) include the weekends. (see attached image)
The image above is the expected output format for DTR. I just want to know how to include Weekends though my data are on weekdays only. Is it possible to do this using SQL Query? If yes, should I use looping in sql here?
SQL Code:
select user_id,log_date,login_time,logout_time
from table_DTR
where user_id = 'USER1'
AND log_date BETWEEN '11/21/2014' AND '12/09/2014'
Use common table expression and generate date range with from and to date and than use CTE as left join to actual table. I haven't used user_id filter in left join so apply it to your query:
DECLARE #TMEP TABLE
(
[Date] DATE,
[IN] VARCHAR(10),
[OUT] VARCHAR(10)
)
INSERT INTO #TMEP VALUES ('2014-11-11','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-12','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-13','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-14','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-15','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-18','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-19','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-20','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-21','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-22','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-25','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-26','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-27','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-28','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-29','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-12-1','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-12-2','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-12-3','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-12-4','7:30','5:30')
DECLARE #FromDate DATE
SET #FromDate = '2014-11-11 06:00:00.000'
DECLARE #ToDate DATE
SET #ToDate = '2014-12-11 06:00:00.000'
;WITH CTE_TableDate ([CTEDate]) as
(
SELECT #FromDate
UNION ALL
SELECT DATEADD(DAY,1,CTEDate) FROM CTE_TableDate WHERE [CTEDate] < #ToDate
)
SELECT
CTE_TableDate.CTEDate,
CASE WHEN DATEPART(DW, CTE_TableDate.CTEDate) = 7 THEN 'SATURDAY'
WHEN DATEPART(DW, CTE_TableDate.CTEDate) = 1 THEN 'SUNDAY'
ELSE TEMP.[In] END AS [IN],
CASE WHEN DATEPART(DW, CTE_TableDate.CTEDate) = 7 THEN 'SATURDAY'
WHEN DATEPART(DW, CTE_TableDate.CTEDate) = 1 THEN 'SUNDAY'
ELSE TEMP.[OUT] END AS [OUT]
FROM CTE_TableDate
LEFT JOIN
(
select
[Date],
[IN],
[OUT]
from
#TMEP) TEMP
ON
CTE_TableDate.CTEDate = TEMP.[Date]
try below solution :
DECLARE #startdate DATE = '11/21/2014' -- your start date
DECLARE #enddate DATE = '12/09/2014' -- your start date
-- create list of all dates between min(log_date) and MAX(log_date)
;WITH cte
AS (SELECT #startdate AS log_date
UNION ALL
SELECT Dateadd(dd, 1, log_date) log_date
FROM cte
WHERE log_date < #enddate)
-- select the data using left outer join so that it will return missing dates too.
SELECT t1.user_id,
c.log_date,
t2.login_time,
t2.logout_time
FROM cte c
CROSS JOIN (SELECT DISTINCT user_id
FROM mytable) t1
LEFT OUTER JOIN mytable t2
ON t2.user_id = t1.user_id
AND t2.log_date = c.log_date
ORDER BY t1.user_id,c.log_date
OPTION(maxrecursion 1000)
It will return null in time columns for weekends.
Note : if you are getting error : The statement terminated. The maximum recursion 100 has been exhausted before statement completion. then try using OPTION(maxrecursion 3000) or greater.
You can create a Calendar table as below:
CREATE TABLE dbo.Calendar
(
dt DATE PRIMARY KEY, -- use SMALLDATETIME if < SQL Server 2008
IsWorkDay BIT
);
DECLARE #s DATE, #e DATE;
SELECT #s = '2000-01-01' , #e = '2029-12-31';
INSERT dbo.Calendar(dt, IsWorkDay)
SELECT DATEADD(DAY, n-1, '2000-01-01'), 1
FROM
(
SELECT TOP (DATEDIFF(DAY, #s, #e)+1) ROW_NUMBER()
OVER (ORDER BY s1.[object_id])
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
) AS x(n);
SET DATEFIRST 1;
-- weekends
UPDATE dbo.Calendar SET IsWorkDay = 0
WHERE DATEPART(WEEKDAY, dt) IN (6,7);
-- Christmas
UPDATE dbo.Calendar SET IsWorkDay = 0
WHERE MONTH(dt) = 12
AND DAY(dt) = 25
AND IsWorkDay = 1;
and then use the same as
DECLARE #table_DTR TABLE
( USER_ID VARCHAR(10),
log_date DATE,
login_time TIME,
logout_time TIME)
INSERT INTO #table_DTR VALUES ('USER1','11/21/2014','7:55:00','5:00:00')
select CASE d.IsWorkDay WHEN 0 THEN datename(dw,d.dt) else DTR.user_id END AS user_id,
d.dt AS log_date,
DTR.login_time,
DTR.logout_time
from dbo.Calendar d
LEFT JOIN #table_DTR DTR ON d.dt = DTR.log_date AND DTR.user_id = 'USER1'
WHERE d.dt BETWEEN '11/21/2014' AND '11/26/2014'
For detailed explanation on pros of Calendar table you can refer here..
Related
I want to show all dates between two dates when there is any date data missing then its should show zero in val column .
declare #temp table (
id int identity(1,1) not null,
CDate smalldatetime ,
val int
)
INSERT STATEMENT FOR DATA TO CHECK
insert into #temp select '10/2/2012',1
insert into #temp select '10/3/2012',1
insert into #temp select '10/5/2012',1
insert into #temp select '10/7/2012',2
insert into #temp select '10/9/2012',2
insert into #temp select '10/10/2012',2
insert into #temp select '10/13/2012',2
insert into #temp select '10/15/2012',2
Retrieve records between first day of month and today
select * from #temp where CDate between '10/01/2012' AND '10/15/2012'
As i run this query its show me all data between these two dates but i want to also include missing dates with val=0
SQL FIDDLE WITH SAMPLE DATA
;with d(date) as (
select cast('10/01/2012' as datetime)
union all
select date+1
from d
where date < '10/15/2012'
)
select t.ID, d.date CDate, isnull(t.val, 0) val
from d
left join temp t
on t.CDate = d.date
order by d.date
OPTION (MAXRECURSION 0) -- use this if your dates are >99 days apart
You need to make up the dates, so I've use a recursive common table expression here.
SQL Fiddle
MAXRECURSION number
Specifies the maximum number of recursions allowed for this query. number is a nonnegative
integer between 0 and 32767. When 0 is specified, no limit is applied. If this option is
not specified, the default limit for the server is 100.
When the specified or default number for MAXRECURSION limit is reached during query
execution, the query is ended and an error is returned.
This will work as long as there are less than 2047 days between from and to dates
declare #from smalldatetime = '10/01/2012'
declare #to smalldatetime = '10/15/2012'
select t.id, dateadd(day, number,#from), isnull(val, 0) val from #temp t
right join master..spt_values s
on dateadd(d, s.number, #from) = t.CDate
where
datediff(day, #from, #to ) > s.number
and s.type = 'P'
I think the best way to do this is to create your own table with dates (you can also use master.dbo.spt_values, but I personally don't like that solution)
declare #Temp_Dates table (CDate datetime)
declare #Date datetime
select #Date = (select min(CDate) from temp)
while #Date <= (select max(CDate) from temp)
begin
insert into #Temp_Dates (CDate)
select #Date
select #Date = dateadd(dd, 1, #Date)
end
select D.CDate, isnull(T.id, 0) as id
from #Temp_Dates as D
left outer join temp as T on T.CDate = D.CDate
you can also use recursive solution with CTE
DECLARE #min DATETIME,
#max DATETIME,
#val INT
SELECT #min = Min(CDATE),
#max = Max(CDATE)
FROM TEMP
DECLARE #temp TABLE
(
CDATE SMALLDATETIME,
VAL INT
)
WHILE #min < #max
BEGIN
SELECT #val = VAL
FROM TEMP
WHERE CDATE = #min
INSERT #temp
VALUES (#min,
#val)
SET #min = Dateadd(D, 1, #min)
SET #val = 0
END
SELECT *
FROM #temp
Declare #temp Table(id int identity(1,1) not null,CDate smalldatetime ,val int)
insert into #temp select '10/2/2012',1
insert into #temp select '10/3/2012',1
insert into #temp select '10/5/2012',1
insert into #temp select '10/7/2012',2
insert into #temp select '10/9/2012',2
insert into #temp select '10/10/2012',2
insert into #temp select '10/13/2012',2
insert into #temp select '10/15/2012',2
DECLARE #startDate DATE= '10/01/2012'
DECLARE #endDate DATE= '10/15/2012'
SELECT t.Id, X.[Date],Val = COALESCE(t.val,0)
FROM
(SELECT [Date] = DATEADD(Day,Number,#startDate)
FROM master..spt_values
WHERE Type='P'
AND DATEADD(day,Number,#startDate) <= #endDate)X
LEFT JOIN #temp t
ON X.[Date] = t.CDate
using a recursive cte with min and max
declare #T table (id int identity(1,1) primary key, dt date not null, val int not null);
insert into #T (dt, val) values
('10/2/2012',1)
, ('10/3/2012',1)
, ('10/5/2012',1)
, ('10/7/2012',2)
, ('10/9/2012',2)
, ('10/10/2012',2)
, ('10/13/2012',2)
, ('10/15/2012',2);
--select * from #T;
with cte as
( select min(dt) as dt, max(dt) as mx
from #T
union all
select dateadd(dd, 1, dt), mx
from CTE
where dt < mx
)
select c.dt, isnull(t.val, 0) as val
from cte c
left join #T t
on c.dt = t.dt
order by c.dt
option (maxrecursion 0);
dt val
---------- -----------
2012-10-02 1
2012-10-03 1
2012-10-04 0
2012-10-05 1
2012-10-06 0
2012-10-07 2
2012-10-08 0
2012-10-09 2
2012-10-10 2
2012-10-11 0
2012-10-12 0
2012-10-13 2
2012-10-14 0
2012-10-15 2
I have start date, end date and name of days. How can fetch all dates between those two dates of that specific days in sql?
example data:
start_date:4/11/2018
end_date: 5/11/2018
days: monday, thursday
expected output: all dates between start and end date which comes on monday and thursday and store them in table
updated
my present code(not working)
; WITH CTE(dt)
AS
(
SELECT #P_FROM_DATE
UNION ALL
SELECT DATEADD(dw, 1, dt) FROM CTE
WHERE dt < #P_TO_DATE
)
INSERT INTO Table_name
(
ID
,DATE_TIME
,STATUS
,CREATED_DATE
,CREATED_BY
)
SELECT #P_ID
,(SELECT dt FROM CTE WHERE DATENAME(dw, dt) In ('tuesday','friday',null))
,'NOT SENT'
,CAST(GETDATE() AS DATE)
,#USER_ID
Another approach for generating dates between ranges can be like following query. This will be faster compared to CTE or WHILE loop.
DECLARE #StartDate DATETIME = '2018-04-11'
DECLARE #EndDate DATETIME = '2018-05-15'
SELECT #StartDate + RN AS DATE FROM
(
SELECT (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)))-1 RN
FROM master..[spt_values] T1
) T
WHERE RN <= DATEDIFF(DAY,#StartDate,#EndDate)
AND DATENAME(dw,#StartDate + RN) IN('Monday','Thursday')
Note:
If the row count present in master..[spt_values] is not sufficient for the provided range, you can make a cross join with the same to get a bigger range like following.
SELECT (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)))-1 RN
FROM master..[spt_values] T1
CROSS JOIN master..[spt_values] T2
By this you will be able to generate date between a range with gap of 6436369 days.
You can use a recursive common table expression (CTE) to generate a list of days. With datepart(dw, ...) you can filter for specific days of the week.
An example that creates a list of Mondays and Thursdays between March 1st and today:
create table ListOfDates (dt date);
with cte as
(
select cast('2018-03-01' as date) as dt -- First day of interval
union all
select dateadd(day, 1, dt)
from cte
where dt < getdate() -- Last day of interval
)
insert into ListOfDates
(dt)
select dt
from cte
where datepart(dw, dt) in (2, 5) -- 2=Monday and 5=Thursday
option (maxrecursion 0)
See it working at SQL Fiddle.
This will work for you:
DECLARE #table TABLE(
ID INT IDENTITY(1,1),
Date DATETIME,
Day VARCHAR(50)
)
DECLARE #Days TABLE(
ID INT IDENTITY(1,1),
Day VARCHAR(50)
)
INSERT INTO #Days VALUES ('Monday')
INSERT INTO #Days VALUES ('Thursday')
DECLARE #StartDate DATETIME='2018-01-01';
DECLARE #EndDate DATETIME=GETDATE();
DECLARE #Day VARCHAR(50)='Friday';
DECLARE #TempDate DATETIME=#StartDate;
WHILE CAST(#TempDate AS DATE)<=CAST(#EndDate AS DATE)
BEGIN
IF EXISTS (SELECT 1 FROM #Days WHERE DAY IN (DATENAME(dw,#TempDate)))
BEGIN
INSERT INTO #table
VALUES (
#TempDate, -- Date - datetime
DATENAME(dw,#TempDate) -- Day - varchar(50)
)
END
SET #TempDate=DATEADD(DAY,1,#TempDate)
END
SELECT * FROM #table
INSERT INTO TargetTab(dateCOL)
SELECT dateCOL
FROM tab
WHERE dateCOL >= startdate AND dateCOL <= enddate
AND (DATENAME(dw,dateCOL) ='Thursday' OR DATENAME(dw,dateCOL) = 'Monday')
Try this query to get your result.
Use a recursive CTE to generate your dates, then filter by week day.
SET DATEFIRST 1 -- 1: Monday, 7 Sunday
DECLARE #StartDate DATE = '2018-04-11'
DECLARE #EndDate DATE = '2018-05-15'
DECLARE #WeekDays TABLE (WeekDayNumber INT)
INSERT INTO #WeekDays (
WeekDayNumber)
VALUES
(1), -- Monday
(4) -- Thursday
;WITH GeneratingDates AS
(
SELECT
GeneratedDate = #StartDate,
WeekDay = DATEPART(WEEKDAY, #StartDate)
UNION ALL
SELECT
GeneratedDate = DATEADD(DAY, 1, G.GeneratedDate),
WeekDay = DATEPART(WEEKDAY, DATEADD(DAY, 1, G.GeneratedDate))
FROM
GeneratingDates AS G -- Notice that we are referencing a CTE that we are also declaring
WHERE
G.GeneratedDate < #EndDate
)
SELECT
G.GeneratedDate
FROM
GeneratingDates AS G
INNER JOIN #WeekDays AS W ON G.WeekDay = W.WeekDayNumber
OPTION
(MAXRECURSION 30000)
Try this:
declare #start date = '04-11-2018'
declare #end date = '05-11-2018'
declare #P_ID int = 1
declare #USER_ID int = 11
;with cte as(
select #start [date]
union all
select dateadd(DAY, 1, [date]) from cte
where [date] < #end
)
--if MY_TABLE doesn't exist
select #P_ID,
[date],
'NOT SENT',
cast(getdate() as date),
#USER_ID
into MY_TABLE
from cte
--here you can specify days: 1 - Sunday, 2 - Monday, etc.
where DATEPART(dw,[date]) in (2, 5)
option (maxrecursion 0)
--if MY_TABLE does exist
--insert into MY_TABLE
--select #P_ID,
-- [date],
-- 'NOT SENT',
-- cast(getdate() as date),
-- #USER_ID
--from cte
--where DATEPART(dw,[date]) in (2, 5)
--option (maxrecursion 0)
I have a sql server table with the following structure and data:
Created , keyword
'2017-10-03 19:18:00', 'test7'
'2017-10-07 01:06:00', 'test3'
'2017-10-07 15:19:00', 'test2'
'2017-10-07 21:39:00', 'test10'
'2017-10-08 00:36:00', 'test3'
'2017-10-08 01:26:00', 'test13'
'2017-10-08 01:33:00', 'test9'
'2017-10-08 08:23:00', 'test13'
'2017-10-08 09:35:00', 'test9'
'2017-10-08 12:38:00', 'test9'
'2017-10-08 15:07:00', 'test2'
'2017-10-10 05:09:00', 'test4'
I would like to run a query that counts activity and group it by day, also accounting for days when nothing was recorded and show the activity for those days as zero. As such I want a query that will return the result set below:
'2017-10-03', 1
'2017-10-04', 0
'2017-10-05', 0
'2017-10-06', 0
'2017-10-07', 3
'2017-10-08', 7
'2017-10-09', 0
'2017-10-03', 1
I know how to run a query and group it by count for days, but not how to account for days nothing was recorded. As I am new to Sql, I would really appreciate it if someone can provide a working example. Thanks in advance
Try this:
declare #startDate date = '2017-10-01'
declare #endDate date = '2017-10-31'
;with cte as (
select cast(#startDate as date) [dayOfYear]
union all
select DATEADD(day, 1, [dayOfYear]) from cte
where [dayOfYear] < #endDate
)
select dayOfYear, SUM(case when Created is null then 0 else 1 end) from cte
left join MY_TABLE [T] on cte.dayOfYear = CAST(T.Created as date)
group by dayOfYear
The logic is as follows:
get table with all days between #startDate and #endDate (the CTE - I specified first and last of October). Then we left join your table and when the days has no match, we define corresponding value to 0, 1 otherwise. Then it's enough to sum these values day-wise.
Here is a solution when you don't have calendar table:
select row_number() over(order by getdate()) - 1 as n
into #nums
from sys.columns cross join sys.columns c2;
declare #t table(Created datetime, keyword varchar(100));
insert into #t values
('2017-10-03 19:18:00', 'test7'),
('2017-10-07 01:06:00', 'test3'),
('2017-10-07 15:19:00', 'test2'),
('2017-10-07 21:39:00', 'test10'),
('2017-10-08 00:36:00', 'test3'),
('2017-10-08 01:26:00', 'test13'),
('2017-10-08 01:33:00', 'test9'),
('2017-10-08 08:23:00', 'test13'),
('2017-10-08 09:35:00', 'test9'),
('2017-10-08 12:38:00', 'test9'),
('2017-10-08 15:07:00', 'test2'),
('2017-10-10 05:09:00', 'test4')
declare #min_dt date, #max_dt date;
select #min_dt = min(Created), #max_dt = max(Created)
from #t;
with calendar as
(
select dateadd(day, n, #min_dt) as dt
from #nums
where dateadd(day, n, #min_dt) <= #max_dt
)
select c.dt, isnull(count(t.keyword), 0) as cnt
from calendar c left join #t t
on c.dt = cast(t.Created as date)
group by c.dt;
In my case I don't have table calendar but I have fixed table with the numbers (Nums), but if you don't have even table of numbers you can generate it as I did in #nums (you should limit the numbers generated to a reasonable number)
declare #date int
WITH CTE_DatesTable
AS
(
SELECT CAST('20171003' as date) AS [date]
UNION ALL
SELECT DATEADD(dd, 1, [date])
FROM CTE_DatesTable
WHERE DATEADD(dd, 1, [date]) <= '20171010'
)
SELECT [CalendarDate]=[date] into #DimDate FROM CTE_DatesTable
OPTION (MAXRECURSION 0);
select * from #DimDate
This will create a calendar table to join with your current table to fill the gaps
I'm working with a set of date intervals where each interval has a version number and new intervals will frequently overlap old ones, or even be subsets of them. From this data I need to calculate a new set of intervals that shows the most recent version number, at each point in time. Is there a set-based solution to this problem?
Here's an illustration:
Interval 1: 11111111111111111111111
Interval 2: 2222222222
Interval 3: 33333333333333
Interval 4: 444444444
Interval 5: 555555555
Result : 11333333333333331155555555544
Here is a sample of the data I'm working with:
groupId startDate endDate version
-------- --------- ---------- ------
1 1/1/2010 1/1/2011 1
1 10/1/2010 7/5/2011 2
1 7/5/2011 8/13/2012 3
1 8/13/2012 12/31/2012 6
1 10/1/2012 11/1/2012 8
... and the desired output:
groupId startDate endDate version
-------- --------- ---------- ------
1 1/1/2010 10/1/2010 1
1 10/1/2010 7/5/2011 2
1 7/5/2011 8/13/2012 3
1 8/13/2011 10/1/2012 6
1 10/1/2012 11/1/2012 8 << note how version 8 supersedes version 6
1 11/1/2012 12/31/2012 6 << version 6 is split into two records
I haven't found any other examples of this problem, my googling only turns up queries that identify gaps and islands or covering sets.
I think I have an iterative solution (SQL Server 2008). It starts with a temp table for intervals in the result set and defines the start and end points for the range that we want to cover by inserting records with special version numbers. Then, it repeatedly identifies gaps between result set intervals and attempts to fill them with the most recent records from the original data set, until there are no more gaps or no more records to add:
GO
-- Create data set and results table
CREATE TABLE #Data (
groupId INT
,startDate DATE
,endDate DATE
,versionId INT
)
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (1, '2007-12-22', '2008-12-22', 8)
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (1, '2008-12-22', '2009-12-22', 9)
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (1, '2009-12-22', '2010-12-22', 10)
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (1, '2010-12-22', '2011-12-22', 11)
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (1, '2011-01-01', '2011-11-30', 500)
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (1, '2011-12-22', '2012-12-22', 12)
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (1, '2012-01-22', '2012-12-22', 13)
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (1, '2012-01-22', '2012-12-22', 14)
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (1, '2012-04-22', '2012-12-22', 17)
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (1, '2012-04-22', '2012-12-22', 19)
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (2, '2010-01-01', '2011-01-01', 1)
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (2, '2010-10-01', '2011-07-05', 2)
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (2, '2011-07-05', '2012-08-13', 3)
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (2, '2012-08-13', '2012-12-31', 6)
INSERT INTO #Data (groupId, startDate, endDate, versionId) VALUES (2, '2012-10-01', '2012-11-01', 8)
CREATE TABLE #Results (
groupId VARCHAR(10)
,startDate DATE
,endDate DATE
,versionId BIGINT
)
DECLARE #startDate DATE
DECLARE #endDate DATE
DECLARE #placeholderId BIGINT
SET #startDate = '20030101'
SET #endDate = '20121231'
SET #placeholderId = 999999999999999
INSERT #Results
SELECT DISTINCT
groupId
,CASE WHEN MIN(startDate) < #startDate THEN MIN(startDate) ELSE #startDate END
,CASE WHEN MIN(startDate) < #startDate THEN #startDate ELSE MIN(startDate) END
,#placeholderId
FROM #data
GROUP BY groupId
UNION ALL
SELECT DISTINCT
groupId
,CASE WHEN MAX(endDate) < #endDate THEN MAX(endDate) ELSE #endDate END
,CASE WHEN MAX(endDate) < #endDate THEN #endDate ELSE MAX(endDate) END
,#placeholderId
FROM #data
GROUP BY groupId
GO
-- Fill gaps in results table
DECLARE #startDate DATE
DECLARE #endDate DATE
DECLARE #placeholderId BIGINT
SET #startDate = '20030101'
SET #endDate = '20111231'
SET #placeholderId = 999999999999999
DECLARE #counter INT
SET #counter = 0
WHILE #counter < 10
BEGIN
SET #counter = #counter + 1;
WITH Gaps AS (
SELECT
gs.groupId
,gs.startDate
,MIN(ge.endDate) as endDate
,ROW_NUMBER() OVER (ORDER BY gs.groupId, gs.startDate) as gapId
FROM (
SELECT groupId, endDate as startDate
FROM #Results r1
WHERE NOT EXISTS (
SELECT *
FROM #Results r2
WHERE r2.groupId = r1.groupId
AND r2.versionId <> r1.versionId
AND r2.startDate <= r1.endDate
AND r2.endDate > r1.endDate
)
AND NOT (endDate >= #endDate AND versionId = #placeholderId)
) gs
INNER JOIN (
SELECT groupId, startDate as endDate
FROM #Results r1
WHERE NOT EXISTS (
SELECT *
FROM #Results r2
WHERE r2.groupId = r1.groupId
AND r2.versionId <> r1.versionId
AND r2.endDate >= r1.startDate
AND r2.startDate < r1.startDate
)
AND NOT (startDate <= #startDate AND versionId = #placeholderId)
) ge
ON ge.groupId = gs.groupId
AND ge.endDate >= gs.startDate
GROUP BY gs.groupId, gs.startDate
)
INSERT #Results (
groupId
,startDate
,endDate
,versionId
)
SELECT
d.groupId
,CASE WHEN d.startDate < g.startDate THEN g.startDate ELSE d.startDate END
,CASE WHEN d.endDate > g.endDate THEN g.endDate ELSE d.endDate END
,d.versionId
FROM #Data d
INNER JOIN Gaps g
ON g.groupId = d.groupId
AND g.startDate <= d.endDate
AND g.endDate >= d.startDate
INNER JOIN (
SELECT
d.groupId
,gapId
,MAX(d.versionId) as versionId
FROM #Data d
INNER JOIN Gaps g
ON g.groupId = d.groupId
AND g.startDate <= d.endDate
AND g.endDate >= d.startDate
WHERE d.versionId < (
SELECT MIN(versionId)
FROM #Results r
WHERE r.groupId = d.groupId
AND (r.startDate = g.endDate OR r.endDate = g.startDate)
)
AND NOT EXISTS (
SELECT *
FROM #Data dsup
WHERE dsup.groupId = d.groupId
AND dsup.versionId > d.versionId
AND dsup.startDate <= d.startDate
AND dsup.endDate >= d.endDate
)
GROUP BY
d.groupId
,g.gapId
) mg
ON mg.groupId = g.groupId
AND mg.gapId = g.gapId
AND mg.versionId = d.versionId
END
SELECT *
FROM #Results
WHERE versionId <> #placeholderId
order by groupId, startDate
A set-based solution would be much more useful, but I've struggled to find one. Any ideas?
-- create a dates table
create table dates (thedate date primary key clustered);
;with dates(thedate) as (
select dateadd(yy,years.number,0)+days.number
from master..spt_values years
join master..spt_values days
on days.type='p' and days.number < datepart(dy,dateadd(yy,years.number+1,0)-1)
where years.type='p' and years.number between 100 and 150
-- note: 100-150 creates dates in the year range 2000-2050
-- adjust as required
)
insert dbo.dates select * from dates;
-- for each date, determine the prevailing version
select t.groupId, d.thedate, max(t.versionId) versionId
into #tmp1
from dates d
join #Data t on t.startDate <= d.thedate and d.thedate <= t.endDate
group by t.groupId, d.thedate;
-- create index to help
create clustered index cix_tmp1 on #tmp1(groupId, thedate, versionId);
-- find the start dates
;with t as (
select a.*, rn=row_number() over (partition by a.groupId order by a.thedate)
from #tmp1 a
left join #tmp1 b on b.thedate = dateadd(d,-1,a.thedate) and a.groupId = b.groupId and a.versionId = b.versionId
where b.versionId is null
)
select c.groupId, c.thedate startdate, dateadd(d,-1,d.thedate) enddate, c.versionId
from t c
left join t d on d.rn=c.rn+1 and c.groupId = d.groupId
order by groupId, startdate;
Of course, you can do everything in "one query" but do it at your peril, as the performance goes down the drain, big time.
DO NOT USE - for academic interest only-
;with dates(thedate) as (
select dateadd(yy,years.number,0)+days.number
from master..spt_values years
join master..spt_values days
on days.type='p' and days.number < datepart(dy,dateadd(yy,years.number+1,0)-1)
where years.type='p' and years.number between 100 and 150
-- note: 100-150 creates dates in the year range 2000-2050
-- adjust as required
), tmp1 as (
select t.groupId, d.thedate, max(t.versionId) versionId
from dates d
join #Data t on t.startDate <= d.thedate and d.thedate <= t.endDate
group by t.groupId, d.thedate
), t as (
select a.*, rn=row_number() over (partition by a.groupId order by a.thedate)
from tmp1 a
left join tmp1 b on b.thedate = dateadd(d,-1,a.thedate) and a.groupId = b.groupId and a.versionId = b.versionId
where b.versionId is null
)
select c.groupId, c.thedate startdate, dateadd(d,-1,d.thedate) enddate, c.versionId
from t c
left join t d on d.rn=c.rn+1 and c.groupId = d.groupId
order by groupId, startdate;
Updated due to some feedback from the comments. I'm not going to worry about the end cases that a few people have pointed out since they've been proven trivial to solve in other Answers, but I wanted to go ahead and get a working version out that didn't require DDL... I figure it's just good to have options. :-)
This code should work:
select nesty.groupId, nesty.startDate, nesty.segment_end_date, Max(bob.versionId)
from(
select starter.groupId, starter.startDate,
coalesce(DATEADD(DAY,-1,ender.startDate),('2012-12-31')) AS segment_end_date
from
(select groupId, startDate, ROW_NUMBER() over (partition by groupID order by startDate) as rownumber from
(select groupID, startDate from #Data union select groupID, DATEADD(DAY, 1,endDate) as startDate from #Data) xx) starter
left outer join
(select groupId, startDate, ROW_NUMBER() over (partition by groupID order by startDate) as rownumber from
(select groupID, startDate from #Data union select groupID, DATEADD(DAY, 1,endDate) as startDate from #Data) xy) ender on
starter.groupId = ender.groupId and
starter.rownumber = ender.rownumber - 1
where
starter.startDate<= coalesce(DATEADD(DAY,-1,ender.startDate),('2012-12-31'))
) nesty
left outer join #Data bob on
bob.groupId = nesty.groupId and
nesty.segment_end_date between bob.startDate and bob.endDate
group by nesty.groupId, nesty.startDate, nesty.segment_end_date
order by nesty.groupId, nesty.startDate
There are a couple of tiny caveats I had to do to get it into a single SQL statement. First, the max end date is not dynamic; I hard coded '2012-12-31'. You can replace it with a MAX(endDate), but you can't put that in the GROUP BY statement. If you can do this in a procedure, you can do:
select into #max_end_date MAX(endDate) from #Data
and replace '2012-12-31' with #max_end_date.
Second, I do not guarantee that two adjacent segments won't have the same value! This may or may not be important to you... that is, if you had the following:
Interval 1: 111111
Interval 2: 22222222222222
Your output would be:
Interval 1: 2222
Interval 2: 2222222222
Still, I think it's worth hitting it in a simple and efficient SQL query. It may not be hard to fix those caveats, but it didn't matter to what I was working on, so I haven't bothered yet.
If the end dates are important, as well as gaps, here's a way you can do it. This solution could also be adapted to work if your versions are datetimes instead of just dates.
First a bunch of functions
One to get the version at a given date
Create Function dbo.VersionAtDate(#GroupID int, #Date datetime) Returns int as
Begin
Declare #Ret int = Null
Select
#Ret = Max(VersionID)
From
VersionedIntervals iv
Where
iv.GroupID = #GroupID And
iv.StartDate <= #Date And
iv.EndDate + 1 > #Date -- if dates were half open intervals this would just be iv.EndDate > #Date
Return #Ret
End
Next to get the midpoint of two datetimes (minute resolution):
Create Function dbo.Midpoint(#Start datetime, #End datetime) Returns datetime as
Begin
Return DateAdd(Minute, DateDiff(Minute, #Start, #End) / 2, #Start)
End
Version at a midpoint:
Create Function dbo.VersionAtMidpoint(#GroupID int, #Start datetime, #End datetime) returns int as
Begin
Return dbo.VersionAtDate(#GroupID, dbo.Midpoint(#Start, #End))
End;
Finally a table valued function to help with the fact that some points are the start of one range and the end of another, and it helps to get two rows from one input for this:
-- returns two rows if a point is the end of one interval and the
-- start of another
Create Function dbo.EndPoints(#GroupID int, #RN bigint, #Start datetime, #End datetime, #Next datetime, #Version int)
Returns #EndPoints Table (
GroupID int,
RN bigint,
Version int,
StartDate datetime,
EndDate datetime
) As
Begin
Declare #NextVersion int, #VersionAtMidpoint int
Set #NextVersion = dbo.VersionAtDate(#GroupID, #Next)
If #NextVersion = #Version
-- interval carries on
Insert Into #EndPoints Select #GroupID, #RN, #Version, #Start, #Next
Else
Begin
-- interval has ended
Set #VersionAtMidpoint = dbo.VersionAtMidPoint(#GroupID, #End, #Next)
If #VersionAtMidpoint != #Version
-- we have something like this, start a run of 3s (run of 4s is already ended by previous call)
-- 3333333
-- 44
Insert Into #EndPoints Select #GroupID, #RN, #VersionAtMidpoint, #End, #Next
Else
Begin
-- We have something like this, end the run of 3s and start the run of fours
-- 33333
-- 444
Insert Into #EndPoints Select #GroupID, -1, #Version, #Start, #Next
Insert Into #EndPoints Select #GroupID, #RN, #NextVersion, #Next, #Next
End
End
Return
End
With all this machinery in place, finally a recursive CTE plust table variable, you'll need to set maxrecursion appropriately:
Declare #Bounds Table (GroupID int, RN bigint, BoundDate datetime, Primary Key (GroupID, RN))
Insert Into
#Bounds
Select
GroupID,
Row_Number() Over (Partition By GroupID Order By BoundDate),
BoundDate
From (
Select
GroupID,
StartDate As BoundDate
From
dbo.VersionedIntervals
Union
Select
GroupID,
EndDate
From
dbo.VersionedIntervals
) a
;With VersionedBounds (GroupID, RN, StartDate, EndDate, Version) as (
Select
GroupID,
RN,
BoundDate,
BoundDate,
dbo.VersionAtDate(GroupID, BoundDate)
From
#Bounds
Where
RN = 1
Union All
Select
e.GroupID,
e.RN,
e.StartDate,
e.EndDate,
e.Version
From
#Bounds b
Inner Join
VersionedBounds v
On v.GroupID = b.GroupID And b.RN = v.RN + 1
Cross Apply
dbo.EndPoints(v.GroupID, b.RN, v.StartDate, v.EndDate, b.BoundDate, v.Version) e
)
Select
GroupID,
StartDate,
Max(EndDate) As EndDate,
Max(Version) As Version
From
VersionedBounds
Group By
GroupID,
StartDate
Order By
GroupID,
StartDate
http://sqlfiddle.com/#!6/b95bd/2
I want to show all dates between two dates when there is any date data missing then its should show zero in val column .
declare #temp table (
id int identity(1,1) not null,
CDate smalldatetime ,
val int
)
INSERT STATEMENT FOR DATA TO CHECK
insert into #temp select '10/2/2012',1
insert into #temp select '10/3/2012',1
insert into #temp select '10/5/2012',1
insert into #temp select '10/7/2012',2
insert into #temp select '10/9/2012',2
insert into #temp select '10/10/2012',2
insert into #temp select '10/13/2012',2
insert into #temp select '10/15/2012',2
Retrieve records between first day of month and today
select * from #temp where CDate between '10/01/2012' AND '10/15/2012'
As i run this query its show me all data between these two dates but i want to also include missing dates with val=0
SQL FIDDLE WITH SAMPLE DATA
;with d(date) as (
select cast('10/01/2012' as datetime)
union all
select date+1
from d
where date < '10/15/2012'
)
select t.ID, d.date CDate, isnull(t.val, 0) val
from d
left join temp t
on t.CDate = d.date
order by d.date
OPTION (MAXRECURSION 0) -- use this if your dates are >99 days apart
You need to make up the dates, so I've use a recursive common table expression here.
SQL Fiddle
MAXRECURSION number
Specifies the maximum number of recursions allowed for this query. number is a nonnegative
integer between 0 and 32767. When 0 is specified, no limit is applied. If this option is
not specified, the default limit for the server is 100.
When the specified or default number for MAXRECURSION limit is reached during query
execution, the query is ended and an error is returned.
This will work as long as there are less than 2047 days between from and to dates
declare #from smalldatetime = '10/01/2012'
declare #to smalldatetime = '10/15/2012'
select t.id, dateadd(day, number,#from), isnull(val, 0) val from #temp t
right join master..spt_values s
on dateadd(d, s.number, #from) = t.CDate
where
datediff(day, #from, #to ) > s.number
and s.type = 'P'
I think the best way to do this is to create your own table with dates (you can also use master.dbo.spt_values, but I personally don't like that solution)
declare #Temp_Dates table (CDate datetime)
declare #Date datetime
select #Date = (select min(CDate) from temp)
while #Date <= (select max(CDate) from temp)
begin
insert into #Temp_Dates (CDate)
select #Date
select #Date = dateadd(dd, 1, #Date)
end
select D.CDate, isnull(T.id, 0) as id
from #Temp_Dates as D
left outer join temp as T on T.CDate = D.CDate
you can also use recursive solution with CTE
DECLARE #min DATETIME,
#max DATETIME,
#val INT
SELECT #min = Min(CDATE),
#max = Max(CDATE)
FROM TEMP
DECLARE #temp TABLE
(
CDATE SMALLDATETIME,
VAL INT
)
WHILE #min < #max
BEGIN
SELECT #val = VAL
FROM TEMP
WHERE CDATE = #min
INSERT #temp
VALUES (#min,
#val)
SET #min = Dateadd(D, 1, #min)
SET #val = 0
END
SELECT *
FROM #temp
Declare #temp Table(id int identity(1,1) not null,CDate smalldatetime ,val int)
insert into #temp select '10/2/2012',1
insert into #temp select '10/3/2012',1
insert into #temp select '10/5/2012',1
insert into #temp select '10/7/2012',2
insert into #temp select '10/9/2012',2
insert into #temp select '10/10/2012',2
insert into #temp select '10/13/2012',2
insert into #temp select '10/15/2012',2
DECLARE #startDate DATE= '10/01/2012'
DECLARE #endDate DATE= '10/15/2012'
SELECT t.Id, X.[Date],Val = COALESCE(t.val,0)
FROM
(SELECT [Date] = DATEADD(Day,Number,#startDate)
FROM master..spt_values
WHERE Type='P'
AND DATEADD(day,Number,#startDate) <= #endDate)X
LEFT JOIN #temp t
ON X.[Date] = t.CDate
using a recursive cte with min and max
declare #T table (id int identity(1,1) primary key, dt date not null, val int not null);
insert into #T (dt, val) values
('10/2/2012',1)
, ('10/3/2012',1)
, ('10/5/2012',1)
, ('10/7/2012',2)
, ('10/9/2012',2)
, ('10/10/2012',2)
, ('10/13/2012',2)
, ('10/15/2012',2);
--select * from #T;
with cte as
( select min(dt) as dt, max(dt) as mx
from #T
union all
select dateadd(dd, 1, dt), mx
from CTE
where dt < mx
)
select c.dt, isnull(t.val, 0) as val
from cte c
left join #T t
on c.dt = t.dt
order by c.dt
option (maxrecursion 0);
dt val
---------- -----------
2012-10-02 1
2012-10-03 1
2012-10-04 0
2012-10-05 1
2012-10-06 0
2012-10-07 2
2012-10-08 0
2012-10-09 2
2012-10-10 2
2012-10-11 0
2012-10-12 0
2012-10-13 2
2012-10-14 0
2012-10-15 2