2016-07-13 3 views
0

SQL Server Management Studioでデータセットを取得しました。データは次のようになります。私は各人の識別子がuserID,date、開始時刻はstartime、終了時刻はendtimeです。SQLを使用して重複するデータ範囲を排除して削減する

UserID date   startime endtime 
1  20110203  09:30  09:35 
1  20110203  09:31  09:38 
1  20110203  10:03  10:05 
1  20110203  10:04:00 10:35:00 
2  20110203  11:02  11:05 

各ユーザーには、重複している時間があるかどうかを確認します。もしあれば、私は最小startimeと最大のendtimeを保ちたいと思います。時間が重ならなければ、私は元のデータを保持します。さらに、maxi endtimesmallest startimeの期間を計算したいと思います。

結果は次のようになります。誰も私にこのコードを書く方法を教えてもらえますか?

UserID date   startime endtime diff 
1  20110203  09:30  09:38  00:08 
1  20110203  10:03  10:35  00:02 
2  20110203  11:02  11:05  00:03 
+1

を返します。その場合、私は安全CURSORベースのソリューションを好むだろうか? – mcha

+1

Itzik Ben-Ganインターバルパッキングを検索します。 – shawnt00

答えて

1

CTEとSELECTを再帰的に行の未定数をマージする必要があると思われます。

DECLARE @t TABLE 
(
    UserId int, 
    [Date] date, 
    StartTime time, 
    EndTime time 
); 
INSERT INTO @t VALUES 
(1, '2011-02-03', '09:30:00', '09:35:00'), 
(1, '2011-02-03', '09:31:00', '09:38:00'), 
(1, '2011-02-03', '09:36:00', '09:41:00'), 
(1, '2011-02-03', '09:40:00', '09:45:00'), 
(1, '2011-02-03', '09:42:00', '09:43:00'), 
(1, '2011-02-03', '10:03:00', '10:05:00'), 
(2, '2011-02-03', '11:02:00', '11:05:00'), 
(1, '2011-02-03', '12:00:00', '12:05:00'), 
(1, '2011-02-03', '12:04:00', '12:06:00'); 

------------------ 
DECLARE @result TABLE 
(
    UserId int, 
    [Date] date, 
    StartTime time, 
    EndTime time 
) 

DECLARE cur CURSOR FOR 
    SELECT UserId, [Date], StartTime, EndTime 
    FROM @t 
    ORDER BY UserId, [Date], StartTime; 

DECLARE @UserId int 
DECLARE @Date date 
DECLARE @StartTime time 
DECLARE @EndTime time 

DECLARE @LastUserId int 
DECLARE @LastDate date 
DECLARE @LastStartTime time 
DECLARE @LastEndTime time 

OPEN cur 

FETCH NEXT FROM cur INTO @UserId, @Date, @StartTime, @EndTime 
SET @LastUserId = @UserId 
SET @LastDate = @Date 
SET @LastStartTime = @StartTime 
SET @LastEndTime = @EndTime 
WHILE @@FETCH_STATUS = 0 
BEGIN 
    IF @UserId = @LastUserId AND @Date = @LastDate AND @StartTime <= @LastEndTime 
    SET @LastEndTime = CASE WHEN @LastEndTime > @EndTime THEN @LastEndTime ELSE @EndTime END 
    ELSE 
    BEGIN 
    INSERT @result(UserId, [Date], StartTime, EndTime) VALUES (@LastUserId, @LastDate, @LastStartTime, @LastEndTime) 
    SET @LastUserId = @UserId 
    SET @LastDate = @Date 
    SET @LastStartTime = @StartTime 
    SET @LastEndTime = @EndTime 
    END 

    FETCH NEXT FROM cur INTO @UserId, @Date, @StartTime, @EndTime 
END 
INSERT @result(UserId, [Date], StartTime, EndTime) VALUES (@LastUserId, @LastDate, @LastStartTime, @LastEndTime) 

CLOSE cur 
DEALLOCATE cur 

SELECT UserId, 
     [Date], 
     StartTime, 
     EndTime, 
     CAST(DATEADD(second,DATEDIFF(second,StartTime,EndTime),'2000-01-01') AS time) Diff 
     FROM @result 

あなたはこれまでにしようとしているものを共有することができます

1 2011-02-03 09:30:00.0000000 09:45:00.0000000 00:15:00.0000000 
1 2011-02-03 10:03:00.0000000 10:05:00.0000000 00:02:00.0000000 
1 2011-02-03 12:00:00.0000000 12:06:00.0000000 00:06:00.0000000 
2 2011-02-03 11:02:00.0000000 11:05:00.0000000 00:03:00.0000000 
1

以前のcteアプローチの再設計されたバージョンに続いて、しかし、同一の開始時刻の同じユーザーの複数のレコードがある場合には問題は残っています...その時刻を修正する時間がありませんでしたが、説明されているプロセスではこれが不可能であると理解していました。

-- 
-- This part is temporary and has to be replaced by your tables 
-- There several more records included now 
-- There is still a glitch if the starttime is identical for two records - but as far as I understood, this is not possible in the described case? 
-- 
declare @t table (userid int, date int, starttime time, endtime time); 
insert into @t values (1, 20110203, '09:30:00', '09:35:00'), (1, 20110203, '09:31:00', '09:38:00'), (1, 20110203, '09:36:00', '09:41:00'), (1, 20110203, '10:03:00', '10:05:00'),(1, 20110203, '10:04:00', '10:35:00'), 
         (2, 20110203, '11:02:00', '11:05:00'), (2, 20110203, '11:03:00', '11:20:00'), (2, 20110203, '11:04:00', '11:35:00'), (2, 20110203, '13:02:00', '13:05:00'), (2, 20110203, '13:04:00', '13:15:00'); 

-- 
-- First cte: selects all start and endtimes and their - if existing - "overlaps"; recursive cte 
-- 
WITH cte AS(
    SELECT 1 lvl, a.userid 
     ,CASE WHEN a.starttime <= ISNULL(b.starttime, a.starttime) THEN a.starttime ELSE b.starttime END AS starttime 
     ,CASE WHEN a.endtime >= ISNULL(b.endtime, a.endtime) THEN a.endtime ELSE b.endtime END AS endtime 
    FROM @t as a 
    LEFT OUTER JOIN @t AS b ON b.userid = a.userid 
           AND b.starttime < a.starttime 
           AND b.endtime > a.starttime 
    UNION ALL 
    select a.lvl+1, a.userid 
    ,CASE WHEN a.starttime <= ISNULL(b.starttime, a.starttime) THEN a.starttime ELSE b.starttime END AS xStart 
    ,CASE WHEN a.endtime >= ISNULL(b.endtime, a.endtime) THEN a.endtime ELSE b.endtime END AS xEnd 
    from cte as a 
    INNER JOIN @t AS b ON b.userid = a.userid 
           AND b.starttime < a.starttime 
           AND b.endtime > a.starttime 
), 
-- 
-- Second cte: get the max. lvl result per user from the recursive cte 
-- 
cteUserMaxLvl AS (
    SELECT userid, max(lvl) AS MaxLvl 
    FROM cte 
    GROUP BY userid 
), 
-- 
-- third cte: get the rows matching the max.lvl; their timespan should cover the desired min. start and max. end 
-- 
cteNoMoreOverlap AS(
    SELECT a.userid, starttime, endtime 
    FROM cte AS a 
    JOIN cteUserMaxLvl AS b ON a.userid = b.userid AND a.lvl = b.MaxLvl 
) 
-- 
-- Select the rows from the "No more overlap" cte 
-- 
SELECT userid, starttime, endtime 
    FROM cteNoMoreOverlap 
UNION ALL 
-- 
-- And finally select all rows, which are not covered by the previously selected timespan 
-- 
SELECT a.userid, min(a.starttime) AS starttime, max(a.endtime) AS endtime 
    FROM cte AS a 
    JOIN cteNoMoreOverlap AS b ON a.userid = b.userid AND a.starttime NOT BETWEEN b.starttime AND b.endtime 
    GROUP BY a.userid 
