2016-11-29 19 views
3

週の反復計算を計算するCTEで作業していますが、パターンが年を越えているときに問題が発生しています。T-SQL日付の再計算を計算する

CTEべき 計算し、次のパラメータに基づいてすべてのオカレンス:

  • 再発カウント - それは
  • 曜日どうなるの回数から週のどの日には
  • 開始日 - パターン計算を開始するとき
  • 周期性 - どのくらいの頻度で数週間の面で、すなわち1週、2 2週間ごと
  • ウィーク開始 - 開始日欄を反映最初に発生した週番号、

これは私がパターンを保存する方法である:

以下
/* 
    Pattern Table 
*/ 
CREATE TABLE Pattern (
    [Id] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY 
, [Subject] [nvarchar](100) NULL 
, [RecurrenceCount] [int] NULL 
, [WeekDays] [varchar](max) NULL 
, [StartDate] [datetime] NULL 
, [EndDate] [datetime] NULL 
, [Periodicity] [int] NULL 
, [StartingWeek] [int] NULL 

); 

は、私は私のCTEをテストするために使用していたパターンのカップルです:

/* 
    Pattern samples for test 
*/ 
Insert into Pattern Values (N'Every 5 Weeks Fri, Sat, Sun', 72, 'Friday, Saturday, Sunday', N'2016-12-02', N'2016-12-02', 5, datepart(wk, N'2016-12-02')); 
Insert into Pattern Values (N'Every 3 Weeks Tue, Wed, Thu', 20, 'Tuesday, Wednesday, Thursday', N'2016-11-01', N'2016-11-01', 3, datepart(wk, N'2016-11-01')); 

私は月曜日週の考慮初日のカウントを開始

SET DATEFIRST 1 

そして、これは私がこの計算を実行するために使用していますCTEです:

/* 
    Display Patterns 
*/ 
select * from Pattern 

DECLARE @mindate DATE  = (SELECT MIN(StartDate) FROM Pattern) 
DECLARE @maxmindate DATE = (SELECT MAX(StartDate) FROM Pattern) 
DECLARE @maxcount INT  = (SELECT MAX(RecurrenceCount) FROM Pattern) 
DECLARE @maxdate DATE  = DATEADD(WK, @maxcount + 10, @maxmindate) 

/* 
    CTE to generate required occurrences 
*/ 
;With cteKeyDate As (
    Select 
     KeyStartDate = @MinDate, 
     KeyDOW = DateName(WEEKDAY, @MinDate), 
     KeyWeek = datepart(WK,@MinDate) 
    Union All 
    Select 
     KeyStartDate = DateAdd(DD, 1, df.KeyStartDate) , 
     KeyDOW = DateName(WEEKDAY,DateAdd(DD, 1, df.KeyStartDate)), 
     KeyWeek= DatePart(WK,DateAdd(DD, 1, df.KeyStartDate)) 
    From cteKeyDate DF 
    Where DF.KeyStartDate <= @MaxDate 
) 

SELECT 
    Id, KeyStartDate, KeyDow, KeyWeek, RowNr, OccNr = ROW_NUMBER() OVER (PARTITION BY Id ORDER BY StartDate) 
FROM 
    (Select 
     A.Id 
     ,A.StartDate 
     ,A.EndDate 
     ,Count = A.RecurrenceCount 
     ,Days = A.WeekDays 
     ,Every = A.Periodicity    
     ,KeyStartDate = CASE 
     /* 
      if no periodicity (1) then it is sequential 
      if periodicity, first week doesn't apply (MIN KeyWeek) 
     */ 
     WHEN A.Periodicity = 1 
      OR (Periodicity <> 1 AND (SELECT MIN(C.StartingWeek) FROM Pattern AS C WHERE C.Id = A.Id) = KeyWeek) 
      THEN KeyStartDate 
     /* Otherwise formula ADD WEEKS => Current Week Min Week */ 
     ELSE 
      DATEADD(WK, ((A.Periodicity - 1) * (KeyWeek - (SELECT MIN(C.StartingWeek) FROM Pattern AS C WHERE C.Id = A.Id))) , KeyStartDate) 
     END 
     ,KeyDow 
     ,KeyWeek 
     ,RowNr = Row_Number() over (Partition By A.Id Order By B.KeyStartDate) 
     ,Periodicity = A.Periodicity 
    from 
     Pattern A 
     Join cteKeyDate B on B.KeyStartDate >= DATEADD(DAY, -1, A.StartDate) and Charindex(KeyDOW, A.WeekDays) > 0 
    ) Final     
