2017-07-04 48 views
2

各レコード(ジョブ)に、そのジョブがいつ誰かの仕事リストに表示されるかを決定する周期(または頻度)を持つレコードセットがあります。 E.GSQL Server - 繰り返し予定リストの生成

ジョブ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は、テーブル内のフィールドであるジョブズのテーブル全体に対してそれを実行することができますか?

答えて

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<[email protected] 
) 
/* 
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) 
*/ 
場合

編集 - CROSSの実施例は

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 
+0

ありがとう、これはうまく動作し、TVFを好きです。 – totalitarian

+0

@totalitarian –

2

最初に注意しなければならないのは、再帰的なCTEが、セットまたはシリーズを生成する最悪の方法の1つであることです(明示的なループで最悪のことです)。先に進む前に、私は、次の一連の記事読んで推薦:例の便宜上

を、私はあなたが数字を持っていないことを前提としていますテーブルを作成することはできませんので、積み上げた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 

さらに多くの番号が必要な場合は、クロスジョインを追加することができます。必要がない場合は削除することができます。

それからちょうど、仕事のあなたのテーブルにこれらの番号を参加することができ、それぞれの時間は、あなたの開始日に(CycleInDaysをN *)追加:

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 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; 
+0

を適用おかげで、私はこれを見てみましょうが、なぜ我々は数表が必要なのでしょうか?ループする必要性を避けるのですか? – totalitarian

+0

はい、あなたはあなたの仕事が指定された範囲内で何回繰り返されるかわからないので、10年ごとに繰り返される場合は1回、毎日の仕事の場合は1,000回になる可能性があるので、テーブルが必要ですすべてのサイクルを確実にキャプチャできるようにする必要があります。 1000回のループを繰り返すのではなく、1000回の繰り返しを持つ後のケースでは、ループを避けるために1,000行の数値表に直接結合します。 – GarethD

+0

だから一般的な考え方は、ループが遅いということですが、実際には人間の反復がどのようになるか分からないのですが、それではどうしますか?ループ? – totalitarian

関連する問題