1

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

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 为我所尝试的路线所做的努力。

4

3 回答 3

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. 记录按 ( date, timeX) 排序。
于 2013-06-27T01:27:16.330 回答
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 演示

于 2013-06-27T00:18:47.127 回答
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 使其更易于阅读。

于 2013-06-27T05:24:18.137 回答