Устранение и уменьшение перекрывающихся диапазонов данных с использованием SQL

115
13

Я получил набор данных в SQL Server Management Studio. Данные выглядят следующим образом. У меня есть идентификатор для каждого пользователя userID, date записи, время 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 endtime и smallest 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

спросил(а) 2021-01-19T16:37:51+03:00 2 месяца, 3 недели назад
1
Решение
63

Следуя переработанной версии моего предыдущего подхода к 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

ответил(а) 2021-01-19T16:37:51+03:00 2 месяца, 3 недели назад
62

Кажется, что SELECT с CTE требует рекурсивного слияния неопределенного количества строк. В этом случае я предпочел бы безопасное решение на основе 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

ответил(а) 2021-01-19T16:37:51+03:00 2 месяца, 3 недели назад
44

Я верю, что, когда вы говорите о перекрытии времени, вы говорите в тот же час в тот же день. Если это то, что вы имеете в виду, следующее решение может работать. Приложен скриншот моих результатов. 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;

ответил(а) 2021-01-19T16:37:51+03:00 2 месяца, 3 недели назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема