Reputation: 776
I have a table with the following pieces of ordered data:
Employee TimeX TimeY Type Date
-----------------------------------------------
1 0800 0900 'A' 1/1/2013
1 0930 1300 'B' 1/1/2013
1 0600 0845 'A' 1/2/2013
1 0925 1300 'B' 1/2/2013
1 1100 1400 'A' 1/3/2013
1 0500 0700 'A' 1/4/2013
1 0715 0800 'B' 1/4/2013
What I need is to get a count of minutes between TimeY of Type A and TimeX of Type B, for each matching pair. Due to design beyond my control, I have no way to link an 'A' and a 'B' together other than just sequentially by timestamp. And sadly, no, I cannot guarantee that all rows of type 'A' will be followed by rows of type 'B', so any 'A' not followed by a 'B' should be ignored. However, no 'B' will ever be followed by another 'B'. Basically, this is what I'd like to see:
Employee Duration
---------------------
1 30
1 40
1 15
Is there a way to do this easily? The closest solution I've found here involves joining on the dates, but that's not going to work in this case. The only possible solutions I'm coming up with late in the afternoon are overly complicated and not panning out.
Edit: Thanks for the responses! That's some pretty impressive SQL wrangling! I went with Marc's answer, as it was the easiest to read, but thanks to Gordon for providing the inspiration to Marc's answer and to Nenad for the effort along the lines that I was trying.
Upvotes: 1
Views: 1331
Reputation: 571
SELECT
a.Employee,
a.TimeY,
b.TimeX
FROM Table1 a
CROSS APPLY
(
SELECT TOP(1) t.TimeX
FROM Table1 t
WHERE a.[Date] = t.[Date]
AND a.Employee = t.Employee
AND a.TimeY < t.TimeX
AND t.[Type] = 'B'
ORDER BY t.TimeX ASC
) b
WHERE a.[Type] = 'A'
ORDER BY a.Employee ASC
;
This doesn't actually do the subtraction, since I'm not clear on the types of TimeX and TimeY.
This is similar to the correlated subquery answer, but I think the CROSS APPLY makes it easier to read.
Upvotes: 1
Reputation: 1270713
I think the easiest way to express this is with a correlated subquery:
select t.employee, t.nextBtime - t.time
from (select t.*,
(select top 1 (case when type = 'B' then timeY end)
from t t2
where t2.employee = t.employee and
t2.date = t.date and
t2.timeX > t.timeX
order by t2.timeX
) nextBtime
from t
where type = 'A'
) t
where nextBtime is null;
This is making the following assumptions:
date
, timeX
).Upvotes: 1
Reputation: 18559
It's 2AM, this is probably one of the ugliest queries I have ever written and I am pretty sure that there are ways to simplify some parts a bit. But, important - it's working :)
;WITH CTE1 AS
(
--first CTE is simply to get row numbering over all dates
SELECT *, ROW_NUMBER() OVER (ORDER BY [Date],[Type]) RN
FROM Table1
)
, RCTE1 AS
(
--recursive cte is going row-by-row checking if next type is same or different
SELECT *, 1 AS L FROM CTE1 WHERE RN =1
UNION ALL
--assigning same L if next is same, L+1 if different
SELECT c.*, CASE WHEN r.Type = c.Type THEN L ELSE L+1 END AS L
FROM RCTE1 r
INNER JOIN CTE1 c ON r.RN +1 = c.RN
)
, CTE2 AS
(
--here we search for same L values
SELECT *, ROW_NUMBER() OVER (PARTITION BY L ORDER BY RN DESC) RN2 FROM RCTE1
)
, CTE3 AS
(
--and eliminate the rows not needed (ie A in front of A)
SELECT *, ROW_NUMBER() OVER (PARTITION BY [Type] ORDER BY L) RN3
FROM CTE2 WHERE RN2 =1
)
-- at the end join CTE3 based on same RN3 and different type
SELECT *
-- and some datetime operations to get times from strings
, DATEDIFF(MI,DATEADD(MI,CAST(RIGHT(A.TimeY,2) AS INT) , DATEADD(HH,CAST(LEFT(A.TimeY,2) AS INT),0)), DATEADD(MI,CAST(RIGHT(B.TimeX,2) AS INT) , DATEADD(HH,CAST(LEFT(B.TimeX,2) AS INT),0))) AS Goal
FROM CTE3 a
INNER JOIN CTE3 B ON a.RN3 = b.RN3 AND a.[Type] = 'A' AND b.[Type] = 'B'
-- maxrecursion off so Recursive CTE can work
OPTION (MAXRECURSION 0)
Upvotes: 1