【问题标题】:T-SQL - Minutes per hour between two datetimesT-SQL - 两个日期时间之间的每小时分钟数
【发布时间】:2016-12-05 11:34:14
【问题描述】:

我必须关注数据:

 | tid | startdate        | enddate                |
 | 1   | 2016-12-26 12:30 | 2016-12-26 15:30       |
 | 2   | 2016-12-26 13:15 | 2016-12-26 15:15       |

我想创建一个带有小时数的结果,然后是日期时间在该小时内的分钟数。

示例结果:

 | tid | hour | minutes_in |
 | 1   | 12   | 30         |
 | 1   | 13   | 60         |
 | 1   | 14   | 60         |
 | 1   | 15   | 30         |
 | 2   | 13   | 45         |
 | 2   | 14   | 60         |
 | 2   | 15   | 15         |

有什么建议吗?

【问题讨论】:

  • 分钟是如何隔离的
  • 我很好奇 - 你为什么需要这个结果?这里是否有更大的目标,而这只是迈出的一步?感觉这有可能成为XY Problem
  • 每个 tid 是一个用户登录/注销时间。我需要每小时登录的分钟数。因此,如果三个用户在 12:00 到 13:00 之间登录 30 分钟,则在 12 小时内登录时间为 90 分钟。

标签: sql-server tsql date datetime


【解决方案1】:

首先,您需要一个数字表来获取从 0 到 23 的小时数,这可以很容易地使用表值构造函数即时创建:

SELECT N
FROM (VALUES 
        (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),
        (13),(14),(15),(16),(17),(18),(19),(20),(21),(22),(23)
    ) n (N);

然后,您可以将其加入原始数据,以将行拆分为所需的数量。然后你只需要一个 case 表达式来应用正确的逻辑来计算分钟:

WITH Numbers (Number) AS
(   SELECT N
    FROM (VALUES 
            (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),
            (13),(14),(15),(16),(17),(18),(19),(20),(21),(22),(23)
        ) n (N)
), SampleData (tid, StartDate, EndDate) AS
(   SELECT  tid, CONVERT(DATETIME2, StartDate), CONVERT(DATETIME2, EndDate)
    FROM (VALUES 
            (1, '2016-12-26 12:30', '2016-12-26 15:30'),
            (2, '2016-12-26 13:15', '2016-12-26 15:15')
        ) d (tid, StartDate, EndDate)
)
SELECT  d.tid,
        [Hour] = n.Number,
        Minutes_in = CASE  
                        -- SPECIAL CASE: START HOUR = END HOUR
                        WHEN DATEPART(HOUR, d.StartDate) = DATEPART(HOUR, d.EndDate) 
                            THEN DATEDIFF(MINUTE, d.StartDate, d.EndDate)

                        -- FULL HOURS IN BETWEEN START AND END
                        WHEN n.Number > DATEPART(HOUR, d.StartDate) 
                            AND n.Number < DATEPART(HOUR, d.EndDate) THEN 60

                        -- START HOUR
                        WHEN n.Number = DATEPART(HOUR, d.StartDate) 
                            THEN 60 - DATEPART(MINUTE, d.StartDate)

                        -- END HOUR
                        WHEN n.Number = DATEPART(HOUR, d.EndDate) 
                            THEN DATEPART(MINUTE, d.EndDate)
                    END
FROM    SampleData d
        INNER JOIN Numbers n 
            ON n.Number >= DATEPART(HOUR, d.StartDate)
            AND n.Number <= DATEPART(HOUR, d.EndDate);

附录

如果你需要跨越几天,那么你可以稍微改变逻辑,生成一组更大的数字来覆盖更多的小时差,而不是加入一天中的小时,加入与小时差的数字开始日期时间到结束日期时间:

SELECT  *
FROM    SampleData d
        INNER JOIN Numbers n 
            ON n.Number <= DATEDIFF(HOUR, d.StartDate, d.EndDate)

这意味着范围跨越几天,然后没有问题,小时只是不断增加。例如