Where 
    RowNr <= Count AND Id = 1 
Option (maxrecursion 32767) 

、私は再び私をテストしている場合パターン、例えば最初のもの、私はこの結果を得て、来年に起こったときにバグがあります。 RowNr 15は、次の週ではなく、4月23日(日曜日)に発生するはずなので間違っています。

Id KeyStartDate KeyDow  KeyWeek RowNr OccNr 
1 02.12.2016  Friday  49  1  1 
2 03.12.2016  Saturday 49  2  2 
3 04.12.2016  Sunday  49  3  3 
4 06.01.2017  Friday  50  4  4 
5 07.01.2017  Saturday 50  5  5 
6 08.01.2017  Sunday  50  6  6 
7 10.02.2017  Friday  51  7  7 
8 11.02.2017  Saturday 51  8  8 
9 12.02.2017  Sunday  51  9  9 
10 17.03.2017  Friday  52  10  10 
11 18.03.2017  Saturday 52  11  11 
12 19.03.2017  Sunday  52  12  12 
13 21.04.2017  Friday  53  13  13 
14 22.04.2017  Saturday 53  14  14 
15 28.04.2013  Sunday  1  15  15 
16 31.05.2013  Friday  2  16  16 
17 01.06.2013  Saturday 2  17  17 

2番目のパターンは正確に計算されますが、私はパターンが年を越えてSQLの0にリセットされたが、解決策を見つけることができないときに論理に問題があると思うので、数日間は苦労しました。

サンプルコードhereを使用してコードを実行することができます。

+0

基準点としては、あなたの 'ROW_NUMBER'にその' PARTITIONのBY'を必要としないと* *はA'、 'B'、 'C​​' 'でコードを展開しないでくださいテーブルエイリアス。 – iamdave

+0

エイリアスについて@iamdave、私はA、B、Cを使用しませんが、Aは "アクティビティ"を意味しますので、よく知られているエイリアスです 2008年のような特定のSQLサーバで複数のパターンでクエリを実行すると、行が正しく順序付けされていない – Raffaeu

+1

CTEが何をしようとしているのか、ここで達成しようとしていることがわかっていれば、あなたのコードをよりよく理解できるようになります。現在、開始日と終了日はパターンあなたがここで達成しようとしていることを教えてください。 – Surendra

答えて

1

この上でいくつかの時間をお過ごしください。あなたの計算には欠陥があります。あなたの質問に示されていない特別なルールがない限り、私はなぜいくつかの日付が特別な理由は見当たりません。私は変数テーブルを使用することを好む。

/* 
     Pattern Table 
    */ 
    DECLARE @Pattern TABLE(
     [Id] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY 
     ,[Subject] [nvarchar](100) NULL 
     ,[RecurrenceCount] [int] NULL 
     ,[WeekDays] [varchar](max) NULL 
     ,[StartDate] [datetime] NULL 
     ,[EndDate] [datetime] NULL 
     ,[Periodicity] [int] NULL 
     ,[StartingWeek] [int] NULL 
    ); 

    /* 
     Populate with values based on Recurreance and Startdate. The startdate will give the start week, which make the start week obsolete. 
    */ 
    DECLARE @PreferredDate TABLE(
     [Id] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY 
     ,[PreferredDate] [datetime] NULL 
     ,[PreferredWeek] [int] NULL 
     ,[PreferredYear] [int] NULL 
    ) 

datefirstの現在の設定を常に取得することは非常に重要です。別の設定を使用すると、他の人の計算が中断されます。私は明白な理由からパターンIDを追加しました。

