Related
I need to fill the range from 2017-04-01 to 2017-04-30 with the data from this table, knowing that the highest priority records should prevail over those with lower priorities
id startValidity endValidity priority
-------------------------------------------
1004 2017-04-03 2017-04-30 1
1005 2017-04-10 2017-04-22 2
1010 2017-04-19 2017-04-23 3
1006 2017-04-24 2017-04-28 2
1008 2017-04-26 2017-04-28 3
In practice I would need to get a result like this:
id startValidity endValidity priority
--------------------------------------------
1004 2017-04-03 2017-04-09 1
1005 2017-04-10 2017-04-18 2
1010 2017-04-19 2017-04-23 3
1006 2017-04-24 2017-04-25 2
1008 2017-04-26 2017-04-28 3
1004 2017-04-29 2017-04-30 1
can't think of anything elegant or more efficient solution right now . . .
-- Sample Table
declare #tbl table
(
id int,
startValidity date,
endValidty date,
priority int
)
-- Sample Data
insert into #tbl select 1004, '2017-04-03', '2017-04-30', 1
insert into #tbl select 1005, '2017-04-10', '2017-04-22', 2
insert into #tbl select 1010, '2017-04-19', '2017-04-23', 3
insert into #tbl select 1006, '2017-04-24', '2017-04-28', 2
insert into #tbl select 1008, '2017-04-26', '2017-04-28', 3
-- Query
; with
date_range as -- find the min and max date for generating list of dates
(
select start_date = min(startValidity), end_date = max(endValidty)
from #tbl
),
dates as -- gen the list of dates using recursive CTE
(
select rn = 1, date = start_date
from date_range
union all
select rn = rn + 1, date = dateadd(day, 1, d.date)
from dates d
where d.date < (select end_date from date_range)
),
cte as -- for each date, get the ID based on priority
(
select *, grp = row_number() over(order by id) - rn
from dates d
outer apply
(
select top 1 x.id, x.priority
from #tbl x
where x.startValidity <= d.date
and x.endValidty >= d.date
order by x.priority desc
) t
)
-- final result
select id, startValidity = min(date), endValidty = max(date), priority
from cte
group by grp, id, priority
order by startValidity
I do not understand the purpose of Calendar CTE or table.
So I am not using any REcursive CTE or calendar.
May be I hvn't understood the requirement completly.
Try this with diff sample data,
declare #tbl table
(
id int,
startValidity date,
endValidty date,
priority int
)
-- Sample Data
insert into #tbl select 1004, '2017-04-03', '2017-04-30', 1
insert into #tbl select 1005, '2017-04-10', '2017-04-22', 2
insert into #tbl select 1010, '2017-04-19', '2017-04-23', 3
insert into #tbl select 1006, '2017-04-24', '2017-04-28', 2
insert into #tbl select 1008, '2017-04-26', '2017-04-28', 3
;With CTE as
(
select * ,ROW_NUMBER()over(order by startValidity)rn
from #tbl
)
,CTE1 as
(
select c.id,c.startvalidity,isnull(dateadd(day,-1, c1.startvalidity)
,c.endValidty) Endvalidity
,c.[priority],c.rn
from cte c
left join cte c1
on c.rn+1=c1.rn
)
select id,startvalidity,Endvalidity,priority from cte1
union ALL
select id,startvalidity,Endvalidity,priority from
(
select top 1 id,ca.startvalidity,ca.Endvalidity,priority from cte1
cross apply(
select top 1
dateadd(day,1,endvalidity) startvalidity
,dateadd(day,-1,dateadd(month, datediff(month,0,endvalidity)+1,0)) Endvalidity
from cte1
order by rn desc)CA
order by priority
)t4
--order by startvalidity --if req
In a same id, if any of row's effective date and enddate overlaps then we need group it up in a unique id
In below image dategroup is the desired output column
Data is sorted in order by ID asc, EffectiveDate ASC, EndDate Desc
CREATE TABLE #DataTable (id int , EffectiveDate datetime, Enddate Datetime )
INSERT [dbo].#DataTable ([id], [EffectiveDate], [Enddate]) VALUES (1, CAST(N'2017-01-01 00:00:00.000' AS DateTime), CAST(N'2017-01-11 00:00:00.000' AS DateTime))
GO
INSERT [dbo].#DataTable ([id], [EffectiveDate], [Enddate]) VALUES (1, CAST(N'2017-01-02 00:00:00.000' AS DateTime), CAST(N'2017-01-05 00:00:00.000' AS DateTime))
GO
INSERT [dbo].#DataTable ([id], [EffectiveDate], [Enddate]) VALUES (1, CAST(N'2017-01-03 00:00:00.000' AS DateTime), CAST(N'2017-01-12 00:00:00.000' AS DateTime))
GO
INSERT [dbo].#DataTable ([id], [EffectiveDate], [Enddate]) VALUES (1, CAST(N'2017-01-06 00:00:00.000' AS DateTime), CAST(N'2017-01-09 00:00:00.000' AS DateTime))
GO
INSERT [dbo].#DataTable ([id], [EffectiveDate], [Enddate]) VALUES (1, CAST(N'2017-01-13 00:00:00.000' AS DateTime), CAST(N'2017-01-19 00:00:00.000' AS DateTime))
GO
INSERT [dbo].#DataTable ([id], [EffectiveDate], [Enddate]) VALUES (2, CAST(N'2017-02-01 00:00:00.000' AS DateTime), CAST(N'2017-02-11 00:00:00.000' AS DateTime))
GO
INSERT [dbo].#DataTable ([id], [EffectiveDate], [Enddate]) VALUES (2, CAST(N'2017-02-06 00:00:00.000' AS DateTime), CAST(N'2017-02-16 00:00:00.000' AS DateTime))
GO
Try this, Hope it helps. Not the most attractive code but it should work. I may clean it up later if I find some time.
;WITH cte_StepOne as
(
SELECT ROW_NUMBER() OVER (ORDER BY a.[id],
a.[EffectiveDate],
a.[Enddate]) AS SeqNo,
a.[id],
a.[EffectiveDate],
a.[Enddate],
b.[id] AS OverLapID,
b.[EffectiveDate] AS [OverLapEffectiveDate],
b.[Enddate] AS [OverLapEnddate]
FROM ##DataTable a
LEFT JOIN ##DataTable b
ON a.EffectiveDate BETWEEN b.EffectiveDate
AND b.EndDate
AND a.EffectiveDate <> b.EffectiveDate
AND a.EndDate <> b.EndDate --and a.ID <> b.ID
)
,cte_StepTwo AS
(
SELECT SeqNo,
id,
EffectiveDate,
Enddate,
LEAD(OverLapEffectiveDate, 1) OVER (ORDER BY SeqNo) AS LeadValue,LAG(id, 1) OVER (ORDER BY SeqNo) AS LeadValueID,
OverLapID,
OverLapEffectiveDate,
OverLapEnddate
FROM cte_StepOne
)
,cte_Result AS
(
SELECT id,
EffectiveDate,
Enddate,
CASE
WHEN LeadValue = EffectiveDate AND OverLapEffectiveDate IS NULL THEN ID
WHEN OverLapID IS NULL THEN LeadValueID + 1
ELSE OverLapID
END AS OverLapID,
CASE
WHEN LeadValue = EffectiveDate AND OverLapEffectiveDate IS NULL THEN EffectiveDate
ELSE OverLapEffectiveDate
END AS OverLapEffectiveDate,
CASE
WHEN LeadValue = EffectiveDate AND OverLapEffectiveDate IS NULL THEN Enddate
ELSE OverLapEnddate
END AS OverLapEnddate
FROM cte_StepTwo
)
SELECT DISTINCT id,
EffectiveDate,
Enddate,
DENSE_RANK() OVER (ORDER BY ID,OverLapID) AS DateGroup
FROM cte_Result
ORDER BY id,EffectiveDate
Result:
This answer takes the approach of trying to identify records for which the running DateGroup counter should be incremented. Ultimately, we will assign a value of 1 to such records. With this assignment in hand, we can then simply take a cumulative sum to generate the DateGroup.
-- this CTE identifies all new ID records
WITH cte1 AS (
SELECT t.ID, t.EffectiveDate, t.EndDate
FROM
(
SELECT ID, EffectiveDate, EndDate,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY EffectiveDate) rn
FROM yourTable
) t
WHERE t.rn = 1
),
-- this CTE identifies all records whose both effective and end dates
-- do not fall within the range of the start ID record
cte2 AS (
SELECT t1.ID, t1.EffectiveDate, t1.EndDate
FROM yourTable t1
INNER JOIN cte1 t2
ON t1.ID = t2.ID AND
t1.EffectiveDate NOT BETWEEN t2.EffectiveDate AND t2.EndDate AND
t1.EndDate NOT BETWEEN t2.EffectiveDate AND t2.EndDate
),
-- this CTE returns the original table with a new column, amount, which
-- contains a value of 1 should that record cause the DateGroup to be
-- incremented by 1
cte3 AS (
SELECT t1.ID, t1.EffectiveDate, t1.EndDate,
CASE WHEN t2.ID IS NOT NULL OR t3.ID IS NOT NULL THEN 1 ELSE 0 END AS amount
FROM yourTable t1
LEFT JOIN cte1 t2
ON t1.ID = t2.ID AND
t1.EffectiveDate = t2.EffectiveDate AND
t1.EndDate = t2.EndDate
LEFT JOIN cte2 t3
ON t1.ID = t3.ID AND
t1.EffectiveDate = t3.EffectiveDate AND
t1.EndDate = t3.EndDate
)
-- finally, take a cumulative sum of the 'amount' column to generate the DateGroup
SELECT t1.ID,
t1.EffectiveDate,
t1.EndDate,
SUM(t2.amount) AS DateGroup
FROM cte3 t1
INNER JOIN cte3 t2
ON t1.ID >= t2.ID AND
t1.EffectiveDate >= t2.EffectiveDate
GROUP BY t1.id, t1.EffectiveDate, t1.EndDate;
Output:
Demo here:
Rextester
Data used:
CREATE TABLE yourTable (ID int, EffectiveDate datetime, EndDate datetime);
INSERT INTO yourTable
VALUES
(1, '2017-01-01 00:00:00.000', '2017-01-11 00:00:00.000'),
(1, '2017-01-02 00:00:00.000', '2017-01-05 00:00:00.000'),
(1, '2017-01-03 00:00:00.000', '2017-01-12 00:00:00.000'),
(1, '2017-01-06 00:00:00.000', '2017-01-09 00:00:00.000'),
(1, '2017-01-13 00:00:00.000', '2017-01-19 00:00:00.000'),
(2, '2017-02-01 00:00:00.000', '2017-02-11 00:00:00.000'),
(2, '2017-02-06 00:00:00.000', '2017-02-16 00:00:00.000');
What about this? It's simpler that other solutions posted:
WITH
CTE_GetFirstRecordForEachId AS
(
SELECT
id,
EffectiveDate,
Enddate,
rn = ROW_NUMBER() OVER (PARTITION BY id ORDER BY EffectiveDate, EndDate)
FROM
#DataTable
),
CTE_GetOutOfDateRange AS
(
SELECT
a.*,
OutOfDateRange =
CASE WHEN (b.EffectiveDate>=a.EffectiveDate AND b.EffectiveDate<=b.Enddate) OR (b.Enddate>=a.EffectiveDate AND b.Enddate<=b.Enddate)
THEN 0
ELSE 1
END
FROM
#DataTable a
INNER JOIN
CTE_GetFirstRecordForEachId b ON a.id = b.id AND b.rn=1
)
SELECT
id,
Effectivedate,
Enddate,
DateGroup = DENSE_RANK() OVER (ORDER BY id, OutOfDateRange)
FROM
CTE_GetOutOfDateRange
ORDER BY
id, Effectivedate, Enddate
Output:
id Effectivedate Enddate DateGroup
----------- ----------------------- ----------------------- --------------------
1 2017-01-01 00:00:00.000 2017-01-11 00:00:00.000 1
1 2017-01-02 00:00:00.000 2017-01-05 00:00:00.000 1
1 2017-01-03 00:00:00.000 2017-01-12 00:00:00.000 1
1 2017-01-06 00:00:00.000 2017-01-09 00:00:00.000 1
1 2017-01-13 00:00:00.000 2017-01-19 00:00:00.000 2
2 2017-02-01 00:00:00.000 2017-02-11 00:00:00.000 3
2 2017-02-06 00:00:00.000 2017-02-16 00:00:00.000 3
What about this (I am still testing it)
WITH Z AS
(SELECT * FROM (SELECT ID, [EffectiveDate], ENDDate
, LAG(ID) OVER (PARTITION BY ID ORDER BY EffectiveDate, ENDDate Desc) AS ID_Prec
, LAG(EffectiveDate) OVER (PARTITION BY ID ORDER BY EffectiveDate, ENDDate Desc) AS EffDate_Prec
, LAG(ENDDate) OVER (PARTITION BY ID ORDER BY EffectiveDate, ENDDate Desc) AS EndDate_Prec
, ROW_NUMBER() OVER (ORDER BY ID, EffectiveDate,ENDDate DESC) AS RN
, 1 AS DATEGROUP
FROM #DataTable ) C WHERE RN = 1
UNION ALL
SELECT A.ID, A.EffectiveDate, A.Enddate
, A.ID_Prec, A.EffDate_Prec
, A.EndDate_Prec
, A.RN
, CASE WHEN A.ID = A.ID_PREC AND (A.EffectiveDate <=A.EndDate_Prec /* OR A.EndDate>=A.EffDate_Prec*/) THEN Z.DATEGROUP
ELSE Z.DATEGROUP+1 END AS DATEGROUP
FROM (SELECT A.ID, A.EffectiveDate, A.ENDDate
, LAG(A.ID) OVER (PARTITION BY A.ID ORDER BY A.EffectiveDate, A.ENDDate Desc) AS ID_Prec
, LAG(A.EffectiveDate) OVER (PARTITION BY A.ID ORDER BY A.EffectiveDate, A.ENDDate Desc) AS EffDate_Prec
, LAG(A.ENDDate) OVER (PARTITION BY A.ID ORDER BY A.EffectiveDate, A.ENDDate Desc) AS EndDate_Prec
, ROW_NUMBER() OVER (ORDER BY A.ID, A.EffectiveDate,A.ENDDate DESC) AS RN
, 1 AS DATEGROUP
FROM #DataTable A) A
INNER JOIN Z ON A.RN -1= Z.RN
)
SELECT ID, EffectiveDate, Enddate, DATEGROUP FROM Z
Output:
ID EffectiveDate Enddate DATEGROUP
----------- ----------------------- ----------------------- -----------
1 2017-01-01 00:00:00.000 2017-01-11 00:00:00.000 1
1 2017-01-02 00:00:00.000 2017-01-05 00:00:00.000 1
1 2017-01-03 00:00:00.000 2017-01-12 00:00:00.000 1
1 2017-01-06 00:00:00.000 2017-01-09 00:00:00.000 1
1 2017-01-13 00:00:00.000 2017-01-19 00:00:00.000 2
2 2017-02-01 00:00:00.000 2017-02-11 00:00:00.000 3
2 2017-02-06 00:00:00.000 2017-02-16 00:00:00.000 3
guess you are missing some test scenario in your sample date.
;with CTE as
(
select *,ROW_NUMBER()over(order by id, effectivedate)rn
from #DataTable
)
,CTE1 AS
(
select *, 1 New_ID
from cte
where rn=1
union ALL
select c.id,c.effectivedate,c.enddate,c.rn
,case when c.effectivedate between c1.effectivedate
and c1.enddate
and c.id=c1.id then c1.New_ID
else c1.New_ID+1
END
from cte c
inner join cte1 c1
on c.rn=c1.rn+1
and c.rn>1 and c.rn<=7
)
select * from cte1
drop table #DataTable
this may help you. I posted here shortest and simplest version of tsql...
WITH CTE AS (
SELECT *,
ISNULL(LAG(EffectiveDate) OVER (PARTITION BY id ORDER BY id,EffectiveDate,Enddate),EffectiveDate) AS PreviousEffDate,
ISNULL(LAG(Enddate) OVER (PARTITION BY id ORDER BY id,EffectiveDate,Enddate),Enddate) AS PreviousEndDate
FROM #DataTable)
SELECT id,
EffectiveDate,
Enddate,
DENSE_RANK() OVER (ORDER BY id,CASE
WHEN EffectiveDate BETWEEN PreviousEffDate AND PreviousEndDate OR
Enddate BETWEEN PreviousEffDate AND PreviousEndDate OR
PreviousEffDate BETWEEN EffectiveDate AND Enddate OR
PreviousEndDate BETWEEN EffectiveDate AND Enddate
THEN 0
ELSE 1
END) AS DateGroup
FROM CTE
Result:
Have got this one from another forum; altered as per my requirement . looks simple and effective.
WITH C1 AS (
SELECT *,
CASE WHEN EffectiveDate <= MAX(ISnull(EndDate,'9999-12-31 00:00:00.000')) OVER(partition by id ORDER BY EffectiveDate ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) THEN 0 ELSE 1 END AS isstart
FROM #DataTable
)
SELECT ID,EffectiveDate,EndDate,
SUM(isstart) OVER(ORDER BY ID ROWS UNBOUNDED PRECEDING) AS DG
FROM C1
I have two table like
ItemTable_One
id itemID Date qty
===================================================
1 1 2015-07-1 10
2 1 2015-07-3 20
3 2 2015-07-5 30
4 2 2015-07-7 40
ItemTable_Two
id itemID Date qty
===================================================
1 1 2015-07-2 50
2 1 2015-07-4 60
3 3 2015-07-6 70
4 3 2015-07-8 80
I want to retrieve data where itemID is equal to 1 in a date range.
For example ( date range between 2015-07-1 and 2015-07-30 )
itemID Date ItemTableOne_qty ItemTableTwo_qty
============================================================================
1 2015-07-1 10 0
1 2015-07-3 20 0
1 2015-07-2 0 50
1 2015-07-4 0 60
I've tried union join and subquery to do it, but I'm very weak in sql query.
You can use UNION ALL to get all the data you need:
SELECT ItemTable_One.itemID, ItemTable_One.Date, ItemTable_One.qty as ItemTableOne_qty, 0 as ItemTableTwo_qty
FROM ItemTable_One
WHERE ItemTable_One.itemID = 1 AND
ItemTable_One.Date BETWEEN '2015-07-01' AND '2015-08-01'
UNION ALL
SELECT ItemTable_Two.itemID, ItemTable_Two.Date, 0 as ItemTableOne_qty, ItemTable_Two.qty as ItemTableTwo_qty
FROM ItemTable_One
WHERE ItemTable_Two.itemID = 1 AND
ItemTable_Two.Date BETWEEN '2015-07-01' AND '2015-08-01'
You can use a FULL OUTER JOIN:
SELECT COALESCE(t1.itemID, t2.itemID) AS itemID,
COALESCE(t1.[Date], t2.[Date]) AS [Date],
COALESCE(t1.qty, 0) AS ItemTableOne_qty,
COALESCE(t2.qty, 0) AS ItemTableTwo_qty
FROM ItemTable_One AS t1
FULL OUTER JOIN ItemTable_Two AS t2 ON t1.itemID = t2.itemID AND t1.[Date] = t2.[Date]
WHERE COALESCE(t1.itemID, t2.itemID) = 1 AND
COALESCE(t1.[Date], t2.[Date]) BETWEEN '2015-07-01' AND '2015-07-31'
ORDER BY COALESCE(t1.[Date], t2.[Date])
This will put records having the same [Date] value in the source tables, into the same row of the output table.
If records of ItemTable_One always have separate [Date] values from records of ItemTable_Two, then the UNION solution proposed in other answers is preferable.
Demo here
Try this:
select itemID, Date, qty as ItemTableOne_qty, 0 as ItemTableTwo_qty
from ItemTable_One
where ItemID = 1
and date >= '20150701'
and date < '20150731'
union all
select itemID, Date, 0 as ItemTableOne_qty, qty as ItemTableTwo_qty
from ItemTable_Two
where ItemID = 1
and date >= '20150701'
and date < '20150731'
The upper limit for date is intentionally < than the wanted date +1 so that in case it's a datetime that has the time in it, the last day will be included too.
You do it with a FULL JOIN or a UNION ALL (depending on exact required output)
FULL JOIN
A FULL JOIN will allow you to get results like you show as long as there are no identical dates between the 2 tables. If such dates are present in both tables, you'd get a single row per date with both values filled.
The query to use is:
SELECT COALESCE(t1.itemID, t2.itemID) itemID, COALESCE(t1.Date, t2.Date) Date,
ISNULL(t1.qty, 0) ItemTableOne_qty, ISNULL(t2.qty, 0) ItemTableTwo_qty
FROM ItemTable_One t1 FULL JOIN ItemTable_Two t2
ON t1.itemID = t2.itemID AND t1.Date = t2.Date
WHERE COALESCE(t1.itemID, t2.itemID) = 1 AND
COALESCE(t1.Date, t2.Date) BETWEEN '2015-07-01' AND '2015-08-01'
UNION ALL
A UNION ALL will allow you to get resutls like you show and will create duplicate rows where the same date exists in both tables. There will always be at least 1 '0' value in any row.
The query to use is:
SELECT itemID, Date, qty ItemTableOne_qty, 0 ItemTableTwo_qty
FROM ItemTable_One
WHERE itemID = 1 AND Date BETWEEN '2015-07-01' AND '2015-08-01'
UNION ALL
SELECT itemID, Date, 0 ItemTableOne_qty, qty ItemTableTwo_qty
FROM ItemTable_Two
WHERE itemID = 1 AND Date BETWEEN '2015-07-01' AND '2015-08-01'
this will be the other way we can achieve the same result basing on your sample data
declare #ItemTable_One table (id int, itemID int, Date date, qty int)
insert into #ItemTable_One values
(1, 1, '2015-07-1', 10),
(2, 1, '2015-07-3', 20),
(3, 2, '2015-07-5', 30),
(4, 2, '2015-07-7', 40)
declare #ItemTable_Two table (id int, itemID int, Date date, qty int)
insert into #ItemTable_Two values
(1, 1, '2015-07-2', 50),
(2, 1, '2015-07-4', 60) ,
(3, 3, '2015-07-6', 70) ,
(4, 3, '2015-07-8', 80)
;with CTE AS (
select i.itemID As ItemID1,ii.itemID As ItemID2,i.Date As Dated1,ii.Date As Dated2,i.qty as qty,ii.qty As qty1 from #ItemTable_One i
CROSS APPLY (select * from #ItemTable_Two )ii
where i.id = ii.id AND i.itemID = ii.itemID
)
select * from (
Select ItemID1 As item,Dated1 AS Date, qty,'' as qty1 from CTE
UNION
Select ItemID2 As item,Dated2 AS Date,'' as qty,qty1 from CTE)T
--ORDER BY t.qty desc ,t.qty1
Use inline view to simplify horrid sql used in prior answers:
SELECT *
FROM (
SELECT COALESCE(t1.itemID, t2.itemID) AS itemID,
COALESCE(t1.[Date], t2.[Date]) AS [Date],
COALESCE(t1.qty, 0) AS ItemTableOne_qty,
COALESCE(t2.qty, 0) AS ItemTableTwo_qty
FROM ItemTable_One AS t1 FULL OUTER JOIN ItemTable_Two AS t2
ON t1.itemID = t2.itemID AND t1.[Date] = t2.[Date]
) AS v
WHERE v.itemID = 1 AND
v.[Date] BETWEEN '2015-07-01' AND '2015-07-31'
ORDER BY v.[Date]
I have the following data in a table in SQL Server 2008 R2:
ID Code StartDate EndDate
10001 3 2014-07-25 2014-07-28
10001 3 2014-07-29 2014-10-06
10001 3 2014-10-07 2014-10-10
10001 1 2014-10-11 2014-10-31
10001 1 2014-11-01 2014-11-15
10001 3 2014-11-16 2014-11-25
10001 3 2014-11-26 NULL
20002 3 2014-07-25 2014-07-28
20002 3 2014-07-29 2014-10-06
20002 3 2014-10-07 NULL
30003 3 2014-07-25 2014-11-13
30003 3 2014-11-14 2014-11-24
30003 2 2014-11-25 NULL
I want to "collapse" any records with the same Code, and adjacent EndDate and StartDate. The results should be:
ID Code StartDate EndDate
10001 3 2014-07-25 2014-10-10
10001 1 2014-10-11 2014-11-15
10001 3 2014-11-16 NULL
20002 3 2014-07-25 NULL
30003 3 2014-07-25 2014-11-24
30003 2 2014-11-25 NULL
I've been trying to use various sub-queries and the ROW_NUMBER() function, but just can't get it to work. I suspect this would be easily done with a CTE, but I haven't been able to wrap my head around how those work in order to try it here. Any ideas?
Since your ranges are continuous, the problem essentially becomes a gaps-and-islands one.
© Andriy M
Thanks to Steve Ford for the table:
declare #EventLog table
(
Id int,
Code tinyint,
StartDate date,
EndDate date null
)
insert into #EventLog
values
(10001, 3, '2014-07-25', '2014-07-28'),
(10001, 3, '2014-07-29', '2014-10-06'),
(10001, 3, '2014-10-07', '2014-10-10'),
(10001, 1, '2014-10-11', '2014-10-31'),
(10001, 1, '2014-11-01', '2014-11-15'),
(10001, 3, '2014-11-16', '2014-11-25'),
(10001, 3, '2014-11-26', null),
(20002, 3, '2014-07-25', '2014-07-28'),
(20002, 3, '2014-07-29', '2014-10-06'),
(20002, 3, '2014-10-07', null),
(30003, 3, '2014-07-25', '2014-11-13'),
(30003, 3, '2014-11-14', '2014-11-24'),
(30003, 2, '2014-11-25', null);
Thanks Andriy M for the solution:
declare #MaxDate date = '9999-12-31';
with cte as
(
select *,
g = row_number() over (partition by Id order by StartDate)
- row_number() over (partition by Id, Code order by StartDate)
from #EventLog
)
select
Id,
Code,
StartDate = min(StartDate),
EndDate = nullif(max(isnull(EndDate, #MaxDate)), #MaxDate)
from cte
group by
Id, Code, g;
Try this,
CREATE TABLE #TEMP
(
ID INT,
CODE INT,
STARTDATE DATE,
ENDDATE DATE
)
INSERT INTO #TEMP VALUES
(10001,3,'2014-07-25','2014-07-28'),
(10001,3,'2014-07-29','2014-10-06'),
(10001,3,'2014-10-07','2014-10-10'),
(10001,1,'2014-10-11','2014-10-31'),
(10001,1,'2014-11-01','2014-11-15'),
(10001,3,'2014-11-16','2014-11-25'),
(10001,3,'2014-11-26',NULL),
(20002,3,'2014-07-25','2014-07-28'),
(20002,3,'2014-07-29','2014-10-06'),
(20002,3,'2014-10-07',NULL),
(30003,3,'2014-07-25','2014-11-13'),
(30003,3,'2014-11-14','2014-11-24'),
(30003,2,'2014-11-25',NULL)
SELECT T1.ID,T1.CODE,T1.STARTDATE,A.ENDDATE FROM (SELECT L.ID,L.CODE,MIN(STARTDATE) AS STARTDATE,DIFF FROM (SELECT ID,
CODE,
STARTDATE,
ENDDATE,
IsNull(Lag(CODE, 2)
OVER (
ORDER BY ID, STARTDATE, ENDDATE), CODE) AS T_LAG,
CODE - IsNull(Lag(CODE, 2)
OVER (
ORDER BY ID, STARTDATE, ENDDATE), CODE) AS DIFF
FROM #TEMP ) L
GROUP BY L.ID,L.CODE,DIFF) T1
CROSS APPLY(
SELECT ID,CODE,ENDDATE,DIFF FROM (SELECT ID,CODE,ENDDATE,DIFF,ROW_NUMBER() OVER (PARTITION BY ID,CODE,DIFF ORDER BY ID,CODE,STARTDATE DESC,ENDDATE DESC) AS T_R FROM (SELECT ID,
CODE,
STARTDATE,
ENDDATE,
IsNull(Lag(CODE, 2)
OVER (
ORDER BY ID, STARTDATE, ENDDATE), CODE) AS T_LAG,
CODE - IsNull(Lag(CODE, 2)
OVER (
ORDER BY ID, STARTDATE, ENDDATE), CODE) AS DIFF
FROM #TEMP ) A) A
WHERE T_R=1 AND ID=T1.ID AND CODE=T1.CODE AND DIFF=T1.DIFF)A
ORDER BY T1.ID,T1.STARTDATE
Try this (I'm replicating SQL 2012 Lead And Lag functionality using Row_Number() in 2008):
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE EventLog
(
ID Int,
Code tinyint,
StartDate Date,
EndDate Date Null
)
INSERT INTO EventLog
Values
(10001, 3, '2014-07-25', '2014-07-28'),
(10001, 3, '2014-07-29', '2014-10-06'),
(10001, 3, '2014-10-07', '2014-10-10'),
(10001, 1, '2014-10-11', '2014-10-31'),
(10001, 1, '2014-11-01', '2014-11-15'),
(10001, 3, '2014-11-16', '2014-11-25'),
(10001, 3, '2014-11-26', NULL),
(20002, 3, '2014-07-25', '2014-07-28'),
(20002, 3, '2014-07-29', '2014-10-06'),
(20002, 3, '2014-10-07', NULL),
(30003, 3, '2014-07-25', '2014-11-13'),
(30003, 3, '2014-11-14', '2014-11-24'),
(30003, 2, '2014-11-25', NULL)
Query 1:
WITH CTE
AS
(
SELECT ID,
Code,
StartDate,
EndDate,
ROW_NUMBER() OVER (PARTITION BY Id, Code ORDER BY Id, Code, StartDate) As RN
FROM EventLog
),
CTE2
AS
(
SELECT CTE.Id, CTE.Code, CTE.StartDate, CTE.EndDate,
CASE WHEN DATEDIFF(d, LAG.EndDate, CTE.StartDate) = 1
THEN Lag.EndDate
ELSE NULL
END AS PrevEndDate,
CASE WHEN DateDiff(d, LEAD.StartDate, CTE.EndDate) = -1
THEN Lead.StartDate
ELSE NULL
END As NextStartDate
FROM CTE
LEFT OUTER JOIN CTE AS Lag
ON CTE.ID = Lag.ID AND CTE.Code = Lag.Code AND Lag.Rn = CTE.RN - 1
LEFT OUTER JOIN CTE AS Lead
ON CTE.ID = Lead.ID AND CTE.Code = Lead.Code AND Lead.Rn = CTE.RN + 1
),
StartAndEnd
As
(
SELECT ID,
Code,
StartDate,
EndDate,
PrevEndDate,
NextStartDate,
ROW_NUMBER() OVER (PARTITION BY Id, Code ORDER BY ID, Code, StartDate) As RN
FROM CTE2
WHERE (PrevEndDate IS NULL Or NextStartDate IS NULL)
)
SELECT S.ID, s.Code, S.StartDate, E.EndDate
FROM StartAndEnd as S
LEFT JOIN StartAndEnd E
ON S.ID = E.ID AND S.Code = E.Code AND E.RN = S.Rn + 1
WHERE S.PrevEndDate Is Null
ORDER By s.Id, S.StartDate
Results:
| ID | CODE | STARTDATE | ENDDATE |
|-------|------|------------|------------|
| 10001 | 3 | 2014-07-25 | 2014-10-10 |
| 10001 | 1 | 2014-10-11 | 2014-11-15 |
| 10001 | 3 | 2014-11-16 | (null) |
| 20002 | 3 | 2014-07-25 | (null) |
| 30003 | 3 | 2014-07-25 | 2014-11-24 |
| 30003 | 2 | 2014-11-25 | (null) |
;WITH StartDates
AS(
Select e1.ID, e1.Code, e1.StartDate, ROW_NUMBER() OVER (Order By e1.ID asc) as RowNumber
From #EventLog e1
LEFT JOIN #EventLog e2 ON e1.Code = e2.Code and e2.EndDate = DATEADD(day,-1,e1.StartDate)
WHERE e2.Id is null
),
EndDates as(
Select e1.ID, e1.Code, e1.EndDate, ROW_NUMBER() OVER (Order by e1.ID asc) as RowNumber
FROM #EventLog e1
LEFT JOIN #EventLog e2 ON e1.Code = e2.Code and e2.StartDate = DATEADD(day,1,e1.EndDate)
WHERE e2.Id is null
)
Select s.ID, s.Code, s.StartDate, e.EndDate
FROM StartDates s
JOIN EndDates e ON s.Code = e.Code and s.RowNumber = e.RowNumber
I have 2 queries which return counts of different information in a table:
SELECT Date, COUNT(*) AS Total
FROM Table
WHERE Type = 7 AND Date >= '2010-01-01'
GROUP BY Date
HAVING COUNT(*) > 5000
ORDER BY Date
which returns the totals for all of the 'busy' dates:
Date Total
---------- -----------
2010-01-05 9466
2010-02-02 8747
2010-03-02 9010
2010-04-06 7916
2010-05-05 9342
2010-06-02 8723
2010-07-02 7829
2010-08-03 8411
2010-09-02 7687
2010-10-04 7706
2010-11-02 8567
2010-12-02 7645
and
SELECT Date, COUNT(*) AS Failures
FROM Table
WHERE Type = 7 AND ErrorCode = -2 AND Date >= '2010-01-01'
GROUP BY Date
ORDER BY Date
which returns the total failures (all of which happened on busy dates):
Date Failures
---------- -----------
2010-09-02 29
2010-10-04 16
2010-11-02 8
Is it possible to combine these into a single query to return one result?
E.g.:
Date Total Failures
---------- ----------- -----------
2010-01-05 9466
2010-02-02 8747
2010-03-02 9010
2010-04-06 7916
2010-05-05 9342
2010-06-02 8723
2010-07-02 7829
2010-08-03 8411
2010-09-02 7687 29
2010-10-04 7706 16
2010-11-02 8567 8
2010-12-02 7645
;With baseData As
(
SELECT
Date,
COUNT(*) AS Total,
COUNT(CASE WHEN ErrorCode = -2 THEN 1 END) AS Failures
FROM Table
WHERE Type = 7 AND Date >= '2010-01-01'
GROUP BY Date
)
SELECT
Date,
Total,
Failures,
CAST(Failures AS float)/Total AS Ratio
FROM baseData
WHERE Total > 5000 OR Failures > 0
ORDER BY Date
If you can refactor to the same where clause, this should be possible.
I haven't taken your HAVING(Count()) into consideration
SELECT [Date], COUNT(*) AS Total, SUM(CASE WHEN ErrorCode = -2 THEN 1 ELSE 0 END) AS Failures
FROM [Table]
WHERE [Type] = 7 AND [Date] >= '2010-01-01'
GROUP BY [Date]
ORDER BY [Date]
Edit : Here is some test data
create table [Table]
(
[ErrorCode] int,
[Type] int,
[Date] datetime
)
insert into [table]([Date], [Type], [ErrorCode] )values ('1 Jan 2010', 7, 0)
insert into [table]([Date], [Type], [ErrorCode] )values ('1 Jan 2010', 7, -2)
insert into [table]([Date], [Type], [ErrorCode] )values ('2 Jan 2010', 7, -2)
insert into [table]([Date], [Type], [ErrorCode] )values ('2 Jan 2010', 8, -2)
insert into [table]([Date], [Type], [ErrorCode] )values ('2 Jan 2010', 7, 1)
yes you should be able to do a UNION ALL between the 2 tables