将连续的SQL行(每隔一行)与警告相结合

时间:2013-06-26 23:17:25

标签: sql sql-server

我有一张包含以下有序数据的表格:

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

对于每个匹配的对,我需要的是在类型A的TimeY和类型B的TimeX之间获得分钟数。由于我无法控制的设计,我无法将“A”和“B”连接在一起,而不仅仅是按时间戳顺序连接。遗憾的是,不能,我不能保证所有类型'A'的行都会跟着'B'类型的行,所以任何'A'都不会被'B'所忽略。但是,没有'B'会跟随另一个'B'。基本上,这是我想看到的:

Employee     Duration
---------------------
    1           30
    1           40
    1           15

有没有办法轻松完成这项工作?我在这里找到的最接近的解决方案涉及加入日期,但在这种情况下这不会起作用。我下午晚些时候提出的唯一可能的解决办法过于复杂而且没有过时。

编辑:感谢您的回复!这是一些令人印象深刻的SQL争吵!我选择了Marc的答案,因为它是最容易阅读的,但感谢Gordon为Marc的回答提供灵感,感谢Nenad在我尝试的过程中所做的努力。

3 个答案:

答案 0 :(得分:1)

现在是凌晨2点,这可能是我写过的最丑陋的查询之一,我很确定有一些方法可以简化一些部分。但是,重要的是 - 它正在发挥作用:)

;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)

<强> SQLFiddle DEMO

答案 1 :(得分:1)

我认为最简单的表达方式是使用相关的子查询:

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;

这是做出以下假设:

  1. 下一个“B”在同一天
  2. 然而,你代表的是时间,你可以采取差异来获得持续时间
  3. 记录按(datetimeX)排序。

答案 2 :(得分:1)

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
;

这实际上并没有进行减法,因为我不清楚TimeX和TimeY的类型。

这类似于相关的子查询答案,但我认为CROSS APPLY使其更容易阅读。