/* 
     CTE to generate required preferred dates 
    */ 
    ;With ctePreferredDate AS (
     Select PreferredDate = @MinDate, PreferredWeek = DATEPART(WK, @MinDate), PreferredYear = DATEPART(YYYY, @MinDate) 
     Union All 
     SELECT PreferredDate = DATEADD(WK,(SELECT Periodicity FROM @Pattern WHERE [email protected]), PreferredDate) 
       ,PreferredWeek = DATEPART(WK,DATEADD(WK,(SELECT Periodicity FROM @Pattern WHERE [email protected]), PreferredDate)) 
       ,PreferredYear = DATEPART(yyyy,DATEADD(WK,(SELECT Periodicity FROM @Pattern WHERE [email protected]), PreferredDate)) 
     From ctePreferredDate pFD 
     Where pFD.PreferredDate <= @MaxDate 

    ) 
    INSERT INTO @PreferredDate (PreferredDate, PreferredWeek, PreferredYear) 
    SELECT PreferredDate, PreferredWeek, PreferredYear 
    FROM ctePreferredDate 

最終CTEテーブルは、以下を使用して移入され:

/* 
     CTE to generate required occurrences 
    */ 
    ;With cteKeyDate As (
     Select KeyStartDate = @MinDate 
       ,KeyDOW = DateName(WEEKDAY, @MinDate) 
       ,KeyWeek = datepart(WK,@MinDate) 
       ,id = @PreferredSubjectID 
       ,KeyOccurrance = @maxcount 
     Union All 
     Select KeyStartDate = DateAdd(DD, 1, df.KeyStartDate) 
       ,KeyDOW = DateName(WEEKDAY,DateAdd(DD, 1, df.KeyStartDate)) 
       ,KeyWeek= DatePart(WK,DateAdd(DD, 1, df.KeyStartDate)) 
       ,[email protected] 
       ,KeyOccurrance = @maxcount 
     From cteKeyDate DF 
     Where DF.KeyStartDate <= @MaxDate 
    ) 
    SELECT StartDate 
      ,[DayOfWeek] 
      ,[Week] 
      ,OccNr = ROW_NUMBER() OVER   (PARTITION BY Id ORDER BY StartDate) 
    FROM 
     (
     SELECT cte.KeyStartDate AS StartDate 
       ,cte.KeyDOW AS [DayOfWeek] 
       ,cte.KeyWeek AS [Week] 
       ,cte.id 
       ,cte.KeyOccurrance AS Occurrance 
       ,RowNr = ROW_NUMBER() OVER   (PARTITION BY KeyOccurrance ORDER BY KeyStartDate) 
     FROM cteKeyDate cte 
       INNER JOIN 
       @PreferredDate pfd 
        ON cte.KeyWeek = pfd.PreferredWeek 
         AND YEAR(cte.KeyStartDate) = pfd.PreferredYear 
     WHERE cte.KeyDOW IN (SELECT LTRIM(RTRIM(Item)) FROM fn_SplitString((SELECT weekdays from @Pattern WHERE Id=1),',')) 
     )cte 
    WHERE cte.RowNr <= cte.Occurrance 
    ORDER BY cte.StartDate 
    Option (maxrecursion 32767) 

    SET DATEFIRST @DateFirst --Quite important 

結果

