2016-11-29 106 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 

虽然第二个模式计算得很好。我认为当模式跨越了一年,并且在SQL中重置为0的周数时,我在逻辑上存在问题,但是我找不到解决方案,因此我现在挣扎了好几天。

您可以使用示例here执行代码。

+0

作为参考点,你并不需要一个'PARTITION BY'在'ROW_NUMBER'和*请*不A'''B','C'部署代码表别名。 – iamdave

+0

@iamdave关于别名,我不使用A,B,C但A代表“活动”,所以是一个众所周知的别名 关于PARTITION BY是否需要,否则当查询运行在某些SQL Server中的多个模式(如2008)时,这些行没有正确排列 – 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 

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 

的@preferreddate表是使用以下填充分裂字符串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

你做了@ danie-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是错误它指向.yours而不是我的... – Surendra

+0

排15说2017年5月21日,但它应该是2017年4月23日 – Raffaeu