【问题标题】:T-SQL: Create record for each day in datediffT-SQL:在 datediff 中为每一天创建记录
【发布时间】:2016-07-19 07:04:34
【问题描述】:

我有以下表结构(myTable):

EmployeeID, StartDate, EndDate, Hours
---------------------------------------
1           1/1/2016   1/8/2016 20
2           1/4/2016   1/6/2016 10
3           1/2/2016   1/3/2016 13

我需要将小时数除以开始日期和结束日期的 datediff 并显示每天的记录,如下所示:

1           1/1/2016   1/2/2016 2.85
1           1/2/2016   1/3/2016 2.85
...
1           1/7/2016   1/8/2016 2.85
2           1/4/2016   1/5/2016 5
2           1/5/2016   1/6/2016 5
3           1/2/2016   1/3/2016 13   

小时数应四舍五入到小数点后两位。假设开始日期和结束日期永远不会相同。

如何使用 T-SQL 做到这一点?

编辑:我不是 SQL 专家,所以我没有做太多尝试,因为它看起来不像是一个简单的选择。我在想我需要使用“分区依据”吗?对于几个小时,2.86 也很好。向上或向下舍入无关紧要,只要一致即可。

另外,为了澄清,我不需要 3 行。我需要 10 行。我不只是需要一个简单的

hours / datediff(day, startdate, enddate)

【问题讨论】:

  • 那么,你有什么尝试?
  • @clifton_h 查看我的编辑

标签: sql-server tsql


【解决方案1】:

可以通过加入数字列表来还原该日期范围。
master..spt_values 可以用于此。

要将 [小时] 除以 datediff,首先将其转换为浮点数,然后通过舍入截断为 2 位小数。

select t.EmployeeID,
dateadd(d, v.number, t.StartDate) as StartDate, 
dateadd(d, v.number+1, t.StartDate) as EndDate, 
t.DivHours as [Hours]
from ( 
  select EmployeeID, StartDate, EndDate, 
  round(cast([Hours] as float)/datediff(d, StartDate, EndDate),2,1) as DivHours 
  from myTable
  where EndDate > StartDate 
) t
join master..spt_values v 
  on (v.type='P' and v.number >= 0 and v.number < datediff(d, t.StartDate, t.EndDate));

给予:

EmployeeID StartDate   EndDate     Hours
1          2016-07-01  2016-07-02  2,85
1          2016-07-02  2016-07-03  2,85
1          2016-07-03  2016-07-04  2,85
1          2016-07-04  2016-07-05  2,85
1          2016-07-05  2016-07-06  2,85
1          2016-07-06  2016-07-07  2,85
1          2016-07-07  2016-07-08  2,85
2          2016-07-04  2016-07-05  5
2          2016-07-05  2016-07-06  5
3          2016-07-02  2016-07-03  13

但是,只有当 datediff 低于 2047 时,这才有效。
因为 2047 是您从该系统表中获得的最大数字。
但这仍然是超过 5 年的日期范围。

但是,如果您在该表中有更大的范围。
然后,您可以生成一个包含更多数字的表格。
此示例将 1000000 个数字放入表变量中:

DECLARE @Numbers TABLE (num int primary key);

-- Who dares to claim that cross joins are always useless?
WITH d AS (select n from (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9))q(n))
insert into @Numbers (num)
select (d6.n*100000+d5.n*10000+d4.n*1000+d3.n*100+d2.n*10+d1.n) as num 
from d d1, d d2, d d3, d d4, d d5, d d6;

select count(*) as total, min(num) as min_num, max(num) as max_num from @Numbers;

您也可以为此使用递归。
但如果您想添加额外的列,这种方法有点麻烦。
要从 myTable 添加更多列,您可以在 EmployeeID 上将 myTable 加入 R。

WITH R (EmployeeID, StartDate, EndDate, FinalDate, [Hours]) AS
(
    SELECT EmployeeID, StartDate, dateadd(d, 1, StartDate),
    EndDate as FinalDate,
    round(cast([Hours] as float)/datediff(d, StartDate, EndDate),2,1)
    from myTable
    where StartDate < EndDate
    UNION ALL
    SELECT EmployeeID, dateadd(d, 1, StartDate), dateadd(d, 2, StartDate),
    FinalDate, [Hours]
    FROM R WHERE dateadd(d, 1, StartDate) < FinalDate
)
SELECT EmployeeID, StartDate, EndDate, [Hours]
FROM R
ORDER BY EmployeeID, StartDate, EndDate;

如果分段小时数的总和仍需要等于原始小时数?
然后它变得稍微复杂一些。

declare @myTable TABLE (EmployeeID int, StartDate date, EndDate date, [Hours] int);

insert into @myTable values 
(0,'2016-1-1','2016-1-4',10),
(1,'2016-1-1','2016-1-8',20);

WITH R (EmployeeID, StartDate, EndDate, FinalDate, [Hours], RemainingHours) AS
(
    SELECT EmployeeID, 
    StartDate, 
    dateadd(d, 1, StartDate),
    EndDate,
    round(cast([Hours] as float)/datediff(d, StartDate, EndDate),2,1),
    round(cast([Hours] as float),2,1)
    from @myTable
    where StartDate < EndDate
    UNION ALL
    SELECT EmployeeID, 
    dateadd(d, 1, StartDate),
    dateadd(d, 1, EndDate),
    FinalDate,
    (case when dateadd(d, 1, EndDate) < FinalDate then [Hours] else (RemainingHours - [Hours]) end),
    (RemainingHours - [Hours])
    FROM R WHERE EndDate < FinalDate
)
SELECT EmployeeID, StartDate, EndDate, [Hours]
FROM R
ORDER BY EmployeeID, StartDate, EndDate;

【讨论】:

  • 我使用了你的第二个建议。奇迹般有效。谢谢。
  • 递归看起来像是一些秘密艺术。很高兴听到它有帮助:)
  • 使用递归方法,我们如何将精度损失放在最后一条记录上?因此,如果我们将 10 小时除以 3 天,我们每天得到 3.33。最后一天,我想看 3.34。
  • 这是一个有趣的小挑战。 :) 我已经为答案添加了一个解决方案。
【解决方案2】:

为此,您必须使用 计数表

;WITH Tally AS (
   SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS n
   FROM (VALUES (0), (0), (0), (0), (0), (0)) t1(v)
   CROSS JOIN (VALUES (0), (0), (0), (0), (0), (0)) t2(v)
)
SELECT t1.EmployeeID,
       DATEADD(d, t3.n-1, t1.StartDate), 
       DATEADD(d, t3.n, t1.StartDate), 
       ROUND([Hours] * 1.0 / t2.days, 2)
FROM mytable AS t1
CROSS APPLY (SELECT DATEDIFF(d, t1.StartDate, t1.EndDate)) As t2(days)
JOIN Tally AS t3 ON t3.n <= t2.days
ORDER BY t1.EmployeeID, n, t1.StartDate

上述查询使用CTE 来创建一个包含36 行的计数表。它可以轻松扩展以创建更多行。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-08
    • 1970-01-01
    • 2016-09-05
    相关资源
    最近更新 更多