【问题标题】:Total Minutes by Hour按小时计算的总分钟数
【发布时间】:2015-05-01 02:09:33
【问题描述】:

我在数据表中有两个字段 - “startTime”和“endTime”。这两个字段表示用户在特定任务上花费的持续时间。这些是 varchar 字段。因此,假设我们的 startTime 为“21:05:00”,endTime 为“22:09:00”。我需要代码将第 21 小时和 2200 小时分别花费的总分钟数相加(即 9 分钟)。因此,不仅仅是简单的分钟差异,而是按小时细分。

最好的方法是什么?

到目前为止,我已经创建了一个表,它将返回 24 小时内所有可能的时间。这是一个示例:

Hour    startTime                endTime
0       2015-01-01 00:00:00.000  2015-01-01 01:00:00.000
1       2015-01-01 01:00:00.000  2015-01-01 02:00:00.000
2       2015-01-01 02:00:00.000  2015-01-01 03:00:00.000

我已将 startTime 字段从 varchar 转换为 dateteime 并将其命名为 sessionHour:

Convert(datetime, startTime) As sessionHour

此外,我可以通过以下方式获得 startTime 的小时数:

DateAdd(Minute, 60 * (DateDiff(Minute, 0, startTime) / 60), 0)  As hourOf

除此之外,我不知道如何解析每小时的分钟数。