2016/12/02 Friday 49 1 
2016/12/03 Saturday 49 2 
2016/12/04 Sunday 49 3 
2017/01/06 Friday 2 4 
2017/01/07 Saturday 2 5 
2017/01/08 Sunday 2 6 
2017/02/10 Friday 7 7 
2017/02/11 Saturday 7 8 
2017/02/12 Sunday 7 9 
2017/03/17 Friday 12 10 
2017/03/18 Saturday 12 11 
2017/03/19 Sunday 12 12 
2017/04/21 Friday 17 13 
2017/04/22 Saturday 17 14 
2017/04/23 Sunday 17 15 
2017/05/26 Friday 22 16 
2017/05/27 Saturday 22 17 
2017/05/28 Sunday 22 18 
2017/06/30 Friday 27 19 
2017/07/01 Saturday 27 20 
2017/07/02 Sunday 27 21 
2017/08/04 Friday 32 22 
2017/08/05 Saturday 32 23 
2017/08/06 Sunday 32 24 
2017/09/08 Friday 37 25 
2017/09/09 Saturday 37 26 
2017/09/10 Sunday 37 27 
2017/10/13 Friday 42 28 
2017/10/14 Saturday 42 29 
2017/10/15 Sunday 42 30 
2017/11/17 Friday 47 31 
2017/11/18 Saturday 47 32 
2017/11/19 Sunday 47 33 
2017/12/22 Friday 52 34 
2017/12/23 Saturday 52 35 
2017/12/24 Sunday 52 36 
2018/01/26 Friday 4 37 
2018/01/27 Saturday 4 38 
2018/01/28 Sunday 4 39 
2018/03/02 Friday 9 40 
2018/03/03 Saturday 9 41 
2018/03/04 Sunday 9 42 
2018/04/06 Friday 14 43 
2018/04/07 Saturday 14 44 
2018/04/08 Sunday 14 45 
2018/05/11 Friday 19 46 
2018/05/12 Saturday 19 47 
2018/05/13 Sunday 19 48 
2018/06/15 Friday 24 49 
2018/06/16 Saturday 24 50 
2018/06/17 Sunday 24 51 
2018/07/20 Friday 29 52 
2018/07/21 Saturday 29 53 
2018/07/22 Sunday 29 54 
2018/08/24 Friday 34 55 
2018/08/25 Saturday 34 56 
2018/08/26 Sunday 34 57 
2018/09/28 Friday 39 58 
2018/09/29 Saturday 39 59 
2018/09/30 Sunday 39 60 
2018/11/02 Friday 44 61 
2018/11/03 Saturday 44 62 
2018/11/04 Sunday 44 63 
2018/12/07 Friday 49 64 
2018/12/08 Saturday 49 65 
2018/12/09 Sunday 49 66 
2019/01/11 Friday 2 67 
2019/01/12 Saturday 2 68 
2019/01/13 Sunday 2 69 
2019/02/15 Friday 7 70 
2019/02/16 Saturday 7 71 
2019/02/17 Sunday 7 72 

@preferreddateテーブルは以下を使用して移入され

DECLARE @DateFirst int = @@dateFirst --DATEFIRST is a global setting 

    DECLARE @mindate DATE  = (SELECT MIN(StartDate) FROM @Pattern WHERE [email protected]) 
    DECLARE @maxmindate DATE = (SELECT MAX(StartDate) FROM @Pattern WHERE [email protected]) 
    DECLARE @maxcount INT  = (SELECT MAX(RecurrenceCount) FROM @Pattern WHERE [email protected]) 
    DECLARE @maxdate DATE  = DATEADD(WK, @maxcount + 50, @maxmindate) 

    SET DATEFIRST 1 

    DECLARE @PreferredSubjectID int = 1 

スプリットストリングf慰め:

ALTER FUNCTION [dbo].[fn_SplitString](
    @InputStr varchar(Max), 
    @Seperator varchar(10)) 
RETURNS @OutStrings TABLE (ItemNo int identity(1,1), Item varchar(256)) 

AS 
BEGIN 

    DECLARE @Str varchar(2000), 
      @Poz int, @cnt int 

    --DECLARE @OutStrings TABLE (Item varchar(2000)) 

    SELECT @Poz = CHARINDEX (@Seperator, @InputStr), @cnt = 0 
    WHILE @Poz > 0 AND @cnt <= 10000 
    BEGIN 
     SELECT @Str = SubString(@InputStr, 1, @Poz - 1) 
     INSERT INTO @OutStrings(Item) VALUES(@Str) 

     SELECT @InputStr = Right(@Inputstr, Len(@InputStr) - (len(@Str) + len(@Seperator))) 
     SELECT @Poz = CHARINDEX (@Seperator, @InputStr), @cnt = @cnt + 1 
    END 
    IF @InputStr <> '' 
    BEGIN 
     INSERT INTO @OutStrings(Item) VALUES(@InputStr) 
    END 

    RETURN 
END 
+0

でなければなりません。応答、感謝の努力;-) – Raffaeu

+0

あなたはそれをしました@ダニー - schoeman、それは私の現在のビューをリファクタリングし、私はしばらくかかったので、私は現在、私はそれが動作すると言う必要があります、それは速く、それはすべてのパターンをカバーします。ありがとう!! – Raffaeu

+1

喜んで私は助けてくれました。 –

