【问题标题】:Loop/Iterate through date range while inserting插入时循环/迭代日期范围
【发布时间】:2013-03-20 06:26:10
【问题描述】:

所以我将数据导入到表格中(我们称之为 RAWDATA),格式如下:

EMPID  | STARTDATE  | ENDDATE    | TOTALHOURS | TOTALWAGES
ABC123 | 01-01-2013 | 01-28-2013 | 160.0      | 1800.00
XYZ987 | 01-01-2013 | 01-31-2013 | 200.0      | 2500.00

我需要获取该数据,并将其按以下格式放入不同的表 (EMPDATA) 中:

EMPID |日期 |小时 |工资
ABC123 | 01-01-2013 | 5.71 | 64.29
ABC123 | 01-02-2013 | 5.71 | 64.29
ABC123 | 01-03-2013 | 5.71 | 64.29
...... | .......... | .... | ......
XYZ987 | 01-01-2013 | 6.45 | 80.66
XYZ987 | 01-02-2013 | 6.45 | 80.66
XYZ987 | 01-03-2013 | 6.45 | 80.66
...... | .......... | .... | ......

我的想法是在 STARTDATE 和 ENDDATE 之间做一个 DATEDIFF 来计算出多少天(在本例中:28)来分配小时数和工资,然后对于每一天,插入一个包含平均小时数和工资的行每天工作。这一切都将通过 RAWDATA 表上的触发器来完成。我只是不确定如何在触发器中从 STARTDATE 迭代到 ENDDATE。

编辑: 我还应该指出,导入的数据并不总是每行都有相同的开始/结束日期。我已经更新了第一个表格示例以表明这一点。

【问题讨论】:

  • 您需要处理一行还是多行?
  • 多行。我已经更新了问题
  • 您需要支持的最大日期范围是多少? 31天,还是更长?这是 SQL Server 2000、2005、2008、2008 R2、2012 吗?
  • @D.R.我的答案中包含的演示也显示了多行。

标签: sql sql-server triggers


【解决方案1】:
  1. 创建date 表并使用JOIN
  2. 计算startdateenddate 之间的天数
  3. totalhourstotalwages 除以计算天数。

这是我的解决方案:

SELECT a.empid, b.dd AS date, 
  CAST(a.totalhours AS decimal) / (DATEDIFF(day, startdate, enddate) + 1) AS hours,
  CAST(a.totalwages AS decimal) / (DATEDIFF(day, startdate, enddate) + 1) AS wages
FROM wages a
INNER JOIN dates b ON dd BETWEEN a.startdate AND a.enddate

结果

| EMPID |日期 |小时 |工资 | -------------------------------------------------- ------ | ABC123 | 2013-01-01 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-02 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-03 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-04 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-05 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-06 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-07 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-08 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-09 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-10 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-11 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-12 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-13 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-14 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-15 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-16 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-17 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-18 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-19 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-20 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-21 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-22 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-23 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-24 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-25 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-26 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-27 | 5.71428571428 | 64.28571428571 | | ABC123 | 2013-01-28 | 5.71428571428 | 64.28571428571 |

See the demo

【讨论】:

  • @swasheck,sdlect into 在触发器中不起作用,它仅在表不存在时起作用。您需要使用您的表名和字段将插入添加到此顶部。这个作者是对的,我们不应该为你做你的工作。
  • 我需要先喝杯咖啡,然后才能开始评论/回答 SO
  • @D.R.日期表确实是您的最佳解决方案。只需在this article 中的性能图表上快速达到峰值。
  • @D.R.这取决于,您对日期表的具体反对意见是什么?
  • 我咨询了 dba,他对日期表没意见,所以我使用了您的解决方案!谢谢!
【解决方案2】:

假设您使用的是 SQL Server 2005 及更高版本,您可以为此使用Recursive CTE 来创建动态日期范围(与使用查找表相比)。这在 empid 字段上使用 GROUP BY 并假设每个 empid 有一行:

WITH RecCTE AS (
  SELECT empid, startdate
  FROM rawdata
  UNION ALL
  SELECT R.empid, DATEADD(day,1,R.startdate)
  FROM RecCTE R JOIN rawdata RD ON R.startdate < RD.enddate
  )
INSERT INTO EMPDATA 
SELECT R.EmpId, R.StartDate, T.TotalHours/R2.cnt, T.TotalWages/R2.cnt
FROM RecCTE R
  JOIN (SELECT empid, totalhours, totalwages
        FROM rawdata
       ) T ON R.empid = T.empid
  JOIN (SELECT EmpID, COUNT(*) cnt 
        FROM RecCTE 
        GROUP BY EmpID) R2 ON R.EmpID=R2.EmpId

SQL Fiddle Demo

可能有一种方法可以提高效率,但这应该会让您朝着正确的方向前进。例如,您可以使用 COUNT OVER 函数返回计数与子查询:

WITH RecCTE AS (
  SELECT empid, startdate
  FROM rawdata
  UNION ALL
  SELECT R.empid, DATEADD(day,1,R.startdate)
  FROM RecCTE R JOIN rawdata RD ON R.startdate < RD.enddate
  )
INSERT INTO EMPDATA 
SELECT R.EmpId, R.StartDate, 
    T.TotalHours/COUNT(1) OVER (PARTITION BY R.EmpId), 
    T.TotalWages/COUNT(1) OVER (PARTITION BY R.EmpId)
FROM RecCTE R
  JOIN (SELECT empid, totalhours, totalwages
        FROM rawdata
       ) T ON R.empid = T.empid
ORDER BY R.StartDate

【讨论】:

  • 感谢您的回答,但我能够使用 PolishPrince 的回答!我感谢您的帮助
  • @D.R. - 没问题 - 很高兴他们能提供帮助。只是想提供一种替代方法,而不必创建和维护单独的日期表。最好的问候!
  • 大家对“必须创建和维护单独的日期表”的最大反对意见是什么?构建一个表真的很难,一次,填充它,一次,并且根本不需要维护它(直到我们的日历改变)?是关注绩效吗? (这样的表会很小,如果你引用的足够多,它会一直在内存中。)我不明白为什么这么有用的表会遇到这样的阻力。
  • @AaronBertrand -- 我对接受的答案没有任何问题(我给了它+1)。我试图提供一个替代方案(正如 OP 所要求的那样)。在我的咨询生涯中,有几个实例/客户无法创建新表(例如权限),因此必须使用其他方法来解决问题(在这种情况下,是递归 CTE)。最好的问候。
  • @sgeddes 对,这个问题不是针对你的,比这更笼统一点。人们经常嘲笑创建表格的建议,我想我不喜欢看到这种嘲笑受到鼓励。 :-)
猜你喜欢
  • 1970-01-01
  • 2019-10-08
  • 1970-01-01
  • 2019-09-10
  • 1970-01-01
  • 1970-01-01
  • 2017-12-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多