I wish I could correlate an "inline view" - sql

I have a Patient table:
PatientId Admitted
--------- ---------------
1 d/m/yy hh:mm:ss
2 d/m/yy hh:mm:ss
3 d/m/yy hh:mm:ss
I have a PatientMeasurement table (0 to many):
PatientId MeasurementId Recorded Value
--------- ------------- --------------- -----
1 A d/h/yy hh:mm:ss 100
1 A d/h/yy hh:mm:ss 200
1 A d/h/yy hh:mm:ss 300
2 A d/h/yy hh:mm:ss 10
2 A d/h/yy hh:mm:ss 20
1 B d/h/yy hh:mm:ss 1
1 B d/h/yy hh:mm:ss 2
I am trying to create a result set that resembles:
PatientId Numerator Denominator
--------- -------- -----------
1 1 1
2 1 1
3 0 1
Essentially, a patient will have a 1 in the numerator if the have at least one value for measurement A and one value for measurement B. In this example, patient 1 has 3 A measurements and 2 B measures, so the numerator is 1. Patient 2 has 2 A measurements, but no B measurements, so the numerator is 0. Patient has neither an A measurement nor a B measurement, so the numerator is 0.
My query thus far is:
SELECT PatientId, CASE WHEN a.cnt+b.cnt>2 THEN 1 ELSE 0 END Numerator, 1 Denominator
FROM patient p
LEFT OUTER JOIN (
SELECT PatientId, count(*) cnt
FROM PatientMeasurement pm
WHERE MeasurementId='A'
--AND Recorded <= dateadd(hh, 12, Admitted)
GROUP BY PatientId
) a ON p.PatientId=a.PatientId
LEFT OUTER JOIN (
SELECT PatientId, count(*) cnt
FROM PatientMeasurement pm
WHERE MeasurementId='B'
--AND Recorded <= dateadd(hh, 12, Admitted)
GROUP BY PatientId
) b ON p.PatientId=b.PatientId
This works as expected as long as I don't include the correlated, date restriction (Recorded < dateadd(hh, 12, Admitted). Unfortunately, correlating an 'inline view' in this manner is not syntactically valid.
This has forced me to re-write the SQL to:
SELECT PatientId, CASE WHEN v.a+v.b>2 THEN 1 ELSE 0 END Numerator, 1 Denominator
FROM (
SELECT PatientId,
(
SELECT PatientId, count(*) cnt
FROM PatientMeasurement pm
WHERE PatientId=p.PatientId
AND MeasurementId='A'
AND Recorded <= dateadd(hh, 12, Admitted)
GROUP BY PatientId
) a,
(
SELECT PatientId, count(*) cnt
FROM PatientMeasurement pm
WHERE PatientId=p.PatientId
AND MeasurementId='B'
AND Recorded <= dateadd(hh, 12, Admitted)
GROUP BY PatientId
) b
FROM Patient p
) v
My question: Is there a better, more-efficient way to do this?
Thanks for your time.

Try this :
WITH GroupPatients AS
(SELECT MeasurementID, PatientId, Count(*) AS cnt
FROM PatientMeasurement AS pm
INNER JOIN Patient p ON pm.PatientID = p.PatientID
WHERE
MeasurementId IN ('A', 'B')
AND
Recorded <= dateadd(hh, 12, Admitted)
GROUP BY MeasureMentID, PatientId)
SELECT p.PatientID, Case
When IsNull(GPA.cnt, 0) > 0 AND IsNull(GPB.cnt, 0) > 0 Then 1
Else 0
End AS Numerator, 1 AS Denominator
FROM Patient p
LEFT JOIN GroupPatientsA AS GPA ON p.PatientID = GPA.PatientID AND GPA.MeasurementID = 'A'
LEFT JOIN GroupPatientsB AS GPB ON p.PatientID = GPB.PatientID AND GPB.MeasurementID = 'B'
I've made one tweak to the business logic too - your spec says Numerator should be one if a patient has both A and B measurements - however, your clause of a.cnt+b.cnt>2 will erroneously return one if either a.cnt or b.cnt are 3 or more and the other is zero.

Another solution can be close to your original attempt using OUTER APPLY:
SELECT PatientId, CASE WHEN a.cnt+b.cnt>2 THEN 1 ELSE 0 END Numerator, 1 Denominator
FROM patient p
OUTER APPLY (
SELECT count(*) cnt
FROM PatientMeasurement pm
WHERE MeasurementId='A'
AND Recorded <= dateadd(hh, 12, p.Admitted)
AND pm.PatientId = p.PatientId
) AS a(cnt)
OUTER APPLY (
SELECT count(*) cnt
FROM PatientMeasurement pm
WHERE MeasurementId='B'
AND Recorded <= dateadd(hh, 12, p.Admitted)
AND pm.PatientId = p.PatientId
) AS b(cnt)

SELECT p.*,
CASE WHEN
EXISTS
(
SELECT NULL
FROM PatientMeasurement pm
WHERE pm.PatientID = p.ID
AND pm.Type = 'A'
AND pm.Recorded <= DATEADD(hh, 12, p.Admitted)
) AND EXISTS (
SELECT NULL
FROM PatientMeasurement pm
WHERE pm.PatientID = p.ID
AND pm.Type = 'B'
AND pm.Recorded <= DATEADD(hh, 12, p.Admitted)
) THEN 1 ELSE 0 END
FROM Patient p