【问题讨论】:

    标签: sql-server sql-server-2005


    【解决方案1】:

    你很亲密。您只需要将数字表与数据结合起来。我将使用CROSS APPLY。这是SQLFiddle 的最终解决方案。

    样本数据

    DECLARE @Durations TABLE (ID int IDENTITY(1,1), StartTime datetime, EndTime datetime);
    
    INSERT INTO @Durations VALUES
    ('2015-01-01 21:05:00', '2015-01-01 22:09:00'),
    ('2015-01-01 01:05:00', '2015-01-01 01:20:00'),
    ('2015-01-01 11:05:00', '2015-01-01 13:09:00'),
    ('2015-01-01 15:05:00', '2015-01-01 17:50:00'),
    ('2015-01-01 16:30:00', '2015-01-01 17:20:00');
    

    我将从一开始就使用datetime 类型,因为您已将varchar 值转换为正确的datetime

    我将使用一个数字表。它的行数应与数据中最长的持续时间(以小时为单位)一样多。它可能超过 24 个。通常,在数据库中为其他报告提供此类表很有用。

    DECLARE @Numbers TABLE (Number int);
    INSERT INTO @Numbers VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10);
    -- Number of rows in this table should be more than the longest duration in hours
    

    我需要一些参考时间点来删除datetime 的微小部分。它可以是任何日期时间,只要它没有分钟和秒。

    DECLARE @VarStart datetime;
    SET @VarStart = '2000-01-01';
    

    主要步骤 - 展开数据

    SELECT *
    FROM
        @Durations AS D
        CROSS APPLY
        (
            SELECT N.Number
            FROM @Numbers AS N
            WHERE N.Number <= DATEDIFF(hour, StartTime, EndTime)
        ) AS CA_Number
    ORDER BY ID;
    
    ID   StartTime              EndTime                Number
    1    2015-01-01 21:05:00    2015-01-01 22:09:00    0
    1    2015-01-01 21:05:00    2015-01-01 22:09:00    1
    2    2015-01-01 01:05:00    2015-01-01 01:20:00    0
    3    2015-01-01 11:05:00    2015-01-01 13:09:00    0
    3    2015-01-01 11:05:00    2015-01-01 13:09:00    1
    3    2015-01-01 11:05:00    2015-01-01 13:09:00    2
    4    2015-01-01 15:05:00    2015-01-01 17:50:00    0
    4    2015-01-01 15:05:00    2015-01-01 17:50:00    1
    4    2015-01-01 15:05:00    2015-01-01 17:50:00    2
    5    2015-01-01 16:30:00    2015-01-01 17:20:00    0
    5    2015-01-01 16:30:00    2015-01-01 17:20:00    1
    

    您可以看到我们根据原始行的持续时间为每个原始行创建了几行。剩下的就是简单的算术。

    每小时分钟数

    SELECT *
        ,DATEDIFF(minute, MaxStart, MinEnd) AS MinutesPerHour
    FROM
        @Durations AS D
        CROSS APPLY
        (
            SELECT N.Number
            FROM @Numbers AS N
            WHERE N.Number <= DATEDIFF(hour, StartTime, EndTime)
        ) AS CA_Number
        CROSS APPLY
        (
            SELECT
                DATEADD(hour, CA_Number.Number, StartTime) AS HourStart
                ,DATEADD(hour, CA_Number.Number+1, StartTime) AS HourEnd
        ) AS CA_HourEnd
        CROSS APPLY
        (
            -- Truncate to 1 hour.
            SELECT
                DATEADD(hour, DATEDIFF(hour, @VarStart, HourStart), @VarStart) AS HourStartFinal
                ,DATEADD(hour, DATEDIFF(hour, @VarStart, HourEnd), @VarStart) AS HourEndFinal
        ) AS CA_HourEndFinal
        -- Intersect intervals [StartTime, EndTime] with [HourStartFinal, HourEndFinal]
        CROSS APPLY
        (
            SELECT
                CASE WHEN StartTime > HourStartFinal THEN StartTime ELSE HourStartFinal END AS MaxStart
                ,CASE WHEN EndTime < HourEndFinal THEN EndTime ELSE HourEndFinal END AS MinEnd
        ) AS CA_Intersect
    ORDER BY ID;
    

    CA_HourEndCA_HourEndFinal 中,我使用Number 计算小时边界。然后相交两个区间并计算每个相交的分钟数。这是结果集:

    ID   StartTime              EndTime                Number    HourStart              HourEnd                HourStartFinal         HourEndFinal           MaxStart               MinEnd                 MinutesPerHour
    1    2015-01-01 21:05:00    2015-01-01 22:09:00    0         2015-01-01 21:05:00    2015-01-01 22:05:00    2015-01-01 21:00:00    2015-01-01 22:00:00    2015-01-01 21:05:00    2015-01-01 22:00:00    55
    1    2015-01-01 21:05:00    2015-01-01 22:09:00    1         2015-01-01 22:05:00    2015-01-01 23:05:00    2015-01-01 22:00:00    2015-01-01 23:00:00    2015-01-01 22:00:00    2015-01-01 22:09:00    9
    2    2015-01-01 01:05:00    2015-01-01 01:20:00    0         2015-01-01 01:05:00    2015-01-01 02:05:00    2015-01-01 01:00:00    2015-01-01 02:00:00    2015-01-01 01:05:00    2015-01-01 01:20:00    15
    3    2015-01-01 11:05:00    2015-01-01 13:09:00    0         2015-01-01 11:05:00    2015-01-01 12:05:00    2015-01-01 11:00:00    2015-01-01 12:00:00    2015-01-01 11:05:00    2015-01-01 12:00:00    55
    3    2015-01-01 11:05:00    2015-01-01 13:09:00    1         2015-01-01 12:05:00    2015-01-01 13:05:00    2015-01-01 12:00:00    2015-01-01 13:00:00    2015-01-01 12:00:00    2015-01-01 13:00:00    60
    3    2015-01-01 11:05:00    2015-01-01 13:09:00    2         2015-01-01 13:05:00    2015-01-01 14:05:00    2015-01-01 13:00:00    2015-01-01 14:00:00    2015-01-01 13:00:00    2015-01-01 13:09:00    9
    4    2015-01-01 15:05:00    2015-01-01 17:50:00    0         2015-01-01 15:05:00    2015-01-01 16:05:00    2015-01-01 15:00:00    2015-01-01 16:00:00    2015-01-01 15:05:00    2015-01-01 16:00:00    55
    4    2015-01-01 15:05:00    2015-01-01 17:50:00    1         2015-01-01 16:05:00    2015-01-01 17:05:00    2015-01-01 16:00:00    2015-01-01 17:00:00    2015-01-01 16:00:00    2015-01-01 17:00:00    60
    4    2015-01-01 15:05:00    2015-01-01 17:50:00    2         2015-01-01 17:05:00    2015-01-01 18:05:00    2015-01-01 17:00:00    2015-01-01 18:00:00    2015-01-01 17:00:00    2015-01-01 17:50:00    50
    5    2015-01-01 16:30:00    2015-01-01 17:20:00    0         2015-01-01 16:30:00    2015-01-01 17:30:00    2015-01-01 16:00:00    2015-01-01 17:00:00    2015-01-01 16:30:00    2015-01-01 17:00:00    30
    5    2015-01-01 16:30:00    2015-01-01 17:20:00    1         2015-01-01 17:30:00    2015-01-01 18:30:00    2015-01-01 17:00:00    2015-01-01 18:00:00    2015-01-01 17:00:00    2015-01-01 17:20:00    20
    

    最终查询

    最后,我将分钟按一个小时分组:

    SELECT
        HourStartFinal
        ,SUM(DATEDIFF(minute, MaxStart, MinEnd)) AS SumMinutesPerHour
    FROM
        @Durations AS D
        CROSS APPLY
        (
            SELECT N.Number
            FROM @Numbers AS N
            WHERE N.Number <= DATEDIFF(hour, StartTime, EndTime)
        ) AS CA_Number
        CROSS APPLY
        (
            SELECT
                DATEADD(hour, CA_Number.Number, StartTime) AS HourStart
                ,DATEADD(hour, CA_Number.Number+1, StartTime) AS HourEnd
        ) AS CA_HourEnd
        CROSS APPLY
        (
            -- Truncate to 1 hour.
            SELECT
                DATEADD(hour, DATEDIFF(hour, @VarStart, HourStart), @VarStart) AS HourStartFinal
                ,DATEADD(hour, DATEDIFF(hour, @VarStart, HourEnd), @VarStart) AS HourEndFinal
        ) AS CA_HourEndFinal
        -- Intersect intervals [StartTime, EndTime] with [HourStartFinal, HourEndFinal]
        CROSS APPLY
        (
            SELECT
                CASE WHEN StartTime > HourStartFinal THEN StartTime ELSE HourStartFinal END AS MaxStart
                ,CASE WHEN EndTime < HourEndFinal THEN EndTime ELSE HourEndFinal END AS MinEnd
        ) AS CA_Intersect
    GROUP BY HourStartFinal
    ORDER BY HourStartFinal;
    

    最终结果集

    HourStartFinal             SumMinutesPerHour
    2015-01-01 01:00:00.000    15
    2015-01-01 11:00:00.000    55
    2015-01-01 12:00:00.000    60
    2015-01-01 13:00:00.000    9
    2015-01-01 15:00:00.000    55
    2015-01-01 16:00:00.000    90
    2015-01-01 17:00:00.000    70
    2015-01-01 21:00:00.000    55
    2015-01-01 22:00:00.000    9
    

    SQLFiddle

    【讨论】:

    • 这很好用,我很欣赏小提琴。谢谢!我不清楚的一件事是“数字”表以及它如何连接所有这些?
    • Numbers 表只是有从 0 到足够大的连续数字。在我的数据库中,它有 100K 行。您只需填写一次,并在需要时在许多报告中使用。在此示例中,我显式地添加了几行以使示例工作。在实际系统中,该表已经具有这些数字,并且在 Number 列上将具有主键/唯一索引。该表使用CROSS APPLY 连接到主表,为每个原始行创建几行。例如,如果某行中的持续时间跨越 3 小时,则该行将在 CROSS APPLY 之后出现 3 次。
    【解决方案2】:

    虽然 Vladimir Baranov 的回答是正确的,但它使用了许多 CROSS APPLY 的方式。

    另一种按小时获取分钟的方法,可以使用以下事实:在starttimeendtime 的范围内,除了第一个小时和最后一个小时外,所有其他小时都将有一个小时的minutediff 60.

    我们可以使用它来构建我们的逻辑,就像这样。

    DECLARE @UserTask TABLE (ID int IDENTITY(1,1),UserID INT,TaskID INT, StartTime datetime, EndTime datetime);
    
    INSERT INTO @UserTask VALUES
    (1,1,'2015-01-01 21:05:00', '2015-01-01 22:09:00'),
    (1,1,'2015-01-01 01:05:00', '2015-01-01 01:20:00'),
    (1,1,'2015-01-01 11:05:00', '2015-01-01 13:09:00'),
    (1,1,'2015-01-01 15:05:00', '2015-01-01 17:50:00'),
    (1,1,'2015-01-01 16:30:00', '2015-01-01 17:20:00'),
    (2,2,'2015-01-01 21:05:00', '2015-01-01 22:09:00');
    
    ;WITH CTENum AS 
    (
    SELECT 1 rn UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
    ), CTEHours as 
    (
    SELECT TOP 24 ROW_NUMBER()OVER(ORDER BY c1.rn) - 1 rn FROM CTENum c1 CROSS JOIN CTENum c2
    )
    SELECT ID,UserID,TaskID,StartTime,EndTime,rn as DayHour,
    CASE WHEN r_asc = 1 AND r_desc = 1 THEN DATEDIFF(minute,StartTime,EndTime) 
    WHEN r_asc = 1 THEN 60 - DATEPART(minute,StartTime)
    WHEN r_desc = 1 THEN DATEPART(minute,EndTime)
    ELSE 60 END MinuteTime
    FROM @UserTask
    CROSS APPLY(
    SELECT *,ROW_NUMBER()OVER(ORDER BY rn ASC) r_asc,ROW_NUMBER()OVER(ORDER BY rn DESC) r_desc
    FROM CTEHours C
    WHERE C.rn BETWEEN DATEDIFF(hour,CONVERT(VARCHAR(10),StartTime,112),StartTime) AND DATEDIFF(hour,CONVERT(VARCHAR(10),StartTime,112),EndTime)
    ) N
    ORDER BY ID,DayHour
    

    【讨论】:

    • 你说得对,我经常使用CROSS APPLY 来说明它对复杂公式的用途。我假设 OP 想看到这个答案:第 21 小时 55 分钟;当 startTime="21:05:00" 和 endTime="22:09:00" 时,第 22 小时为 9 分钟。总时长为 55+9=64 分钟。您的查询返回第 21 小时的 5 分钟,而不是 55 分钟。您一定错过了 CASE 公式中的一些比较。此外,当持续时间跨越午夜边界时,该解决方案不起作用。尝试将源数据中的2015-01-01 22:09:00 更改为2015-01-03 22:09:00
    • @Vladimir - 关于初始小时持续时间的好消息。我错过了60 - x。是的,我的解决方案在一天的范围内有效:)。根据 OP 的问题和样本数据,他似乎没有跨越不同日期的记录。
    【解决方案3】:

    您可能想查看datepart() 函数,它会帮助您更清晰地处理日期。例如:

    select @endOfHour = dateadd(hour,
        1,
        datetimefromparts(
            datepart(year, @startTime),
            datepart(month, @startTime),
            datepart(day, @startTime),
            datepart(hour, @startTime),
            0,
            0,
            0));
    
    
    select h.theHour, @startTime, @endOfHour, datediff(minute, @startTime, @endOfHour)
    from ListOfHours as h
    where datepart(hour, @startTime) = h.Hour;
    

    【讨论】:

      猜你喜欢
      • 2023-02-16
      • 1970-01-01
      • 1970-01-01
      • 2021-06-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多