-1

あなたがその問題を抱えている理由は、週の番号を取得するためにdatepartを使用しているためです。年が変わると、datepartは1に戻ります。これは変更する必要があります... 私はこの変更を行いました。シーケンスの順番で取得してください。これをチェックしてください。 変更は最初のCTEです。

/* 
    Display Patterns 
*/ 
select * from Pattern 

DECLARE @mindate DATE  = (SELECT MIN(StartDate) FROM Pattern) 
DECLARE @maxmindate DATE = (SELECT MAX(StartDate) FROM Pattern) 
DECLARE @maxcount INT  = (SELECT MAX(RecurrenceCount) FROM Pattern) 
DECLARE @maxdate DATE  = DATEADD(WK, @maxcount + 10, @maxmindate) 

declare @minWeekPart INT = DATEPART(WK,@MinDate) 
/* 
    CTE to generate required occurrences 
*/ 
;With cteKeyDate As (
    Select 
     KeyStartDate = @MinDate, 
     KeyDOW = DateName(WEEKDAY, @MinDate), 
     KeyWeek = @minWeekPart 
    Union All 
    Select 
     KeyStartDate = DateAdd(DD, 1, df.KeyStartDate) , 
     KeyDOW = DateName(WEEKDAY,DateAdd(DD, 1, df.KeyStartDate)), 
     KeyWeek= @minWeekPart + datediff(WK,@MinDate,DateAdd(DD, 1, df.KeyStartDate)) 
    From cteKeyDate DF 
    Where DF.KeyStartDate <= @MaxDate 
) 
--select * from cteKeyDate 
-- order by 1 
--Option (maxrecursion 32767) 


SELECT 
    Id, KeyStartDate, KeyDow, KeyWeek, RowNr, OccNr = ROW_NUMBER() OVER (PARTITION BY Id ORDER BY StartDate) 
FROM 
    (Select 
     A.Id 
     ,A.StartDate 
     ,A.EndDate 
     ,Count = A.RecurrenceCount 
     ,Days = A.WeekDays 
     ,Every = A.Periodicity    
     ,KeyStartDate = CASE 
     /* 
      if no periodicity (1) then it is sequential 
      if periodicity, first week doesn't apply (MIN KeyWeek) 
     */ 
     WHEN A.Periodicity = 1 
      OR (Periodicity <> 1 AND (SELECT MIN(C.StartingWeek) FROM Pattern AS C WHERE C.Id = A.Id) = KeyWeek) 
      THEN KeyStartDate 
     /* Otherwise formula ADD WEEKS => Current Week Min Week */ 
     ELSE 
      DATEADD(WK, ((A.Periodicity - 1) * (KeyWeek - (SELECT MIN(C.StartingWeek) FROM Pattern AS C WHERE C.Id = A.Id))) , KeyStartDate) 
     END 
     ,KeyDow 
     ,KeyWeek 
     ,RowNr = Row_Number() over (Partition By A.Id Order By B.KeyStartDate) 
     ,Periodicity = A.Periodicity 
    from 
     Pattern A 
     Join cteKeyDate B on B.KeyStartDate >= DATEADD(DAY, -1, A.StartDate) and Charindex(KeyDOW, A.WeekDays) > 0 
    ) Final     
Where 
    RowNr <= Count AND Id = 1 
    order by 2 
Option (maxrecursion 32767) 

私はrextesterでそれを実行しなかった、あなたはそれをここに見つけることができます - >http://rextester.com/GWEY37271

+0

こんにちは、実際にテストして、同じバグを再現しました。私はスクリーンショットを作成しました。週53日後、スクリプトは週数を増やして動作しますが、日付は間違って計算されます: https://snag.gy/MoNBxw.jpg – Raffaeu

+1

あなたのスクリーンショットはあまり明確ではありません。今のバグは16日26日です2017年はそうではないかもしれないし、日曜日の後の次の金曜日だ...それは完璧に収まっている。どの列が間違っている..あなたは私に教えてもらえますか?ああ、私が与えたrexterへのリンクは間違っているのは私の代わりにあなたのことを指しています... – Surendra

+0

行15は21-May-2017と言っていますが、23-April-2017 – Raffaeu