- 将两个日期之间的范围变成一个集合。
- 提取每个小时的开始和结束的 DATEPART(HOUR)。
- 仅在结束 = 22 时计算小时
这是我最终得到的查询,假设您真的不会超出 4 天(96 小时)的范围。如果您的范围超过 100 小时,您将需要 MAXRECURSION 提示:
/* basic test harness - just change @test to change the range to use */
DECLARE @test int = 1, @d1 datetime, @d2 datetime, @lb datetime;
SELECT @d1 = d1, @d2 = d2, @lb = SMALLDATETIMEFROMPARTS(
YEAR(d1),MONTH(d1),DAY(d1),DATEPART(HOUR,d1),0)
FROM (SELECT * FROM (VALUES
(1, '2021-06-01T05:00:00.000', '2021-06-03T05:00:00.000'),
(2, '2021-06-22T04:55:00.000', '2021-06-22T16:45:00.000'),
(3, '2021-06-01T10:50:00.000', '2021-06-01T22:50:00.000'))
AS t(t,d1,d2)) AS t WHERE t = @test;
;WITH n(d) AS (
SELECT DATEADD(HOUR, 1, @lb) UNION ALL
SELECT DATEADD(HOUR, 1, d) FROM n WHERE d < @d2
),
d(d) AS (
SELECT d FROM n UNION ALL SELECT d FROM (VALUES(@d1),(@d2)) AS v(d)
),
seg(s, e, starting_hour, ending_hour, diff_seconds) AS (
SELECT LAG(d,1) OVER (ORDER BY d), d,
DATEPART(HOUR, LAG(d,1) OVER (ORDER BY d)),
DATEPART(HOUR, d), DATEDIFF(SECOND, LAG(d,1) OVER (ORDER BY d), d)
FROM d
), src AS
(
SELECT *, count_it = CASE
WHEN (ending_hour <= 6 OR starting_hour >= 22) AND e <= @d2
THEN COALESCE(diff_seconds,0)/60/60.0 ELSE 0 END
FROM seg
)
SELECT CONVERT(decimal(12,6), SUM(count_it)) FROM src;
@test = 1 (fiddle)、@test = 2 (fiddle)、@test = 3 (fiddle) 的结果:
1 2 3
--------- --------- ---------
16.000000 1.083333 0.833333
如果你想避免递归,你可以创建一个函数来生成一系列数字(一些很棒的背景信息in this series);我为 SQL 2016+ 选择了一个简单的:
CREATE FUNCTION dbo.GetNums_Split(@low bigint,@high bigint)
RETURNS table AS RETURN
WITH spaces AS (SELECT REPLICATE(CONVERT(varchar(max),' '),
CONVERT(integer, CEILING(SQRT(CONVERT(float,
IIF(@high >= @low, @high - @low + 1, 0)))))) AS spc),
splt AS (SELECT CONVERT(bit,NULL) b FROM spaces
CROSS APPLY STRING_SPLIT(spaces.spc,' ')),
Nums AS (SELECT ROW_NUMBER() OVER(ORDER BY @@SPID) AS rownum
FROM splt AS A, splt AS B)
SELECT TOP(@high - @low + 1) rownum AS rn,
@high + 1 - rownum AS op,
@low - 1 + rownum AS n
FROM Nums ORDER BY rownum;
GO
然后你可以用这个替换第一个 CTE:
;WITH n(d) AS (
SELECT DATEADD(HOUR, n, @lb)
FROM dbo.GetNums_Split(1,DATEDIFF(HOUR, @lb, @d2)-1)
),