我们有一组记录,其中每个记录(作业)都有一个周期(或频率),以确定作业何时出现在某人的工作清单上。 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
是表中的字段?
答案 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;