【问题标题】:Loop through rows and exploding date range循环遍历行和爆炸日期范围
【发布时间】:2018-09-17 13:21:51
【问题描述】:

我有一个任务表,我想在其中创建一个可以执行以下操作的视图: 1. 遍历每一行 2. 将开始日期和结束日期之间的每一天分解成一个新行 3. 在新列中插入平均任务工时

这是桌子:

CREATE TABLE #InputTABLE
(
TaskID varchar (200),
startdate DATETIME,
enddate DATETIME,
TaskWork int 
)

INSERT INTO #InputTABLE VALUES('2298aas','2018-06-06','2018-06-12',200);

到目前为止,我已经解决了 2 和 3,但我在迭代部分真的很挣扎。如果 InputTABLE 中存在多于一行,则以下代码将失败:

CREATE TABLE #OutputTABLE
(
TaskID varchar (200),
startdate DATETIME,
enddate DATETIME,
TaskWork int 
)

DECLARE @taskid varchar (200)
DECLARE @cnt int
DECLARE @startDate datetime
DECLARE @endDate datetime
DECLARE @incr int
DECLARE @tempDate datetime 
DECLARE @work int
DECLARE @averagework int

SET @taskid=(Select TaskID from #InputTABLE)
SET @startDate=(Select startdate from #InputTABLE)
SET @endDate=(Select enddate from #InputTABLE)
SET @cnt=DATEDIFF(dy,@startDate,@endDate)
SET @incr=0

SET @tempDate=DATEADD(dy,@incr,Cast(@startDate As datetime))
SET @work=(Select TaskWork from #InputTABLE)
SET @averagework= @work/@cnt

WHILE @cnt>=0
BEGIN

   IF @cnt = 0 
      BEGIN
         INSERT INTO #OutputTABLE VALUES(@taskid,@tempDate,@endDate,@averagework);
      END
   ELSE
      BEGIN
         insert into #OutputTABLE values(@taskid,@tempDate,DATEADD(dy, DATEDIFF(dy,0,@tempDate)+1, -1),@averagework);
      END
   SET @tempDate=DATEADD(dy,@incr+1,DATEADD(dy,DATEDIFF(dy,0,@startDate),0))

   SET @cnt=@cnt-1
   SET @incr=@incr+1

   END

我考虑过使用来自this 的光标来实现该解决方案,但我不确定该怎么做? cmets 也担心循环遍历行对性能不利,因此非常感谢任何关于此的建议!

表格大约有 15.000 行,平均日期范围大约是 31 天,因此视图大约有 465.000 行。不是一个很高的数字,但表格在不断增长,所以我可能还需要将视图限制为仅过去两年。

【问题讨论】:

标签: tsql database-cursor


【解决方案1】:

你的问题不是很清楚,但我的魔法水晶球告诉我,你可能正在寻找这个:

SET DATEFORMAT ymd;
CREATE TABLE #InputTABLE
(
TaskID varchar (200),
startdate DATETIME,
enddate DATETIME,
TaskWork int 
);

INSERT INTO #InputTABLE VALUES('six days','2018-06-06','2018-06-12',200)
                             ,('one day','2018-06-06','2018-06-07',200);

SELECT TaskID
      ,DATEADD(DAY,B.Numbr,startdate) AS ExplodingDate
      ,CAST(TaskWork AS DECIMAL(10,4))/A.DayDiff
FROM #InputTABLE
CROSS APPLY(SELECT DATEDIFF(DAY,startdate,enddate) AS DayDiff) A
CROSS APPLY(SELECT TOP (A.DayDiff) ROW_NUMBER() OVER(ORDER BY (SELECT NULL))-1 AS Numbr FROM master..spt_values) B

DROP TABLE #InputTABLE;

结果

TaskID      ExplodingDate           (Kein Spaltenname)
six days    2018-06-06 00:00:00.000 33.333333333333333
six days    2018-06-07 00:00:00.000 33.333333333333333
six days    2018-06-08 00:00:00.000 33.333333333333333
six days    2018-06-09 00:00:00.000 33.333333333333333
six days    2018-06-10 00:00:00.000 33.333333333333333
six days    2018-06-11 00:00:00.000 33.333333333333333
one day     2018-06-06 00:00:00.000 200.000000000000000

简短说明
第一个 APPLY 计算两个日期之间的天数差。
第二个APPLY 使用TOPROW_NUMBER 的技巧来动态创建计数表
这将为每个输入行创建尽可能多的行,因为开始日期和结束日期之间的天数。
剩下的就是简单的计算...

更新一个带有持久表的完整示例

CREATE DATABASE TestDB;
GO
USE TestDB;
GO
CREATE TABLE dbo.RunningNumbers(Number INT NOT NULL
                               ,CalendarDate DATE NOT NULL
                               ,CalendarYear INT NOT NULL
                               ,CalendarMonth INT NOT NULL
                               ,CalendarDay INT NOT NULL
                               ,CalendarWeek INT NOT NULL
                               ,CalendarYearDay INT NOT NULL
                               ,CalendarWeekDay INT NOT NULL);

DECLARE @CountEntries INT = 100000;
DECLARE @StartNumber INT = 0;


WITH E1(N) AS(SELECT 1 FROM(VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t(N)), --10 ^ 1
    E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b), -- 10 ^ 2 = 100 rows
    E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b), -- 10 ^ 4 = 10,000 rows
    E8(N) AS(SELECT 1 FROM E4 a CROSS JOIN E4 b), -- 10 ^ 8 = 10,000,000 rows
    CteTally AS
    (
        SELECT TOP(ISNULL(@CountEntries,1000000)) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) -1 + ISNULL(@StartNumber,0) As Nmbr
        FROM E8
    )
INSERT INTO dbo.RunningNumbers
SELECT CteTally.Nmbr,CalendarDate.d,CalendarExt.*
FROM CteTally
CROSS APPLY
(
    SELECT DATEADD(DAY,CteTally.Nmbr,{ts'1900-01-01 00:00:00'})
) AS CalendarDate(d)
CROSS APPLY
(
    SELECT YEAR(CalendarDate.d) AS CalendarYear
          ,MONTH(CalendarDate.d) AS CalendarMonth
          ,DAY(CalendarDate.d) AS CalendarDay
          ,DATEPART(WEEK,CalendarDate.d) AS CalendarWeek
          ,DATEPART(DAYOFYEAR,CalendarDate.d) AS CalendarYearDay
          ,DATEPART(WEEKDAY,CalendarDate.d) AS CalendarWeekDay
) AS CalendarExt;
GO

SET DATEFORMAT ymd;
CREATE TABLE #InputTABLE
(
TaskID varchar (200),
startdate DATETIME,
enddate DATETIME,
TaskWork int 
);

INSERT INTO #InputTABLE VALUES('six days','2018-06-06','2018-06-12',200)
                             ,('one day','2018-06-06','2018-06-07',200);

SELECT TaskID
      ,B.CalendarDate
      ,CAST(TaskWork AS DECIMAL(10,4))/(A.DayDiff+1)
FROM #InputTABLE
CROSS APPLY(SELECT DATEDIFF(DAY,startdate,enddate) AS DayDiff) A
CROSS APPLY(SELECT * FROM dbo.RunningNumbers WHERE CalendarDate BETWEEN startdate AND enddate) B

DROP TABLE #InputTABLE;
GO

USE master;
GO

DROP DATABASE TestDB;

【讨论】:

  • 可能是正确的,但是 FROM master..spt_values 是做什么的?当我尝试运行它时,我收到此错误消息:“此版本的 SQL Server 不支持对 'master..spt_values' 中的数据库和/或服务器名称的引用。”
  • @user2776167 master..spt_values 只是一个存在于普通 SQL-Server 环境中的具有数千行的表......我们不需要这些行,只要任何具有足够行的表(至少您从开始日期到结束日期之间的最大距离。这样的表格可以很容易地即时创建。您可以使用任何现有的有很多行的表格。最好是一个持久的数字/日期表格。@ 987654321@
  • 我已经添加了 RunningNumbers 表,并且在调用该表时它似乎可以工作。我仍然收到此错误:“TOP 或 FETCH 子句包含无效值。”我检查了一下,最大日期差是 12.870,输入表大约是 16.000 行。那我错过了什么?
  • @user2776167 与流水号表你根本不需要这个。只需 cross apply 按日期所需的行...
  • 我通过在末尾添加 WHERE A.DayDiff IS NOT NULL 子句解决了这个问题。我发现我在开始日期和结束日期中有 NULL 值。非常感谢您的帮助,您太棒了!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-12-23
  • 1970-01-01
  • 2011-05-19
  • 2011-02-19
  • 2020-03-06
  • 2023-02-17
  • 2021-12-30
相关资源
最近更新 更多