SQL Server - 生成重复日期的计划列表

时间:2017-07-04 10:56:06

标签: sql-server

我们有一组记录,其中每个记录(作业)都有一个周期(或频率),以确定作业何时出现在某人的工作清单上。 E.G

作业A每7天生成一次 作业B每14天生成一次。

每个工作也有一个开始日期。

我需要生成一个表格,其中包含日期范围之间的所有可能的工作日期。

这是我生成可能日期的第一部分代码。

DECLARE @ForecastEarliestStartDate as DATETIME, @formStartDate as DATE,@formEndDate as DATE, @cycleInDays as BIGINT;

--set input param values
SET @ForecastEarliestStartDate = CAST('2017-07-03' AS DATETIME)
SET @formStartDate = getdate();
SET @formEndDate = getdate()+60;
SET @cycleInDays = 28;


WITH mycte AS
(
  SELECT @ForecastEarliestStartDate DateValue
  UNION ALL
  SELECT   DateValue + @cycleInDays
  FROM    mycte   
  WHERE   DateValue + @cycleInDays < @formEndDate
)

SELECT  *
FROM    mycte
WHERE DateValue between @formStartDate and @formEndDate
OPTION (MAXRECURSION 0);

这对于单个作业来说效果很好,但是如何针对整个作业表运行它,其中@ForecastEarliestStartDate, @formStartDate, @formEndDate, @cycleInDays是表中的字段?

2 个答案:

答案 0 :(得分:3)

我经常使用TVF创建动态日期/时间范围。通常比递归CTE更快,并且它是参数驱动的。您提供日期/时间范围,日期部分和增量。

例如

Select * From [dbo].[udf-Range-Date]('2017-07-03',getdate()+60,'DD',7)

<强>返回

RetSeq  RetVal
1   2017-07-03
2   2017-07-10
3   2017-07-17
4   2017-07-24
5   2017-07-31
6   2017-08-07
7   2017-08-14
8   2017-08-21
9   2017-08-28

感兴趣的UDF

CREATE FUNCTION [dbo].[udf-Range-Date] (@R1 datetime,@R2 datetime,@Part varchar(10),@Incr int)
Returns Table
Return (
    with cte0(M)   As (Select 1+Case @Part When 'YY' then DateDiff(YY,@R1,@R2)/@Incr When 'QQ' then DateDiff(QQ,@R1,@R2)/@Incr When 'MM' then DateDiff(MM,@R1,@R2)/@Incr When 'WK' then DateDiff(WK,@R1,@R2)/@Incr When 'DD' then DateDiff(DD,@R1,@R2)/@Incr When 'HH' then DateDiff(HH,@R1,@R2)/@Incr When 'MI' then DateDiff(MI,@R1,@R2)/@Incr When 'SS' then DateDiff(SS,@R1,@R2)/@Incr End),
         cte1(N)   As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
         cte2(N)   As (Select Top (Select M from cte0) Row_Number() over (Order By (Select NULL)) From cte1 a, cte1 b, cte1 c, cte1 d, cte1 e, cte1 f, cte1 g, cte1 h ),
         cte3(N,D) As (Select 0,@R1 Union All Select N,Case @Part When 'YY' then DateAdd(YY, N*@Incr, @R1) When 'QQ' then DateAdd(QQ, N*@Incr, @R1) When 'MM' then DateAdd(MM, N*@Incr, @R1) When 'WK' then DateAdd(WK, N*@Incr, @R1) When 'DD' then DateAdd(DD, N*@Incr, @R1) When 'HH' then DateAdd(HH, N*@Incr, @R1) When 'MI' then DateAdd(MI, N*@Incr, @R1) When 'SS' then DateAdd(SS, N*@Incr, @R1) End From cte2 )

    Select RetSeq = N+1
          ,RetVal = D 
     From  cte3,cte0 
     Where D<=@R2
)
/*
Max 100 million observations -- Date Parts YY QQ MM WK DD HH MI SS
Syntax:
Select * from [dbo].[udf-Range-Date]('2016-10-01','2020-10-01','YY',1) 
Select * from [dbo].[udf-Range-Date]('2016-01-01','2017-01-01','MM',1) 
*/
  

编辑 - 交叉应用的工作示例

Declare @YourTable table (JobName varchar(50),StartDate date,Interval int)
Insert Into @YourTable values
 ('Job A','2017-07-03',7)
,('Job B','2017-07-03',14)


Select A.JobName
      ,JobDate = B.RetVal
 From @YourTable A
 Cross Apply [dbo].[udf-Range-Date](A.StartDate,getdate()+60,'DD',A.Interval) B

<强>返回

JobName JobDate
Job A   2017-07-03
Job A   2017-07-10
Job A   2017-07-17
Job A   2017-07-24
Job A   2017-07-31
Job A   2017-08-07
Job A   2017-08-14
Job A   2017-08-21
Job A   2017-08-28
Job B   2017-07-03   -- << Notice differant span for Job B
Job B   2017-07-17
Job B   2017-07-31
Job B   2017-08-14
Job B   2017-08-28

答案 1 :(得分:2)

首先要注意的是,递归CTE是生成集合或序列的最差方法之一(最差的是使用显式循环)。在继续之前,我建议您阅读以下系列文章:

例如,我假设您没有数字表,并且无法创建数字表,因此我将使用堆叠CTE方法。此查询将为您提供0到99,999之间的数字列表。

WITH N1 AS (SELECT N FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (N)),
N2 (N) AS (SELECT 1 FROM N1 AS N1 CROSS JOIN N1 AS N2),
N3 (N) AS (SELECT 1 FROM N2 AS N1 CROSS JOIN N2 AS N2)
SELECT N = ROW_NUMBER() OVER(ORDER  BY N) - 1
FROM N3

如果您需要更多数字,可以添加交叉联接,如果需要更少,可以删除它们。

然后,您可以将这些数字加入到您的工作表中,每次都将(n * CycleInDays)添加到您的开始日期:

WITH N1 AS (SELECT N FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (N)),
N2 (N) AS (SELECT 1 FROM N1 AS N1 CROSS JOIN N1 AS N2),
N3 (N) AS (SELECT 1 FROM N2 AS N1 CROSS JOIN N2 AS N2),
Numbers (Number) AS (SELECT N = ROW_NUMBER() OVER(ORDER  BY N) - 1 FROM N3)

SELECT  t.*,
        Iteration = n.Number,
        Date = DATEADD(DAY, n.Number * t.cycleInDays, t.ForecastEarliestStartDate)
FROM    (VALUES
            (1, CONVERT(DATE, '2017-07-03'), CONVERT(DATE, '2017-07-03'), CONVERT(DATE, '2017-09-03'), 28 ),
            (2, CONVERT(DATE, '2017-07-03'), CONVERT(DATE, '2017-07-03'), CONVERT(DATE, '2017-09-03'), 19)
        ) t (JobID, ForecastEarliestStartDate, formStartDate, formEndDate, cycleInDays)
        INNER JOIN Numbers AS N
            ON t.formEndDate >= DATEADD(DAY, n.Number * t.cycleInDays, t.ForecastEarliestStartDate)
            AND t.formStartDate <= DATEADD(DAY, n.Number * t.cycleInDays, t.ForecastEarliestStartDate)
ORDER BY t.JobID, n.Number;

这给出了:

JobID   ForecastEarliestStartDate   formStartDate   formEndDate     cycleInDays     Iteration   Date
------------------------------------------------------------------------------------------------------------------
1       2017-07-03                  2017-07-03      2017-09-03      28              0           2017-07-03
1       2017-07-03                  2017-07-03      2017-09-03      28              1           2017-07-31
1       2017-07-03                  2017-07-03      2017-09-03      28              2           2017-08-28
2       2017-07-03                  2017-07-03      2017-09-03      19              0           2017-07-03
2       2017-07-03                  2017-07-03      2017-09-03      19              1           2017-07-22
2       2017-07-03                  2017-07-03      2017-09-03      19              2           2017-08-10
2       2017-07-03                  2017-07-03      2017-09-03      19              3           2017-08-29

<强>附录

回应评论:

  

大。所以一般的想法是循环速度较慢,但​​如果你真的不知道那里的迭代会怎么样,那么呢?循环?

不,只要您的数字表足以​​覆盖最大迭代次数,那么您就不需要循环。我使用100,000行的例子足以涵盖每天运行273年的工作。我原本以为这就足够了。

  是的,我有一张数字表。你能告诉我如何解决我的问题而不需要产生它

当然,只需删除生成数字的CTE,并将Numbers CTE的引用更改为您的数字表调用的任何内容:

SELECT  t.*,
        Iteration = n.Number,
        Date = DATEADD(DAY, n.Number * t.cycleInDays, t.ForecastEarliestStartDate)
FROM    (VALUES
            (1, CONVERT(DATE, '2017-07-03'), CONVERT(DATE, '2017-07-03'), CONVERT(DATE, '2017-09-03'), 28 ),
            (2, CONVERT(DATE, '2017-07-03'), CONVERT(DATE, '2017-07-03'), CONVERT(DATE, '2017-09-03'), 19)
        ) t (JobID, ForecastEarliestStartDate, formStartDate, formEndDate, cycleInDays)
        INNER JOIN dbo.Numbers AS N  ---- CHANGE TO WHATEVER YOUR NUMBERS TABLE IS CALLED
            ON t.formEndDate >= DATEADD(DAY, n.Number * t.cycleInDays, t.ForecastEarliestStartDate)
            AND t.formStartDate <= DATEADD(DAY, n.Number * t.cycleInDays, t.ForecastEarliestStartDate)
ORDER BY t.JobID, n.Number;
  

刚刚注意到你正在将乔布斯硬编码到查询中。它们存在于另一个表中,所以不确定这是如何解决我的问题的

我已经将作业硬编码到查询中,只是为了模拟你拥有的表(由于问题中没有关于此表的信息,我不得不猜测一下)。只需将我用过的表值构造函数替换为实际表。 e.g。

SELECT  t.*,
        Iteration = n.Number,
        Date = DATEADD(DAY, n.Number * t.cycleInDays, t.ForecastEarliestStartDate)
FROM    dbo.[Your Job Table] AS t
        INNER JOIN dbo.[Your Numbers Table] AS N 
            ON t.formEndDate >= DATEADD(DAY, n.Number * t.cycleInDays, t.ForecastEarliestStartDate)
            AND t.formStartDate <= DATEADD(DAY, n.Number * t.cycleInDays, t.ForecastEarliestStartDate)
ORDER BY t.JobID, n.Number;