我正在尝试获取两个 datetime2 字段(不包括周末)之间的差异。
我只能在日期上做到这一点,但很难包含时间:
WITH cte AS (
SELECT
start_datetime, end_datetime,
cast(datediff(dd, start_datetime, end_datetime) -
(datediff(wk, start_datetime, end_datetime) * 2) -
case when datepart(dw, start_datetime) = 1 then 1 else 0 end +
case when datepart(dw, end_datetime) = 1 then 1 else 0 end
as varchar(10)) as datediff_,
DATEDIFF(SECOND, right(start_datetime, 16), right(end_datetime, 16)) timediff_
from mytable
)
SELECT *, datediff_ + ' days, ' +
CONVERT(VARCHAR(5), timediff_/60/60)
+ ':' + RIGHT('0' + CONVERT(VARCHAR(2), timediff_/60%60), 2)
+ ':' + RIGHT('0' + CONVERT(VARCHAR(2), timediff_ % 60), 2) durationwoweekends
FROM cte;
当 timediff_ 为正时,这确实计算并返回良好,但当为负时,我得到一个错误的输出:
我的目标是durationwoweekends列的输出格式是days, hh:mm:ss现在输出一个varchar,这可能是错误的,并且还强制字符串“days,”,但最终需要该字段为持续时间数据类型。
测试数据:
CREATE TABLE #temptable(
start_datetime datetime2,
end_datetime datetime2
)
Insert into #temptable(start_datetime, end_datetime) values ('2022-10-31 10:30:52', '2022-11-02 23:47:55')
Insert into #temptable(start_datetime, end_datetime) values ('2022-11-01 08:30:46', '2022-11-03 02:59:27')
select * from #temptable
我正在使用 SQL Server 2019。 任何人的任何帮助将不胜感激。
忽略(或调整)周末日期,但同时不调整时间的情况是不常见的。对于以下情况,这可能会导致奇怪的结果:
如果忽略周末,我相信你应该完全忽略它们,包括任何时间组成部分。这可以通过将周六 00:00 到周日 24:00 之间的所有日期时间映射为相当于周一 00:00 来完成。这将使计算结果连续且一致。实际上,已用计时器从周六 00:00 到周日 24:00(周一 00:00)暂停。
至于计算显示的日期和时间分量,我建议:
以下内容将实现此目的:
--SET DATEFIRST 7 -- US
--SET DATEFIRST 1 -- Mostly everywhere else
SET DATEFIRST 6 -- Maybe
-- The weekday value from DATEPART(weekday) is locale/settings dependent
-- So we need to calculate the expected values
-- For @@DateFirst = 7, @SaturdayDW = 7 and @SundayDW = 1
-- For @@DateFirst = 1, @SaturdayDW = 6 and @SundayDW = 7
-- For @@DateFirst = 6, @SaturdayDW = 1 and @SundayDW = 2
-- DATEDIFF(week, ...) is unaffected by the DateFirst setting
DECLARE @SaturdayDW INT = DATEPART(weekday, '20000101') -- Reference Saturday
DECLARE @SundayDW INT = DATEPART(weekday, '20000102') -- Reference Sunday
--SELECT @@DateFirst AS DateFirst, @SaturdayDW AS SaturdayDW, @SundayDW SundayDW
SELECT
DATEPART(dw, T.StartDateTime) AS dw,
DATEPART(dw, T.EndDateTime) AS edw,
DATEDIFF(week, T.StartDateTime, T.EndDateTime) AS DiffWeek,
T.TestSet, T.StartDateTime, T.EndDateTime,
LEFT(DATENAME(dw, T.StartDateTime), 3) AS SDay,
LEFT(DATENAME(dw, T.EndDateTime), 3) AS EDay,
FMT.AdjustedStart,
FMT.AdjustedEnd,
FMT.Elapsed
FROM TestData T
CROSS APPLY (
-- Adjust weekend dates forward to 00:00:00 of the following Monday
SELECT
CASE DATEPART(dw, T.StartDateTime)
WHEN @SaturdayDW
THEN CONVERT(DATETIME, DATEADD(day, 2, CONVERT(DATE, T.StartDateTime)))
WHEN @SundayDW
THEN CONVERT(DATETIME, DATEADD(day, 1, CONVERT(DATE, T.StartDateTime)))
ELSE T.StartDateTime
END AS AdjStart,
CASE DATEPART(dw, T.EndDateTime)
WHEN @SaturdayDW
THEN CONVERT(DATETIME, DATEADD(day, 2, CONVERT(DATE, T.EndDateTime)))
WHEN @SundayDW
THEN CONVERT(DATETIME, DATEADD(day, 1, CONVERT(DATE, T.EndDateTime)))
ELSE T.EndDateTime
END AS AdjEnd
) A
CROSS APPLY (
-- Calculate raw differences
SELECT
DATEDIFF(second, AdjStart, AdjEnd) AS Seconds,
DATEDIFF(week, AdjStart, AdjEnd) AS Weeks
) ES
CROSS APPLY (
-- Calculate and adjust for days, calculate separate time, handle negatives
SELECT
CASE WHEN ES.Seconds < 0 THEN '-' ELSE '' END AS Sign,
ABS(ES.Seconds) / 86400 - 2 * ABS(Weeks) AS Days,
DATEADD(second, ABS(ES.Seconds) % 86400, 0) AS Time
) EP
CROSS APPLY (
-- Format intermediate data and results
SELECT
CONCAT(
EP.Sign,
EP.Days,
CASE WHEN EP.Days = 1 THEN ' Day, ' ELSE ' Days, ' END,
CONVERT(CHAR(8), EP.Time, 108)
) AS Elapsed,
CONCAT(
LEFT(DATENAME(dw, A.AdjStart), 3),
', ',
CONVERT(CHAR(8), A.AdjStart, 108)
) AS AdjustedStart,
CONCAT(
LEFT(DATENAME(dw, A.AdjEnd), 3),
', ',
CONVERT(CHAR(8), A.AdjEnd, 108)
) AS AdjustedEnd
) FMT
ORDER BY T.TestSet, T.StartDateTime, T.EndDateTime
上面使用CROSS APPLY
可以方便地封装中间计算,避免子表达式的重复,并减少最终选择列表中的混乱。
DATEDIFF(second, ...)
产生的整数结果有效期长达约 38 年。如果需要更长的间隔,可以使用DATEDIFF_BIG()
。
结果:
测试 设置 |
开始 日期/时间 |
结束 日期/时间 |
纪念日 | 电子日 | 调整开始 | 调整结束 | 已过去 |
---|---|---|---|---|---|---|---|
1 | 2022-10-31 10:30:52 | 2022-10-31 10:30:52 | 周一 | 周一 | 周一,10:30:52 | 周一,10:30:52 | 0 天,00:00:00 |
1 | 2022-10-31 10:30:52 | 2022-11-02 23:47:55 | 周一 | 周三 | 周一,10:30:52 | 周三,23:47:55 | 2 天,13:17:03 |
1 | 2022-11-02 23:47:55 | 2022-10-31 10:30:52 | 周三 | 周一 | 周三,23:47:55 | 周一,10:30:52 | -2 天,13:17:03 |
2 | 2022-11-01 08:30:46 | 2022-11-03 02:59:27 | 周二 | 周四 | 周二,08:30:46 | 周四,02:59:27 | 1 天,18:28:41 |
2 | 2022-11-03 02:59:27 | 2022-11-01 08:30:46 | 周四 | 周二 | 周四,02:59:27 | 周二,08:30:46 | -1 天,18:28:41 |
... | |||||||
3 | 2023-12-01 08:00:00 | 2023-12-01 16:45:00 | 周五 | 周五 | 周五,08:00:00 | 周五 16:45:00 | 0 天,08:45:00 |
3 | 2023-12-01 08:00:00 | 2023-12-02 08:00:00 | 周五 | 周六 | 周五,08:00:00 | 周一,00:00:00 | 0 天,16:00:00 |
3 | 2023-12-01 08:00:00 | 2023-12-03 16:45:00 | 周五 | 太阳 | 周五,08:00:00 | 周一,00:00:00 | 0 天,16:00:00 |
3 | 2023-12-01 08:00:00 | 2023-12-04 16:45:00 | 周五 | 周一 | 周五,08:00:00 | 周一,16:45:00 | 1 天,08:45:00 |
... | |||||||
3 | 2023-12-02 08:00:00 | 2023-12-01 16:45:00 | 周六 | 周五 | 周一,00:00:00 | 周五 16:45:00 | -0 天,07:15:00 |
3 | 2023-12-02 08:00:00 | 2023-12-03 16:45:00 | 周六 | 太阳 | 周一,00:00:00 | 周一,00:00:00 | 0 天,00:00:00 |
3 | 2023-12-02 08:00:00 | 2023-12-04 08:00:00 | 周六 | 周一 | 周一,00:00:00 | 周一,08:00:00 | 0 天,08:00:00 |
3 | 2023-12-02 08:00:00 | 2023-12-04 16:45:00 | 周六 | 周一 | 周一,00:00:00 | 周一,16:45:00 | 0 天,16:45:00 |
3 | 2023-12-02 08:00:00 | 2023-12-3 75e0 1 23:59:59 | 周六 | 太阳 | 周一,00:00:00 | 周一,00:00:00 | 20 天,00:00:00 |
... | |||||||
4 | 2023-12-01 10:00:05 | 2023-12-11 14:30:25 | 周五 | 周一 | 周五,10:00:05 | 周一,14:30:25 | 6 天,04:30:20 |
请参阅 this db<>fiddle 以获取包含各种测试数据的演示。