SQL - 按时间间隔拆分总时间

时间:2017-10-13 14:57:55

标签: sql tsql sql-server-2016

我有一个看起来像这样的条目:

用户ID --- --- StatusStart --- StatusEnd --- StatusKey
StateDuration Joe1 ------ 8:59:46 -------- 9:08:06 -------可用----- 500

我需要做的是将其分成两个条目。一个显示从8:59:46到9:00:00的声明,以及从9:00:00到9:08:06结束的第二个

最终,我希望能够拥有一些我可以应用于这个信息的大表的内容,这样我就可以确定代理商每半小时花费多少和多少时间。我过度思考这个问题,我很积极,因为我每次都把自己变成一个网络 - 我必须忘记一些简单的事情。

另外,理想情况下,无论它是什么公式,如果时间跨越多个区间,它都会起作用。例如StatusStart为8:59:46但StatusEnd为10:08:06。

4 个答案:

答案 0 :(得分:3)

老派的方法,但我会创建第二个表:

CREATE TABLE SplitTimes (
    SplitStart time not null,
    SplitEnd time not null,
    primary key (SplitStart, SplitEnd)
)

填充它:

INSERT INTO SplitTimes (SplitStart, SplitEnd) VALUES
('0:00', '0:30'),
('0:30', '1:00'),
('1:00', '1:30'),
('1:30', '2:00'),
('2:00', '2:30'),
('2:30', '3:00'),
('3:00', '3:30'),
('3:30', '4:00'),
('4:00', '4:30'),
('4:30', '5:00'),
('5:00', '5:30'),
('5:30', '6:00'),
('6:00', '6:30'),
('6:30', '7:00'),
('7:00', '7:30'),
('7:30', '8:00'),
('8:00', '8:30'),
('8:30', '9:00'),
('9:00', '9:30'),
('9:30', '10:00'),
('10:00', '10:30'),
('10:30', '11:00'),
('11:00', '11:30'),
('11:30', '12:00'),
('12:00', '12:30'),
('12:30', '13:00'),
('13:00', '13:30'),
('13:30', '14:00'),
('14:00', '14:30'),
('14:30', '15:00'),
('15:00', '15:30'),
('15:30', '16:00'),
('16:00', '16:30'),
('16:30', '17:00'),
('17:00', '17:30'),
('17:30', '18:00'),
('18:00', '18:30'),
('18:30', '19:00'),
('19:00', '19:30'),
('19:30', '20:00'),
('20:00', '20:30'),
('20:30', '21:00'),
('21:00', '21:30'),
('21:30', '22:00'),
('22:00', '22:30'),
('22:30', '23:00'),
('23:00', '23:30'),
('23:30', '23:59:59.9999999');

现在我可以运行:

SELECT e.UserID
    ,case when e.StatusStart >= t.SplitStart then e.StatusStart else t.SplitStart end as SplitStatusStart
    ,case when e.StatusEnd   <= t.SplitEnd   then e.StatusEnd   else t.SplitEnd   end as SplitStatusEnd
    ,e.StatusKey
    ,datediff(second, case when e.StatusStart >= t.SplitStart then e.StatusStart else t.SplitStart end, 
        case when e.StatusEnd   <= t.SplitEnd   then e.StatusEnd   else t.SplitEnd   end) SplitStatusDuration
FROM EntryTable e
INNER JOIN SplitTimes t
    ON  e.StatusStart <= t.SplitEnd
    AND e.StatusEnd >= t.SplitStart

它可以使用日期时间而不是时间,并且可以在午夜工作。它只是额外摆弄从日期开始的时间,并重新添加另一个。

这样做的好处是不是递归CTE,它可能在大型桌面上表现更好。

答案 1 :(得分:0)

你可以像这样使用recursive CTE

create table #temp (UserID nvarchar(100), StatusStart time, StatusEnd time, StatusKey nvarchar(100), StateDuration int)
insert into #temp (UserID, StatusStart, StatusEnd, StatusKey, StateDuration) values
    ('Joe1', '8:59:46', '9:08:06', 'Available', 500),
    ('Joe2', '8:59:46', '10:08:06', 'Available', 500)