order by userid, starttime, endtime 
+0

別の観測(1、20110203、'10:04:00 '、'10:35:00')を追加した場合、結果は間違っています。 – fly36

+0

ああ、OK。ただし、クエリを再作成しましたが、すでに解決策が見つかりました。 – Tyron78

+0

こんにちは、本当にあなたの助けに感謝します。私はあなたのソリューションが本当に好きです。それはとてもスマートです。あなたのスマートな解決策に感謝します。あなたが気にしないなら、あなたの解決策を教えてください。あなたが提案した方法が大好きです。 – fly36

0

私はあなたが重複する時間を言うとき、同じ日に同じ時間内に言っていると思います。それがあなたが意味することであれば、解決策を実行することができます。添付の私の結果のスクリーンショットです。 Result of Overlapping Dates problem

CREATE TABLE #OverlappingDates 
    (
     UserID INT 
    , [date] DATE 
    , starttime VARCHAR(5) 
    , endtime VARCHAR(5) 
    ); 

INSERT INTO #OverlappingDates 
     (UserID, date, starttime, endtime) 
VALUES (1 -- UserID - int 
      , '20110203' -- date - date 
      , '09:30' -- starttime - time 
      , '09:35' -- endtime - time 
     ), 
     (1 -- UserID - int 
      , '20110203' -- date - date 
      , '09:31' -- starttime - time 
      , '09:38' -- endtime - time 
     ), 
     (1 -- UserID - int 
      , '20110203' -- date - date 
      , '10:03' -- starttime - time 
      , '10:05' -- endtime - time 
     ), 
     (2 -- UserID - int 
      , '20110203' -- date - date 
      , '11:02' -- starttime - time 
      , '11:05' -- endtime - time 
     ), 
     (2 -- UserID - int 
      , '20110203' -- date - date 
      , '11:05' -- starttime - time 
      , '11:15' -- endtime - time 
     ), 
     (2 -- UserID - int 
      , '20110203' -- date - date 
      , '11:05' -- starttime - time 
      , '12:00' -- endtime - time 
     ); 

WITH cte 
      AS (SELECT UserID 
         , date 
         , MIN(starttime) AS StartTime 
         , MAX(endtime) AS EndTime 
       FROM  #OverlappingDates 
       GROUP BY UserID 
         , date 
         , LEFT(starttime, 2) 
         , LEFT(endtime, 2) 
      ) 
    SELECT cte.UserID 
      , cte.date 
      , cte.StartTime 
      , cte.EndTime 
      , (RIGHT('0' 
        + CAST((DATEDIFF(SECOND, 
             (CAST(CONCAT((CAST(cte.[date] AS VARCHAR(10))), 
                ' ', cte.StartTime) AS DATETIME)), 
             (CAST(CONCAT((CAST(cte.[date] AS VARCHAR(10))), 
                ' ', cte.EndTime) AS DATETIME)))) 
        /3600 AS VARCHAR(2)), 2) + ':' + RIGHT('0' 
                  + CAST(((DATEDIFF(SECOND, 
                  (CAST(CONCAT((CAST(cte.[date] AS VARCHAR(10))), 
                  ' ', 
                  cte.StartTime) AS DATETIME)), 
                  (CAST(CONCAT((CAST(cte.[date] AS VARCHAR(10))), 
                  ' ', cte.EndTime) AS DATETIME)))) 
                 /60) % 60 AS VARCHAR(2)), 
                  2)) AS Diff 
    FROM cte; 
+0

実際にはありません。重複時間とは、同じ期間または同じ期間の2つのレコード共有があるかどうかを意味します。たとえば、9:01-9:05は9:02-9:20と部分的に重複しています。この場合、私はそれを1つのレコードとして扱い、期間を9:20-9:01として計算します。しかし、9:01-9:05は9:20-9:21と重複していません。この場合、私はこれら2つのレコードを独立したレコードとして扱います。 – fly36

関連する問題