如何在 SQL 中将时间划分到下一个工作日?

How can I divide hours to next working days in SQL?

我有一个 table 存储开始日期和小时数。我还有另一个时间 table 作为工作日的参考。我的主要目标是将这些时间分配给工作日。

例如:

ID   Date         Hour
1     20210504     40

我希望它的结构为

ID      Date         Hour
1       20210504     8
1       20210505     8
1       20210506     8
1       20210507     8
1       20210510     8

我设法用给定的代码划分时间,但无法在工作日完成。

WITH cte1 AS 
(
  select 1 AS ID, 20210504 AS Date, 40 AS Hours --just a test case
), working_days AS 
(
select date from dateTable
),
cte2 AS 
(
select ID, Date, Hours, IIF(Hours<=8, Hours, 8) AS dailyHours FROM cte1

UNION ALL

SELECT 
cte2.ID,
cte2.Date + 1
,cte2.Hours - 8
,IIF(Hours<=8, Hours, 8)
FROM cte2
JOIN cte1 t ON cte2.ID = t.ID
WHERE cte2.HOURS > 8 AND cte2.Date + 1 IN (select * from working_days)

当我像这样使用它时,它只给我这个输出缺少一天

ID      Date         Hour
1       20210504     8
1       20210505     8
1       20210506     8
1       20210507     8

您可以使用递归 CTE。这应该可以解决问题:

with cte as (
      select id, date, 8 as hour, hour as total_hour
      from t
      union all
      select id, dateadd(day, 1, date), 
             (case when total_hour < 8 then total_hour else 8 end),
             total_hour - 8
      from cte
      where total_hour > 0
     )
select *
from cte;

注意:这里假定 total_hour 至少为 8,只是为了避免在 CTE 的锚点部分出现 case 表达式。可以简单地添加。

此外,如果可能超过 100 天,您将需要 option (maxrecursion 0)

要解决您的问题,您需要以正确的方式构建您的日历, 还向 working_days 添加 ROW_NUMBER 以获得正确的进展。

declare @date_start date = '2021-05-01'

;WITH
cte1 AS (
    SELECT * FROM 
    (VALUES
    (1, '20210504', 40),
    (2, '20210505', 55),
    (3, '20210503', 44)
    ) X (ID, Date, Hour)
),
numbers as (
    SELECT ROW_NUMBER() over (order by o.object_id) N
    FROM sys.objects o
),
cal as (
    SELECT cast(DATEADD(day, n, @date_start) as date) d, n-1 n
    FROM numbers n
    where n.n<32
),
working_days  as (
    select d, ROW_NUMBER() over (order by n) dn
    from cal
    where DATEPART(weekday, d) < 6 /* monday to friday in italy (country dependent) */
),
base as (
    SELECT t.ID, t.Hour, w.d, w.dn
    from cte1 t
    join working_days w on w.d = t.date
)
SELECT t.ID, w.d, iif((8*n)<=Hour, 8, 8 + Hour - (8*n) ) h
FROM base t
join numbers m on m.n <= (t.Hour / 8.0) + 0.5
join working_days w on w.dn = t.dn + N -1
order by 1,2