Assuming you are using Sql 2005 or 2008, the entire query can be simplified using some window functions and a pivot:
with pData as
(
select count(*) over(partition by PatientId, MeasurementId) as cnt,
PatientId, MeasurementId
from PatientMeasurement pm
where MeasurementId in('A','B')
and Recorded <= dateadd(hh, 12, Admitted)
)
select PatientId, coalesce([A],0) as cntA, coalesce([B],0) as cntB,
case when coalesce([A],0) + coalesce([B],0) > 2 then 1 else 0 end as Numerator,
1 as Denominator
from pData
pivot (max(cnt) for MeasurementId in([A],[B])) pvt

DECLARE #TimeSlot int;
SET #TimeSlot = 12;
WITH
pt AS (
SELECT p.PatientID, p.Admitted, m.MeasurementID, m.Recorded,
CASE
WHEN m.Recorded <= dateadd(hh, #TimeSlot, p.Admitted) THEN 1
ELSE 0
END AS "InTimeSlot"
FROM Patient AS p
LEFT JOIN PatientMeasurement AS m ON p.PatientID = m.PatientID
),
cntA AS (
SELECT PatientID, count(*) AS "A_count"
FROM pt WHERE MeasurementID='A' AND InTimeSlot = 1
GROUP BY PatientID
),
cntB AS (
SELECT PatientID, count(*) AS "B_count"
FROM pt WHERE MeasurementID='B' AND InTimeSlot = 1
GROUP BY PatientID
),
cntAB AS (
SELECT p.PatientID
,coalesce(a.A_count, 0) AS "A_cnt"
,coalesce(b.B_count, 0) AS "B_cnt"
FROM Patient as p
LEFT JOIN cntA AS a ON p.PatientID = a.PatientID
LEFT JOIN cntB AS b ON p.PatientID = b.PatientID
),
cntN AS (
SELECT PatientID,
CASE WHEN A_cnt > 0 AND B_cnt > 0 THEN 1 ELSE 0 END AS Numerator
FROM cntAB
)
SELECT PatientID, Numerator, 1 AS Denominator FROM cntN

Related

I need to handle overlapping dates but if the end date is null then it to be assumed that the process has not stopped

I have a start date and end date of a process from two different sources.these two sources will be merged and the dates needs to be handled in case of conflicts
Dataset1
P_startDate P_EndDate
1-Jan-07 1-Jun-15
Dataset2
P_Start Date P_End Date
1-Mar-15 1-Jan-17
2-Jan-17 Null
Merged Dataset / Expected Dataset
| Process Start Date | Process End Date |
| 1-Jan-07 | 1-Mar-15 |
| 1-Mar-15 | 1-Jan-17 |
| 2-Jan-17 | Null |
I did create a code but that is giving me a result where the null (no end date) condition is not considered and my out put comes as
| Process Start Date | Process End Date |
| 1-Jan-07 | 1-Mar-15 |
| 1-Mar-15 | 1-Jan-17 |
| 1-Jan-17 | 2-Jan-17 |
I have followed the guidelines from here
http://www.schemamania.org/sql/#overlapping.dates
with D (ID, bound) as (
select ID
, case T when 's' then StartDate else EndDate end as bound
from (
select ID, StartDate, EndDate from so.A
UNION
select ID, StartDate, EndDate from so.B
) as U
cross join (select 's' as T union select 'e') as T
)
select P.*
from (
select s.ID, s.bound as StartDate, min(e.bound) as EndDate
from D as s join D as e
on s.ID = e.ID
and s.bound < e.bound
group by s.ID, s.bound
) as P
left join so.A as a
on P.ID = a.ID
and a.StartDate <= P.StartDate and P.EndDate <= a.EndDate
left join so.B as b
on P.ID = b.ID
and b.StartDate <= P.StartDate and P.EndDate <= b.EndDate
order by P.ID, P.StartDate, P.EndDate
This looks more like a merge overlapping interval problem. Here is one solution that keeps a running count of starts and ends:
CREATE TABLE ds1 (P_STARTDATE DATE, P_ENDDATE DATE);
CREATE TABLE ds2 (P_STARTDATE DATE, P_ENDDATE DATE);
INSERT INTO ds1 VALUES
('2007-01-01', '2015-06-01');
INSERT INTO ds2 VALUES
('2015-03-01', '2017-01-01'),
('2017-01-02', NULL);
WITH cte1(d, v) AS (
SELECT P_startDate, +1 FROM ds1 UNION ALL
SELECT P_EndDate, -1 FROM ds1 UNION ALL
SELECT P_startDate, +1 FROM ds2 UNION ALL
SELECT P_EndDate, -1 FROM ds2
), cte2(d, c) AS (
SELECT d, SUM(SUM(v)) OVER (ORDER BY CASE WHEN d IS NULL THEN 2 ELSE 1 END, d)
FROM cte1
GROUP BY d
), cte3(d, c, f) AS (
SELECT d, c, CASE WHEN LAG(c) OVER (ORDER BY CASE WHEN d IS NULL THEN 2 ELSE 1 END, d) > 0 THEN 0 ELSE 1 END
FROM cte2
), cte4(d, c, g) AS (
SELECT d, c, SUM(f) OVER (ORDER BY CASE WHEN d IS NULL THEN 2 ELSE 1 END, d)
FROM cte3
)
SELECT MIN(d) AS FromDate, CASE WHEN COUNT(d) = COUNT(*) THEN MAX(d) END AS ToDate
FROM cte4
GROUP BY g;
Result:
FromDate ToDate
01/01/2007 00:00:00 01/01/2017 00:00:00
02/01/2017 00:00:00 02/01/2017 00:00:00

T-SQL Case when with Using with Sql Two Column

First of all I would like to thank the friends who helped this complex and difficult query.
I have three tables
Table 1
StaffId FirstName LastName staffType
---------------------------------------
1 Adam Sorme Student
2 Lara Sandra Teacher
3 Jack Jones Student
Table 2
GateId GateName
---------------------------------------
1 frontDoor
2 superDoor
Table 3
Id transitionDate GateId StaffId
---------------------------------------
1 2018-01-1 08:00:00 1 1
2 2018-01-1 10:00:00 2 1
3 2018-01-1 20:00:00 2 1
4 2018-01-2 07:00:00 1 2
5 2018-01-2 10:00:00 1 3
6 2018-01-9 12:00:00 2 2
I want the first and last movements of students for each day. Value must be set to null if no movement is available between the specified dates
transitionDate> '2018-01-1 00:00:00 000'
and transitionDate< '2018-01-03 00:00:00 000'
OUTPUT:
Id Date MinTransitionDate MaxTransitionDate FirstGateName LastGateName StaffId StaffType
1 2018-01-01 2018-01-1 08:00:00 2018-01-1 20:00:00 frontDoor superDoor 1 Student
2 2018-01-01 null null null null 3 student
3 2018-01-02 null null null null 1 student
4 2018-01-02 2018-01-2 10:00:00 null frontDoor null 3 student
The following query is partially working.
select
q.*,
g1.GateName as first_gate_name,
g2.GateName as last_gate_name
from
(
select s.staffId, d.dte,
min(t.transitionDate) as min_Date,
max_Date= case when count(1)>1 then max(t.transitionDate) else null end,
max(case when seqnum_asc = 1 then gateId end) as first_gateid,
max(case when seqnum_desc = 1 then gateId end) as last_gateid
from (select s.* from Staff s where stafftype = 'Student') s cross join
(select distinct cast(transitionDate as date) as dte from Transitions) d left join
(select t.*,
row_number() over (partition by StaffId, cast(transitionDate as date) order by transitionDate) as seqnum_asc,
row_number() over (partition by StaffId, cast(transitionDate as date) order by transitionDate desc) as seqnum_desc
from Transitions t
) t
on cast(t.transitiondate as date) = d.dte and
t.staffId = s.staffId and
1 in (t.seqnum_asc, t.seqnum_desc)
group by s.staffId, d.dte
) q
left join Gates g1 on g1.gateId = q.first_gateid
left join Gates g2 on g2.gateId = q.last_gateid
see working demo
Problem : max_date in 4. row is empty. I want the value of last_gateid to be null. Can you help me?
screenshot 4. row
https://cdn.pbrd.co/images/H7vyu31.png
A quick fix would be replacing this line
max(case when seqnum_desc = 1 then gateId end) as last_gateid
with
max(case when (seqnum_desc = 1 and seqnum_asc != 1) then t.gateId end) as last_gateid
It is obvious that some one helped you write the query and you gave him more explanation of the problem , what i did is tweaking the query you provided and used the same logic to give you the desired output although i would prefer to rewrite it in a more understandable way :
select
q.*,
g1.GateName as first_gate_name,
g2.GateName as last_gate_name
from
(
select s.staffId, d.dte,
min(t.transitionDate) as min_Date,
max_Date= case when count(1)>1 then max(t.transitionDate) else null end,
max(case when seqnum_asc = 1 then gateId end) as first_gateid,
case when count(1)>1 then MAX(gateId) end as last_gateid
from (select s.* from Staff s where stafftype = 'Student') s cross join
(select distinct cast(transitionDate as date) as dte from Transitions) d left join
(select t.*,
row_number() over (partition by StaffId, cast(transitionDate as date) order by transitionDate) as seqnum_asc,
row_number() over (partition by StaffId, cast(transitionDate as date) order by transitionDate desc) as seqnum_desc
from Transitions t
) t
on cast(t.transitiondate as date) = d.dte and
t.staffId = s.staffId and
1 in (t.seqnum_asc, t.seqnum_desc)
group by s.staffId, d.dte
) q
left join Gates g1 on g1.gateId = q.first_gateid
left join Gates g2 on g2.gateId = q.last_gateid
IF OBJECT_ID('dbo.Staff') IS NOT NULL DROP TABLE Staff
create table Staff (StaffId int, FirstName varchar(20), LastName
varchar(20), staffType varchar(20))
insert into Staff values
(1, 'Adam', 'Sorme', 'Student'),
(2, 'Lara', 'Sandra', 'Teacher'),
(3, 'Jack', 'Jones', 'Student')
IF OBJECT_ID('dbo.Gates') IS NOT NULL DROP TABLE Gates
create table Gates (GateId int, GateName varchar(20))
insert into Gates values
(1, 'frontDoor'),
(2, 'backDoor')
IF OBJECT_ID('dbo.Transitions') IS NOT NULL DROP TABLE Transitions
create table Transitions (Id int, transitionDate datetime, GateId int,
StaffId int)
insert into Transitions values
(1, '2018-01-1 08:00:00', 1, 1),
(2, '2018-01-1 10:00:00', 2, 1),
(3, '2018-01-1 20:00:00', 2, 1),
(4, '2018-01-2 07:00:00', 1, 2),
(5, '2018-01-2 10:00:00', 2, 3),
(6, '2018-01-9 12:00:00', 2, 2)
--select * from Transitions
DECLARE #Datefrom DATETIME = '2018-01-01'
DECLARE #DateTo DATETIME = '2018-01-03'
----1. If the transition table itself is not reliable meaning there
--could be imports and the maxID could be for a lesser datetime
IF OBJECT_ID('tempdb..#Transitions') IS NOT NULL DROP TABLE
#Transitions
SELECT *, [GateSortID] = RANK() OVER (PARTITION BY StaffID ORDER BY
StaffID,GateID,TransitionDate)
INTO #Transitions
FROM Transitions
----2.Based on above temp table get first and last dates
IF OBJECT_ID('tempdb..#FirstandLastDates') IS NOT NULL DROP TABLE
#FirstandLastDates
SELECT T.StaffId,
[FirstDate] = FirstDate.transitionDate,
[LastDate] = LastDate.transitionDate
INTO #FirstandLastDates
FROM #Transitions T
JOIN
(
SELECT TT.StaffId,
[MinGateSortID] = MIN(GateSortID),
TT.GateID,
G.transitionDate
FROM #Transitions TT
JOIN Transitions G
ON TT.StaffId = G.StaffId
AND TT.Id = G.ID
AND TT.GateId = G.GateId
WHERE GateSortID = 1-- MIN VALUE WILL ALWAYS BE 1
GROUP BY TT.StaffID,TT.GateID,G.transitionDate
)FirstDate --lol!!
ON T.StaffID= FirstDate.StaffID
LEFT JOIN
(
SELECT TT.StaffId, TT.GateId, G.TransitionDate
FROM #Transitions TT
JOIN #Transitions G
ON TT.StaffId = G.StaffId
AND TT.Id = G.ID
AND TT.GateId = G.GateId
JOIN
(
SELECT StaffID,
[MaxGateSortID] = MAX(GateSortID)
FROM #Transitions TT
WHERE GateSortID <> 1 -- SO THAT IF THE PERSON HAS NOT --CLOCKED OUT YET ONLY CLOCKED IN DO NOT CONSIDER THAT THE
--GATES ARE SAME
GROUP BY StaffID
) LastGate1
ON TT.StaffId = LastGate1.StaffId
AND TT.GateSortID = LastGate1.MaxGateSortID
)LastDate
ON T.StaffId = LastDate.StaffId
GROUP BY T.StaffId,FirstDate.transitionDate,LastDate.transitionDate
----3.Based on above temp table get first and last transition gates
IF OBJECT_ID('tempdb..#FirstandLastGates') IS NOT NULL DROP TABLE
#FirstandLastGates
SELECT T.StaffId,
[FirstGate] = FirstGate.GateName,
[LastGate] = LastGate.GateName
INTO #FirstandLastGates
FROM #Transitions T
JOIN
(
SELECT StaffID,
[MinGateSortID] = MIN(GateSortID),
TT.GateID,
GateName
FROM #Transitions TT
JOIN Gates G
ON TT.GateId = G.GateId
WHERE GateSortID = 1-- MIN VALUE WILL ALWAYS BE 1
--AND TT.transitionDate BETWEEN #Datefrom AND #DateTo
GROUP BY StaffID,TT.GateID,GateName
)FirstGate
ON T.StaffID= FirstGate.StaffID
LEFT JOIN
(
SELECT TT.StaffId, TT.GateId, G.GateName
FROM #Transitions TT
JOIN Gates G
ON TT.GateId = G.GateId
JOIN
(
SELECT StaffID,
[MaxGateSortID] = MAX(GateSortID)
FROM #Transitions TT
WHERE GateSortID <> 1 -- SO THAT IF THE PERSON HAS NOT CLOCKED
-- OUT YET ONLY CLOCKED IN DO NOT CONSIDER THAT THE GATES ARE --SAME
--AND TT.transitionDate BETWEEN #Datefrom AND #DateTo
GROUP BY StaffID
) LastGate1
ON TT.StaffId = LastGate1.StaffId
AND TT.GateSortID = LastGate1.MaxGateSortID
)LastGate
ON T.StaffId = LastGate.StaffId
GROUP BY T.StaffId,FirstGate.GateName,LastGate.GateName
--MAIN OUTPUT
SELECT DISTINCT --t.Id,
--t.transitionDate,
[MinTransitionDate] = D.FirstDate,
[MaxTransitionDate] = D.LastDate,
[FirstGateName] = G.FirstGate,
[LastGateName] = G.LastGate,
T.[StaffId],
S.StaffType
from Staff S
JOIN #Transitions T
ON S.StaffId = T.StaffId
JOIN #FirstandLastDates D
ON T.StaffId = D.StaffId
JOIN #FirstandLastGates G
ON T.StaffId = G.StaffId
GROUP BY t.Id, D.FirstDate, D.LastDate, G.FirstGate,G.LastGate,T.[StaffId],
S.StaffType

Check consecutive values and update field

I have a table here :
In the "week No"column ,
I have data for 5 consecutive weeks,every week I want to check these conditions-
1.Every week I will check the current week's points.If the points value is -10 for the current week and the previous week's points is also -10 then -40 is added in table 2(below table) in totalpoints field.
2.Again we check it for current week and prevoius two week's points.If the score is -10 for three weeks consecutively the reward -100 for that person and added to total points field in table 2.(below table)
3.
Similarly for consecutive four weeks i.e current week and the previous 3 week's if points is -10 ,then add -200 to table2's total points field.
Can anyone help me on how to achieve this . M using sql server .
Here it is
; WITH
u AS (SELECT * from (values (1,'Bob'),(2,'Deepak'),(3,'Brinda'),(4,'Chriss'),(4,'Chriss')) as d (usr,name)),
N AS (SELECT * from (values (1,1,-10),(1,2,-10),(1,3,-10),(1,4,-10),(1,5,-10),(2,1,50),(2,2,40),(2,3,30),(2,4,-10),(2,5,-10),(3,1,60),(3,2,20),(3,3,40),(3,4,-10),(3,5,20),(4,1,-10),(4,2,90),(4,3,50),(4,4,30),(4,5,50)) as d (usr,wk,pt)),
sc as (
select w.*, case w.pt when -10 then (case w1.pt when -10 then (case w2.pt when -10 then (case w3.pt when -10 then -200 else -100 end) else -40 end) else 0 end) else 0 end x
from n w
left join n w1 on w.usr = w1.usr and w.wk = w1.wk+1
left join n w2 on w.usr = w2.usr and w.wk = w2.wk+2
left join n w3 on w.usr = w3.usr and w.wk = w3.wk+3
),
l as (
select *, pt+x as total
from sc
),
s as (
select usr, sum(total) total
from l
group by usr
)
select u.*,t.*
from s t
inner join u on u.usr = t.usr
If you want to see all details change final select in
select u.*,t.*
from l t
inner join u on u.usr = t.usr
in your database should be something like:
; WITH
n as (
select [EmployeeName], CAST(SUBSTRING([Week No.],5,10) AS INT) as wk, Points as pt
from YourTable --> Change this to your table
),
sc as (
select w.*, case w.pt when -10 then (case w1.pt when -10 then (case w2.pt when -10 then (case w3.pt when -10 then -200 else -100 end) else -40 end) else 0 end) else 0 end x
from n w
left join n w1 on w.[EmployeeName] = w1.[EmployeeName] and w.wk = w1.wk+1
left join n w2 on w.[EmployeeName] = w2.[EmployeeName] and w.wk = w2.wk+2
left join n w3 on w.[EmployeeName] = w3.[EmployeeName] and w.wk = w3.wk+3
),
l as (
select *, pt+x as total
from sc
),
s as (
select [EmployeeName], sum(total) total
from l
group by [EmployeeName]
)
select *
from s

SQL query - sum of values by status for date interval

I get crazy because of one query. I have a table like following and I want to get a data - Summa of Values by Status For every Date in interval.
Table
Id Name Value Date Status
1 pro1 2 01.04.14 0
2 pro1 8 02.04.14 1
3 pro2 6 02.04.14 1
4 pro3 0 03.04.14 0
5 pro4 7 03.04.14 0
6 pro4 2 03.04.14 0
7 pro4 4 03.04.14 1
8 pro4 6 04.04.14 1
9 pro4 1 04.04.14 1
For example,
Input: Name = pro4, minDate = 01.02.14, maxDate = 04.09.14
Output:
Date Values sum for 0 Status Values sum for 1 Status
01.04.14 0 0
02.04.14 0 0
03.04.14 9 (=7+2) 4 (only 4 exist)
04.04.14 0 7 (6+1)
In 01.02.14 and 02.04.14 dates, pro4 has not values by status, but I want to show that rows, because I need all dates in that interval. Can anyone help me to create this query?
Edit:
I can not change structure, I have already that table with data. Every day exist in table many times (minimum 1 time)
Thanks in advance.
Assuming you have a row for each date in the table, use conditional aggregation:
select date,
sum(Case when name = 'pro4' and status = 0 then Value else 0 end) as values_0,
sum(case when name = 'pro4' and status = 1 then Value else 0 end) as values_1
from Table t
where date >= '2014-04-01' and date <= '2014-04-09'
group by date
order by date;
If you don't have this list of dates, you can take this approach instead:
with dates as (
select cast('2014-04-01' as date) as thedate
union all
select dateadd(day, 1, thedate)
from dates
where thedate < '2014-04-09'
)
select dates.thedate,
sum(Case when status = 0 then Value else 0 end) as values_0,
sum(case when status = 1 then Value else 0 end) as values_1
from dates left outer join
table t
on t.date = dates.thedate and t.name = 'pro4'
group by dates.thedate;
just an assumption query :
select Distinct date ,case when status = 0 and MAX(date) then SUM(value) ELSE 0 END Status0 ,
case when status = 1 and MAX(date) then SUM(value) ELSE 0 END Status1 from table
To expand my comment the complete query is
WITH [counter](N) AS
(SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1)
, days(N) AS (
SELECT row_number() over (ORDER BY (SELECT NULL)) FROM [counter])
, months (N) AS (
SELECT N - 1 FROM days WHERE N < 13)
, calendar ([date]) AS (
SELECT DISTINCT cast(dateadd(DAY, days.n
, dateadd(MONTH, months.n, '20131231')) AS date)
FROM months
CROSS JOIN days
)
SELECT a.Name
, c.Date
, [Sum of 0] = SUM(CASE Status WHEN 0 THEN Value ELSE 0 END)
, [Sum of 1] = SUM(CASE Status WHEN 1 THEN Value ELSE 0 END)
FROM Calendar c
LEFT JOIN myTable a ON c.Date = a.Date AND a.name = 'pro4'
WHERE c.date BETWEEN '20140201' AND '20140904'
GROUP BY c.Date, a.Name
ORDER BY c.Date
Note that the condition on the name need to be in the JOIN, otherwise you'll get only the date of your table.
If you need multiple years just add another CTE for the count and a dateadd(YEAR,...) in the CTE calendar
This is not really the exact query, but I think you can get that by having a query that looks like:
select date, status, sum(value) from table
where (date between mindate and maxdate) and name = product_name
group by date, status;
this page gives more info.
EDIT
So the above query only gives a part of the answer required by the OP. A LEFT OUTER JOIN of the original table and the result of the above query on thedate and status fields will give the missing info.
e.g.
select x.date, x.status, x.sum_of_values from table as y
left outer join
(select date, status, sum(value) as sum_of_values
from table
where (date between mindate and maxdate) and name = product_name
group by date, status) as x
on y.date= x.date and y.status = x.status
order by x.date;

Creating groups of consecutive days meeting a given criteria

I have table the following data structure in SQL Server:
ID Date Allocation
1, 2012-01-01, 0
2, 2012-01-02, 2
3, 2012-01-03, 0
4, 2012-01-04, 0
5, 2012-01-05, 0
6, 2012-01-06, 5
etc.
What I need to do is get all consecutive day periods where Allocation = 0, and in the following form:
Start Date End Date DayCount
2012-01-01 2012-01-01 1
2012-01-03 2012-01-05 3
etc.
Is it possible to do this in SQL, and if so how?
In this answer, I'll assume that the "id" field numbers the rows consecutively when sorted by increasing date, like it does in the example data. (Such a column can be created if it does not exist).
This is an example of a technique described here and here.
1) Join the table to itself on adjacent "id" values. This pairs adjacent rows. Select rows where the "allocation" field has changed. Store the result in a temporary table, also keeping a running index.
SET #idx = 0;
CREATE TEMPORARY TABLE boundaries
SELECT
(#idx := #idx + 1) AS idx,
a1.date AS prev_end,
a2.date AS next_start,
a1.allocation as allocation
FROM allocations a1
JOIN allocations a2
ON (a2.id = a1.id + 1)
WHERE a1.allocation != a2.allocation;
This gives you a table having "the end of the previous period", "the start of the next period", and "the value of 'allocation' in the previous period" in each row:
+------+------------+------------+------------+
| idx | prev_end | next_start | allocation |
+------+------------+------------+------------+
| 1 | 2012-01-01 | 2012-01-02 | 0 |
| 2 | 2012-01-02 | 2012-01-03 | 2 |
| 3 | 2012-01-05 | 2012-01-06 | 0 |
+------+------------+------------+------------+
2) We need the start and end of each period in the same row, so we need to combine adjacent rows again. Do this by creating a second temporary table like boundaries but having an idx field 1 greater:
+------+------------+------------+
| idx | prev_end | next_start |
+------+------------+------------+
| 2 | 2012-01-01 | 2012-01-02 |
| 3 | 2012-01-02 | 2012-01-03 |
| 4 | 2012-01-05 | 2012-01-06 |
+------+------------+------------+
Now join on the idx field and we get the answer:
SELECT
boundaries2.next_start AS start,
boundaries.prev_end AS end,
allocation
FROM boundaries
JOIN boundaries2
USING(idx);
+------------+------------+------------+
| start | end | allocation |
+------------+------------+------------+
| 2012-01-02 | 2012-01-02 | 2 |
| 2012-01-03 | 2012-01-05 | 0 |
+------------+------------+------------+
** Note that this answer gets the "internal" periods correctly but misses the two "edge" periods where allocation = 0 at the beginning and allocation = 5 at the end. Those can be pulled in using UNION clauses but I wanted to present the core idea without that complication.
Following would be one way to do it. The gist of this solution is
Use a CTE to get a list of all consecutive start and enddates with Allocation = 0
Use the ROW_NUMBER window function to assign rownumbers depending on both start- and enddates.
Select only those records where both ROW_NUMBERS equal 1.
Use DATEDIFFto calculate the DayCount
SQL Statement
;WITH r AS (
SELECT StartDate = Date, EndDate = Date
FROM YourTable
WHERE Allocation = 0
UNION ALL
SELECT r.StartDate, q.Date
FROM r
INNER JOIN YourTable q ON DATEDIFF(dd, r.EndDate, q.Date) = 1
WHERE q.Allocation = 0
)
SELECT [Start Date] = s.StartDate
, [End Date ] = s.EndDate
, [DayCount] = DATEDIFF(dd, s.StartDate, s.EndDate) + 1
FROM (
SELECT *
, rn1 = ROW_NUMBER() OVER (PARTITION BY StartDate ORDER BY EndDate DESC)
, rn2 = ROW_NUMBER() OVER (PARTITION BY EndDate ORDER BY StartDate ASC)
FROM r
) s
WHERE s.rn1 = 1
AND s.rn2 = 1
OPTION (MAXRECURSION 0)
Test script
;WITH q (ID, Date, Allocation) AS (
SELECT * FROM (VALUES
(1, '2012-01-01', 0)
, (2, '2012-01-02', 2)
, (3, '2012-01-03', 0)
, (4, '2012-01-04', 0)
, (5, '2012-01-05', 0)
, (6, '2012-01-06', 5)
) a (a, b, c)
)
, r AS (
SELECT StartDate = Date, EndDate = Date
FROM q
WHERE Allocation = 0
UNION ALL
SELECT r.StartDate, q.Date
FROM r
INNER JOIN q ON DATEDIFF(dd, r.EndDate, q.Date) = 1
WHERE q.Allocation = 0
)
SELECT s.StartDate, s.EndDate, DATEDIFF(dd, s.StartDate, s.EndDate) + 1
FROM (
SELECT *
, rn1 = ROW_NUMBER() OVER (PARTITION BY StartDate ORDER BY EndDate DESC)
, rn2 = ROW_NUMBER() OVER (PARTITION BY EndDate ORDER BY StartDate ASC)
FROM r
) s
WHERE s.rn1 = 1
AND s.rn2 = 1
OPTION (MAXRECURSION 0)
Alternative way with CTE but without ROW_NUMBER(),
Sample data:
if object_id('tempdb..#tab') is not null
drop table #tab
create table #tab (id int, date datetime, allocation int)
insert into #tab
select 1, '2012-01-01', 0 union
select 2, '2012-01-02', 2 union
select 3, '2012-01-03', 0 union
select 4, '2012-01-04', 0 union
select 5, '2012-01-05', 0 union
select 6, '2012-01-06', 5 union
select 7, '2012-01-07', 0 union
select 8, '2012-01-08', 5 union
select 9, '2012-01-09', 0 union
select 10, '2012-01-10', 0
Query:
;with cte(s_id, e_id, b_id) as (
select s.id, e.id, b.id
from #tab s
left join #tab e on dateadd(dd, 1, s.date) = e.date and e.allocation = 0
left join #tab b on dateadd(dd, -1, s.date) = b.date and b.allocation = 0
where s.allocation = 0
)
select ts.date as [start date], te.date as [end date], count(*) as [day count] from (
select c1.s_id as s, (
select min(s_id) from cte c2
where c2.e_id is null and c2.s_id >= c1.s_id
) as e
from cte c1
where b_id is null
) t
join #tab t1 on t1.id between t.s and t.e and t1.allocation = 0
join #tab ts on ts.id = t.s
join #tab te on te.id = t.e
group by t.s, t.e, ts.date, te.date
Live example at data.SE.
Using this sample data:
CREATE TABLE MyTable (ID INT, Date DATETIME, Allocation INT);
INSERT INTO MyTable VALUES (1, {d '2012-01-01'}, 0);
INSERT INTO MyTable VALUES (2, {d '2012-01-02'}, 2);
INSERT INTO MyTable VALUES (3, {d '2012-01-03'}, 0);
INSERT INTO MyTable VALUES (4, {d '2012-01-04'}, 0);
INSERT INTO MyTable VALUES (5, {d '2012-01-05'}, 0);
INSERT INTO MyTable VALUES (6, {d '2012-01-06'}, 5);
GO
Try this:
WITH DateGroups (ID, Date, Allocation, SeedID) AS (
SELECT MyTable.ID, MyTable.Date, MyTable.Allocation, MyTable.ID
FROM MyTable
LEFT JOIN MyTable Prev ON Prev.Date = DATEADD(d, -1, MyTable.Date)
AND Prev.Allocation = 0
WHERE Prev.ID IS NULL
AND MyTable.Allocation = 0
UNION ALL
SELECT MyTable.ID, MyTable.Date, MyTable.Allocation, DateGroups.SeedID
FROM MyTable
JOIN DateGroups ON MyTable.Date = DATEADD(d, 1, DateGroups.Date)
WHERE MyTable.Allocation = 0
), StartDates (ID, StartDate, DayCount) AS (
SELECT SeedID, MIN(Date), COUNT(ID)
FROM DateGroups
GROUP BY SeedID
), EndDates (ID, EndDate) AS (
SELECT SeedID, MAX(Date)
FROM DateGroups
GROUP BY SeedID
)
SELECT StartDates.StartDate, EndDates.EndDate, StartDates.DayCount
FROM StartDates
JOIN EndDates ON StartDates.ID = EndDates.ID;
The first section of the query is a recursive SELECT, which is anchored by all rows that are allocation = 0, and whose previous day either doesn't exist or has allocation != 0. This effectively returns IDs: 1 and 3 which are the starting dates of the periods of time you want to return.
The recursive part of this same query starts from the anchor rows, and finds all subsequent dates that also have allocation = 0. The SeedID keeps track of the anchored ID through all the iterations.
The result so far is this:
ID Date Allocation SeedID
----------- ----------------------- ----------- -----------
1 2012-01-01 00:00:00.000 0 1
3 2012-01-03 00:00:00.000 0 3
4 2012-01-04 00:00:00.000 0 3
5 2012-01-05 00:00:00.000 0 3
The next sub query uses a simple GROUP BY to filter out all the start dates for each SeedID, and also counts the days.
The last sub query does the same thing with the end dates, but this time the day count isn't needed as we already have this.
The final SELECT query joins these two together to combine the start and end dates, and returns them along with the day count.
Give it a try if it works for you
Here SDATE for your DATE remains same as your table.
SELECT SDATE,
CASE WHEN (SELECT COUNT(*)-1 FROM TABLE1 WHERE ID BETWEEN TBL1.ID AND (SELECT MIN(ID) FROM TABLE1 WHERE ID > TBL1.ID AND ALLOCATION!=0)) >0 THEN(
CASE WHEN (SELECT SDATE FROM TABLE1 WHERE ID =(SELECT MAX(ID) FROM TABLE1 WHERE ID >TBL1.ID AND ID<(SELECT MIN(ID) FROM TABLE1 WHERE ID > TBL1.ID AND ALLOCATION!=0))) IS NULL THEN SDATE
ELSE (SELECT SDATE FROM TABLE1 WHERE ID =(SELECT MAX(ID) FROM TABLE1 WHERE ID >TBL1.ID AND ID<(SELECT MIN(ID) FROM TABLE1 WHERE ID > TBL1.ID AND ALLOCATION!=0))) END
)ELSE (SELECT SDATE FROM TABLE1 WHERE ID = (SELECT MAX(ID) FROM TABLE1 WHERE ID > TBL1.ID ))END AS EDATE
,CASE WHEN (SELECT COUNT(*)-1 FROM TABLE1 WHERE ID BETWEEN TBL1.ID AND (SELECT MIN(ID) FROM TABLE1 WHERE ID > TBL1.ID AND ALLOCATION!=0)) <0 THEN
(SELECT COUNT(*) FROM TABLE1 WHERE ID BETWEEN TBL1.ID AND (SELECT MAX(ID) FROM TABLE1 WHERE ID > TBL1.ID )) ELSE
(SELECT COUNT(*)-1 FROM TABLE1 WHERE ID BETWEEN TBL1.ID AND (SELECT MIN(ID) FROM TABLE1 WHERE ID > TBL1.ID AND ALLOCATION!=0)) END AS DAYCOUNT
FROM TABLE1 TBL1 WHERE ALLOCATION = 0
AND (((SELECT ALLOCATION FROM TABLE1 WHERE ID=(SELECT MAX(ID) FROM TABLE1 WHERE ID < TBL1.ID))<> 0 ) OR (SELECT MAX(ID) FROM TABLE1 WHERE ID < TBL1.ID)IS NULL);
A solution without CTE:
SELECT a.aDate AS StartDate
, MIN(c.aDate) AS EndDate
, (datediff(day, a.aDate, MIN(c.aDate)) + 1) AS DayCount
FROM (
SELECT x.aDate, x.allocation, COUNT(*) idn FROM table1 x
JOIN table1 y ON y.aDate <= x.aDate
GROUP BY x.id, x.aDate, x.allocation
) AS a
LEFT JOIN (
SELECT x.aDate, x.allocation, COUNT(*) idn FROM table1 x
JOIN table1 y ON y.aDate <= x.aDate
GROUP BY x.id, x.aDate, x.allocation
) AS b ON a.idn = b.idn + 1 AND b.allocation = a.allocation
LEFT JOIN (
SELECT x.aDate, x.allocation, COUNT(*) idn FROM table1 x
JOIN table1 y ON y.aDate <= x.aDate
GROUP BY x.id, x.aDate, x.allocation
) AS c ON a.idn <= c.idn AND c.allocation = a.allocation
LEFT JOIN (
SELECT x.aDate, x.allocation, COUNT(*) idn FROM table1 x
JOIN table1 y ON y.aDate <= x.aDate
GROUP BY x.id, x.aDate, x.allocation
) AS d ON c.idn = d.idn - 1 AND d.allocation = c.allocation
WHERE b.idn IS NULL AND c.idn IS NOT NULL AND d.idn IS NULL AND a.allocation = 0
GROUP BY a.aDate
Example