如何在 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
我有一个 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