;WITH cte AS (
    SELECT UserID, StatusStart, CONVERT(time, DATEADD(hour, DATEDIFF(hour, 0, StatusStart) + 1, 0)) AS t, StatusEnd, StatusKey, StateDuration FROM #temp
    UNION ALL
    SELECT UserID, t, CONVERT(time, DATEADD(hour, 1, t)) AS t, StatusEnd, StatusKey, StateDuration FROM cte
    WHERE t < StatusEnd
)
SELECT UserID, StatusStart, CASE WHEN t > StatusEnd THEN StatusEnd ELSE t END AS t, StatusKey, StateDuration FROM cte ORDER BY UserID, t

drop table #temp

CTE选择开始时间和下一个小时,然后使用最后一个小时作为下一个开始时间的联盟,直到下一个小时大于StatusEnd。我必须在time添加一些转化为DATEADD,因为datetimes会返回C

结果:

enter image description here

答案 2 :(得分:0)

另一个选项

示例

Declare @YourTable Table ([UserID] varchar(50),[StatusStart] time,[StatusEnd] time,[StatusKey] varchar(50),[StateDuration] int)
Insert Into @YourTable Values 
 ('Joe1','8:59:46','9:08:06','Available',500)
,('ZZZZ','8:59:46','10:08:06','Available',500)  -- Added multi hour


Select A.UserID
      ,StatusStart = convert(time,IIF(DatePart(HOUR,A.StatusStart)=H,StatusStart,DateAdd(HOUR,H,0)))
      ,StatusEnd   = convert(time,IIF(DatePart(HOUR,A.StatusStart)=H ,DateAdd(HOUR,H+1,0),IIF(H<DatePart(HOUR,StatusEnd),DateAdd(HOUR,H+1,0),StatusEnd)))
      ,A.StatusKey
      ,A.StateDuration 
 From @YourTable A
 Join (
        Select Top 24 H=-1+Row_Number() Over (Order By (Select NULL))
         From  master..spt_values n1
      ) B on H between DatePart(HOUR,A.StatusStart) and DatePart(HOUR,A.StatusEnd)

<强>返回

enter image description here

答案 3 :(得分:0)

这是一个内联表值函数解决方案,它使用数字表来创建时间段。我还为您的测试用例创建了一个示例表,以演示每个请求的输出。

DROP FUNCTION IF EXISTS itvf_Segments;
GO

CREATE FUNCTION itvf_Segments(@start TIME,@end TIME)
RETURNS TABLE
AS
RETURN
(
    WITH N1 (Number) AS (SELECT 1 UNION ALL SELECT 1)
        ,N2 (Number) AS (SELECT 1 FROM N1 CROSS JOIN N1 AS N2)
        ,N3 (Number) AS (SELECT 1 FROM N2 CROSS JOIN N2 AS N3)
        ,N4 (Number) AS (SELECT 1 FROM N3 CROSS JOIN N3 AS N4)
        ,Numbers (Number) AS (SELECT TOP 48 ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1 FROM N4)
        ,Times (StartTime,EndTime) AS (SELECT TIMEFROMPARTS(Number/2,Number%2*30,0,0,0),DATEADD(minute,30,TIMEFROMPARTS(Number/2,Number%2*30,0,0,0)) FROM Numbers)
    SELECT 
        CASE WHEN @start > T.StartTime THEN @start ELSE T.StartTime END AS NewStatusStart
        ,CASE WHEN @end < T.EndTime THEN @end ELSE T.EndTime END AS NewStatusEnd
    FROM Times AS T
    WHERE @start <= T.EndTime
        AND @end >= T.StartTime
)
GO
;

DROP TABLE IF EXISTS UserStatus;

CREATE TABLE UserStatus
(
    UserID VARCHAR(10)
    ,StatusStart TIME
    ,StatusEnd TIME
    ,StatusKey VARCHAR(25)
    ,StateDuration SMALLINT
)
;

INSERT INTO UserStatus
(UserID,StatusStart,StatusEnd,StatusKey,StateDuration)
VALUES
('Joe1','08:59:46','09:08:06','Available',500)
,('Joe2','08:59:46','10:08:06','Available',500)
;

SELECT *
FROM UserStatus
    CROSS APPLY itvf_Segments(StatusStart,StatusEnd)
ORDER BY UserID,NewStatusStart,NewStatusEnd
;

enter image description here