WITH Numbers (Number) AS
(   SELECT ROW_NUMBER() OVER(ORDER BY N1.N) - 1
    FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N1(N)
    CROSS JOIN (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N2 (N)
    CROSS JOIN (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N3 (N)
    CROSS JOIN (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N4 (N)
), SampleData (tid, StartDate, EndDate) AS
(   SELECT  tid, CONVERT(DATETIME2, StartDate), CONVERT(DATETIME2, EndDate)
    FROM (VALUES 
            (1, '2016-12-26 12:30', '2016-12-26 15:30'),
            (2, '2016-12-26 13:15', '2016-12-26 15:15'),
            (3, '2016-12-26 13:15', '2016-12-27 15:15')
        ) d (tid, StartDate, EndDate)
)
SELECT  d.tid,
        [Date] = CONVERT(DATE, d.StartDate),
        [Hour] = CONVERT(TIME(0), DATEADD(HOUR, DATEPART(HOUR, d.StartDate) + n.Number, 0)),
        Minutes_in = CASE  
                        -- SPECIAL CASE: START HOUR = END HOUR
                        WHEN DATEPART(HOUR, d.StartDate) = DATEPART(HOUR, d.EndDate)
                            AND DATEDIFF(DAY, d.StartDate, d.EndDate) = 0
                            THEN DATEDIFF(MINUTE, d.StartDate, d.EndDate)

                        -- START HOUR
                        WHEN n.Number = 0
                            THEN 60 - DATEPART(MINUTE, d.StartDate)

                        -- END HOUR
                        WHEN n.Number = DATEDIFF(HOUR, d.StartDate, d.EndDate) 
                            THEN DATEPART(MINUTE, d.EndDate)

                        -- FULL HOURS IN BETWEEN START AND END
                        ELSE 60

                    END
FROM    SampleData d
        INNER JOIN Numbers n 
            ON n.Number <= DATEDIFF(HOUR, d.StartDate, d.EndDate)
ORDER BY d.tid, n.Number;

【讨论】:

  • 干得好,我打算用小时和 60 - x 做类似的事情,但没有那么快到达那里。投赞成票。 :)
  • 这正是我所要求的 - ty。虽然现在我看到了结果,但我意识到我也需要日期。由于 startdate 和 enddate 并不总是在同一天 - 可能跨越多天。我会尝试根据您的意见来做。
  • @phicon 我已经添加了一些关于当范围超过一天或多天时如何解决这个问题的额外信息。
【解决方案2】:

方法-I

您可以使用 UDF(另一种最简单的方式)实现此目的

让我们为您提供的数据构建架构

CREATE TABLE #TAB ( TID INT, STARTDATE DATETIME, ENDDATE DATETIME)

INSERT INTO #TAB
SELECT 1,'2016-12-26 12:30','2016-12-26 15:30'
UNION ALL
SELECT  2,'2016-12-26 13:15','2016-12-26 15:15' 

创建一个 UDF 以生成 from 和 To 之间的值

ALTER FUNCTION [dbo].[FN_GENERATE] (@FROM_NBR INT, @TO_NBR INT)
RETURNS
 @RESULT TABLE(HR INT)
AS
BEGIN
;WITH CTE AS 
(
    SELECT   @FROM_NBR AS FROM_NBR,@TO_NBR AS TO_NBR
    UNION ALL
    SELECT  FROM_NBR+1 ,TO_NBR    FROM CTE WHERE FROM_NBR<TO_NBR


)
INSERT INTO @RESULT
SELECT FROM_NBR FROM CTE

   RETURN
END

现在通过调用函数来查询数据。

;WITH CTE AS (
SELECT  TID,STARTDATE,ENDDATE,DATEPART(HH,STARTDATE) FROM_HR , DATEPART(HH,ENDDATE) TO_HR FROM #TAB T
)
SELECT C1.TID,F.HR,  COALESCE(DATEPART(MINUTE,FRM_HR_MINUTS.STARTDATE),DATEPART(MINUTE,TO_HR_MINUTS.ENDDATE),60 ) 
FROM CTE C1
CROSS APPLY
(
    SELECT * FROM DBO.[FN_GENERATE] (C1.FROM_HR, C1.TO_HR)
)AS F
LEFT JOIN CTE FRM_HR_MINUTS ON C1.TID= FRM_HR_MINUTS.TID AND DATEPART(HH,FRM_HR_MINUTS.STARTDATE)= F.HR 
LEFT JOIN CTE TO_HR_MINUTS ON C1.TID= TO_HR_MINUTS.TID AND DATEPART(HH,TO_HR_MINUTS.ENDDATE)= F.HR 

编辑:

方法-二

不使用UDF & 使用MASTER.DBO.SPT_VALUES

;WITH CTE AS (

--PREPARING START HR, END HR, START_MIN, END_MIN  FROM #TAB
SELECT  TID,STARTDATE,ENDDATE
,DATEPART(HH,STARTDATE) FROM_HR 
, DATEPART(HH,ENDDATE) TO_HR
, DATEPART(MINUTE, STARTDATE) AS STARTMIN
, DATEPART(MINUTE, ENDDATE) ENDMIN 

FROM #TAB T
)

SELECT TID
, NUMBER AS HRS
--if Outer APply produce Null Display Minutes from CTE else 60 Mins
, CASE ISNULL(OA.FRM_MINS, C1.STARTMIN) + ISNULL(TO_MINS,C1.ENDMIN)  
        WHEN 0
            THEN 60
        ELSE ISNULL(OA.FRM_MINS,  C1.STARTMIN) + ISNULL(TO_MINS,C1.ENDMIN)
        END AS MINS
FROM CTE C1
OUTER APPLY  --JOINING NUMBERS BETWEEN FROM_HR & TO_HR using MASTER.DBO.SPT_VALUES 
(
    SELECT NUMBER
    --IF FROM_HR matched NULL Else 0
    , CASE C1.FROM_HR WHEN NUMBER THEN NULL ELSE 0 END AS FRM_MINS  

    --IF TO_HR matched NULL Else 0 
    ,CASE C1.TO_HR WHEN NUMBER THEN NULL ELSE 0 END AS TO_MINS       

    FROM MASTER.DBO.SPT_VALUES 
    WHERE [type]='P' AND number>0 AND number BETWEEN FROM_HR AND TO_HR

)AS OA

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-01-17
    • 2012-05-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-08
    • 1970-01-01
    相关资源
    最近更新 更多