5

従業員の勤務時間のブロックに基づいてレポートを作成しています。場合によっては、データには、実際には 1 つの時間ブロックである 2 つの別個のレコードが含まれます。

テーブルの基本バージョンといくつかのサンプル レコードを次に示します。

EmployeeID
StartTime
EndTime

データ:

EmpID      Start         End
----------------------------
#1001   10:00 AM    12:00 PM
#1001    4:00 PM     5:30 PM
#1001    5:30 PM     8:00 PM

この例では、最後の 2 つのレコードは時間的に連続しています。結果セットが次のようになるように、隣接するレコードを結合するクエリを作成したいと思います。

EmpID      Start         End
----------------------------
#1001   10:00 AM    12:00 PM
#1001    4:00 PM     8:00 PM

理想的には、2 つ以上の隣接するレコードも処理できる必要がありますが、必須ではありません。

4

5 に答える 5

2

この記事では、あなたの質問に対するかなりの数の可能な解決策を提供します

http://www.sqlmag.com/blog/puzzled-by-t-sql-blog-15/tsql/solutions-to-packing-date-and-time-intervals-puzzle-136851

これは最も簡単なようです:

WITH StartTimes AS
(
  SELECT DISTINCT username, starttime
  FROM dbo.Sessions AS S1
  WHERE NOT EXISTS
    (SELECT * FROM dbo.Sessions AS S2
     WHERE S2.username = S1.username
       AND S2.starttime < S1.starttime
       AND S2.endtime >= S1.starttime)
),
EndTimes AS
(
  SELECT DISTINCT username, endtime
  FROM dbo.Sessions AS S1
  WHERE NOT EXISTS
    (SELECT * FROM dbo.Sessions AS S2
     WHERE S2.username = S1.username
       AND S2.endtime > S1.endtime
       AND S2.starttime <= S1.endtime)
)
SELECT username, starttime,
  (SELECT MIN(endtime) FROM EndTimes AS E
   WHERE E.username = S.username
     AND endtime >= starttime) AS endtime
FROM StartTimes AS S;
于 2013-03-01T15:39:33.383 に答える
1

累積合計の CTE:

DECLARE @t TABLE(EmpId INT, Start TIME, Finish TIME)
INSERT INTO @t (EmpId, Start, Finish)
VALUES
    (1001, '10:00 AM', '12:00 PM'),
    (1001, '4:00 PM', '5:30 PM'),
    (1001, '5:30 PM', '8:00 PM')

;WITH rowind AS (
    SELECT EmpId, Start, Finish,
        -- IIF returns 1 for each row that should generate a new row in the final result
        IIF(Start = LAG(Finish, 1) OVER(PARTITION BY EmpId ORDER BY Start), 0, 1) newrow
    FROM @t),
    groups AS (
    SELECT EmpId, Start, Finish,
        -- Cumulative sum
        SUM(newrow) OVER(PARTITION BY EmpId ORDER BY Start) csum
    FROM rowind)

SELECT
    EmpId,
    MIN(Start) Start,
    MAX(Finish) Finish
FROM groups
GROUP BY EmpId, csum
于 2016-08-02T12:16:09.850 に答える
1

これが厳密に隣接する行 (重複していない行) に関するものである場合は、次の方法を試すことができます。

  1. タイムスタンプをアンピボットします。

  2. 重複がないものだけを残します。

  3. 残りのものを元に戻して、すべてStartを直後のEnd.

または、Transact-SQL では、次のようになります。

WITH unpivoted AS (
  SELECT
    EmpID,
    event,
    dtime,
    count = COUNT(*) OVER (PARTITION BY EmpID, dtime)
  FROM atable
  UNPIVOT (
    dtime FOR event IN (StartTime, EndTime)
  ) u
)
, filtered AS (
  SELECT
    EmpID,
    event,
    dtime,
    rowno = ROW_NUMBER() OVER (PARTITION BY EmpID, event ORDER BY dtime)
  FROM unpivoted
  WHERE count = 1
)
, pivoted AS (
  SELECT
    EmpID,
    StartTime,
    EndTime
  FROM filtered
  PIVOT (
    MAX(dtime) FOR event IN (StartTime, EndTime)
  ) p
)
SELECT *
FROM pivoted
;

このクエリのデモがSQL Fiddle にあります。

于 2013-03-01T21:58:43.507 に答える
0

例を小さくするために名前と型を少し変更しましたが、これは機能し、非常に高速である必要があり、レコード数の制限はありません。

with cte as (
  select 
    x1.id
    ,x1.t1
    ,x1.t2
    ,case when x2.t1 is null then 1 else 0 end as bef
    ,case when x3.t1 is null then 1 else 0 end as aft
  from x x1
  left join x x2 on x1.id=x2.id and x1.t1=x2.t2
  left join x x3 on x1.id=x3.id and x1.t2=x3.t1
  where x2.id is null
  or    x3.id is null
)

select 
  cteo.id
  ,cteo.t1
  ,isnull(z.t2,cteo.t2) as t2

from cte cteo
outer apply (select top 1 * 
             from cte ctei 
             where cteo.id=ctei.id and cteo.aft=0 and ctei.t1>cteo.t1
             order by t1) z
where cteo.bef=1

そしてそれのためのフィドル:http://sqlfiddle.com/#!3/ad737/12/0

于 2013-03-01T15:58:11.220 に答える
0

インライン ユーザー定義関数 AND CTE を含むオプション

CREATE FUNCTION dbo.Overlap
 (
  @availStart datetime,
  @availEnd datetime,
  @availStart2 datetime,
  @availEnd2 datetime
  )
RETURNS TABLE
RETURN
  SELECT CASE WHEN @availStart > @availEnd2 OR @availEnd < @availStart2
              THEN @availStart ELSE
                               CASE WHEN @availStart > @availStart2 THEN @availStart2 ELSE @availStart END
                               END AS availStart,
         CASE WHEN @availStart > @availEnd2 OR @availEnd < @availStart2
              THEN @availEnd ELSE
                             CASE WHEN @availEnd > @availEnd2 THEN @availEnd ELSE @availEnd2 END
                             END AS availEnd

;WITH cte AS
 (
  SELECT EmpID, Start, [End], ROW_NUMBER() OVER (PARTITION BY EmpID ORDER BY Start) AS Id
  FROM dbo.TableName
  ), cte2 AS
 (
  SELECT Id, EmpID, Start, [End]
  FROM cte
  WHERE Id = 1
  UNION ALL
  SELECT c.Id, c.EmpID, o.availStart, o.availEnd
  FROM cte c JOIN cte2 ct ON c.Id = ct.Id + 1
             CROSS APPLY dbo.Overlap(c.Start, c.[End], ct.Start, ct.[End]) AS o
  )
  SELECT EmpID, Start, MAX([End])
  FROM cte2
  GROUP BY EmpID, Start

SQLFiddle のデモ

于 2013-03-01T17:47:15.017 に答える