2016-11-29 1 views
3

Je travaille sur un CTE qui calcule les récurrences d'une semaine mais j'ai quelques problèmes quand le modèle traverse l'année.T-SQL Calculer les récurrences d'une date

Le CTE devrait Calculer toutes les occurrences en fonction des paramètres suivants:

  • Récurrence Count - combien de fois il va se passer
  • Jours de la semaine - où jour de la semaine, il se produira
  • Date de début - lorsque le calcul du modèle va commencer
  • Périodicité - Combien de fois en termes de semaines, soit 1 chaque semaine, 2 toutes les 2 semaines
  • semaine à partir - Le numéro de la semaine de la première occurrence, ce qui reflète la colonne Date de début

Voici comment je stocke les modèles:

/* 
    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 

); 

Voici un couple de motifs que je me sers pour tester mon 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')); 

je commence à compter l'examen premier jour de la semaine lundi

SET DATEFIRST 1 

Et cela est le CTE j'utilise pour exécuter ce calcul:

/* 
    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) 

Maintenant, si je teste à nouveau mon modèles, par exemple, le premier, je reçois ce résultat, qui a le bug lorsque les occurrences se produisent dans l'année suivante. Le RowNr 15 est faux car il devrait arriver le 23 avril (dimanche) et non la semaine prochaine.

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 

Alors que le deuxième modèle est calculé très bien. Je pense que j'ai un problème dans la logique quand le motif traverse l'année et le nombre de semaines remis à 0 en SQL mais je ne trouve pas de solution, j'ai lutté maintenant pour quelques jours.

Vous pouvez exécuter le code avec les exemples here.

+0

En tant que point de référence, vous n'avez pas besoin que 'PARTITION BY' dans votre' ROW_NUMBER' et * * S'il vous plaît ne pas déployer du code avec 'A',' B', 'C' alias de table. – iamdave

+0

@iamdave sur les alias, je n'utilise pas A, B, C mais A signifie "Activités" donc un alias bien connu A propos de PARTITION BY est nécessaire sinon la requête s'exécute sur plusieurs modèles dans certains serveurs SQL comme 2008, les lignes ne sont pas commandées correctement – Raffaeu

+1

Pouvez-vous expliquer ce que le CTE essaie de faire, ce que vous essayez d'accomplir ici, qui nous permettrait de mieux comprendre votre code, actuellement la date de début et la date de fin sont les mêmes table, pouvez-vous s'il vous plaît essayer de nous dire ce que vous essayez d'accomplir ici .. – Surendra

Répondre

1

passer un certain temps à ce sujet. Votre calcul utilisé est erroné.À moins qu'il n'y ait des règles spéciales non indiquées dans votre question, je ne vois pas pourquoi certaines dates sont spéciales. Je préfère utiliser des tables variables.

/* 
     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 
    ) 

Il est très important de toujours récupérer le réglage actuel de datefirst. Vous allez casser le calcul de quelqu'un d'autre s'il utilise un autre paramètre. J'ai également ajouté l'identifiant de motif pour des raisons évidentes.

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 

Le tableau @preferreddate est rempli en utilisant les éléments suivants:

/* 
     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]ID), 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 

Le tableau CTE finale est rempli par les éléments suivants:

/* 
     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 

Résultats

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 

Le splitstring f Onction:

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

c'est très bien, donne moi ce soir pour jouer avec et je serai de retour avec la réponse, merci pour l'effort ;-) – Raffaeu

+0

Vous l'avez fait @ danie-schoeman, il m'a fallu du temps pour le tester et refactoriser ma vue actuelle, donc avec votre solution j'utilise maintenant une fonction de table et non plus une vue mais Je dois dire que ça marche, c'est rapide et ça couvre tous les motifs. Merci!! – Raffaeu

+1

Je suis content d'avoir pu aider. –

-1

La raison pour laquelle vous obtenez ce problème est que vous utilisez la partie date pour obtenir les numéros de semaine, donc une fois l'année change la datepart revient à 1. Cela devrait être changé ... J'ai fait cette modification et les dates Maintenant, obtenez dans l'ordre de la séquence, vérifiez cela. Le changement est le premier CTE en passant.

/* 
    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) 

Je l'ai fait courir dans le rextester et vous pouvez le trouver ici ->http://rextester.com/GWEY37271

+0

Salut, en fait j'ai testé et il reproduit le même bug, j'ai fait une capture d'écran. Après la semaine 53, votre script fonctionne en augmentant le nombre de semaines, mais les dates sont incorrectement calculées: https://snag.gy/MoNBxw.jpg – Raffaeu

+1

Votre capture d'écran n'est pas si claire pour moi, quel est le bug maintenant la 16 date est 26 mai 2017 n'est pas bon et c'est le vendredi suivant après le dimanche ... ça correspond parfaitement. Quelle rangée est dans l'erreur .. pouvez-vous s'il vous plaît laissez-moi ... oh par la façon dont le lien à rexter je donne est mauvais il pointe vers .votre au lieu du mien ... – Surendra

+0

Row 15 dit 21 mai 2017 mais il devrait être 23 avril 2017 – Raffaeu