使用SQL

时间:2016-07-13 15:05:24

标签: sql sql-server

我在SQL Server Management Studio中获得了一个数据集。数据如下所示。我有记录的每个人userIDdate,开始时间startime和结束时间endtime的标识符。

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

3 个答案:

答案 0 :(得分:1)

遵循我之前的cte方法的重新设计版本。但是,如果同一用户的多条记录具有相同的开始时间,它仍然会有问题...没有时间来修复那个,但据我所知,这在描述的过程中是不可能的!?< / p>

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

答案 1 :(得分:1)

似乎带有CTE的SELECT需要以递归方式合并未确定数量的行。在这种情况下,我更喜欢安全的基于CURSOR的解决方案:

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 

答案 2 :(得分: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;