Snippets – Utilité des CTE récursives

Voici quelques snippets utilisant des CTE récursives à des fins utilitaires :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
-------------------------------------------------------------------------
-- Nicolas SOUQUET - 08/10/2008 - Découper une chaîne en ses caractères -
-------------------------------------------------------------------------
DECLARE @String VARCHAR(64)
SELECT @String = 'Ceci est un test ...';
 
WITH CTE_PARSE AS
(
    SELECT SUBSTRING(@String, 1, 1) Caractere, 1 Indice
  UNION ALL
    SELECT SUBSTRING(@String, Indice + 1, 1), Indice + 1
    FROM CTE_PARSE
    WHERE (Indice + 1) <= LEN(@String)
)
SELECT Caractere
FROM CTE_PARSE
OPTION (MAXRECURSION 0)
 
 
------------------------------------------------------------------------------------------------
-- Nicolas SOUQUET - 08/10/2008 - Découper les chaines d'une colonne d'une table en leurs mots -
------------------------------------------------------------------------------------------------
CREATE TABLE TbPhrase
(
  IDPhrase INT IDENTITY CONSTRAINT PK_TbPhrase PRIMARY KEY,
  Phrase VARCHAR(128)
)
GO
 
INSERT INTO dbo.TbPhrase VALUES ('C''est le printemps chic chic')
INSERT INTO dbo.TbPhrase VALUES ('Il fait beau demain')
GO
 
;WITH
  CTE (IDPhrase, Deb, Fin, Indice) AS  
  (  
      SELECT T.IDPhrase,
          1 Deb,
          CHARINDEX(CHAR(32), T.Phrase + CHAR(32)) Fin,
          1 AS Indice
      FROM dbo.TbPhrase AS T
    UNION ALL  
      SELECT T.IDPhrase,
          CTE.Fin + 1,
          CHARINDEX(CHAR(32), T.Phrase + CHAR(32), CTE.Fin + 1),
          CTE.Indice + 1
      FROM CTE
      JOIN dbo.TbPhrase AS T ON T.IDPhrase = CTE.IDPhrase
      WHERE CHARINDEX(CHAR(32), T.Phrase + CHAR(32), CTE.Fin + 1) > 0
  )
SELECT T.Phrase,
    SUBSTRING(T.Phrase, CTE.Deb , CTE.Fin - CTE.Deb) AS Mots
FROM CTE  
JOIN dbo.TbPhrase AS T ON T.IDPhrase = CTE.IDPhrase
ORDER BY CTE.IDPhrase
 
--------------------------------------------------------------------------------------------
-- Nicolas SOUQUET - 08/10/2008 - Decouper une chaine en ses mots séparés par des virgules -
--------------------------------------------------------------------------------------------
DECLARE @str VARCHAR(64)
SET @str = 'toto,titi,tutu,tata';
 
WITH CTE (Deb, Fin) AS
(
  SELECT 1 Deb, CHARINDEX(',', @str + ',') Fin
 UNION ALL
  SELECT Fin + 1, CHARINDEX(',', @str + ',', Fin + 1)
  FROM CTE
  WHERE CHARINDEX( ',', @str + ',', Fin + 1 ) > 0
)
SELECT SUBSTRING(@str, Deb , Fin - Deb), Deb, Fin
FROM CTE
 
--------------------------------------------------------------------------
-- Nicolas SOUQUET - 08/10/2008 - Découper une chaîne avec un séparateur -
--------------------------------------------------------------------------
DECLARE @String VARCHAR(64) SET @String = 'un,deux,trois,quatre,cinq';
DECLARE @Separator CHAR(1) SET @Separator = ',';
WITH INDICES AS
(
    SELECT 0 Deb, 1 Fin
  UNION ALL
    SELECT Fin, CHARINDEX(@Separator, @String, Fin) + LEN(@Separator)
    FROM INDICES
    WHERE Fin > Deb
)
SELECT SUBSTRING(
          @String,
          Deb,
          CASE
            WHEN Fin > LEN(@Separator) THEN Fin - Deb - LEN(@Separator)
            ELSE LEN(@String) - Deb + 1
          END
        ) String,
        Deb,
        Fin,
        LEN(@String) - Deb + 1 Lgr
FROM INDICES WHERE Deb > 0
 
---------------------------------------------------------------------------------------------------------------------
-- Nicolas SOUQUET - 08/10/2008 - Fonction de mise en majuscule de la première lettre de tous les mots d'une phrase -
---------------------------------------------------------------------------------------------------------------------
CREATE FUNCTION fPremiereLettreMajuscule (@str VARCHAR(8000))
  RETURNS VARCHAR(8000)
AS
BEGIN
  DECLARE @result VARCHAR(8000);
 
  -- Extraction des mots de la phrase par recherche des espaces
  WITH
    CTE_Espaces AS
    (
        SELECT 1 Deb, CHARINDEX(' ', @str + ' ') Fin
      UNION ALL
        SELECT Fin + 1, CHARINDEX(' ', @str + ' ', Fin + 1)
        FROM CTE_Espaces
        WHERE CHARINDEX(' ', @str + ' ', Fin + 1 ) > 0
    )
  -- Formatage des mots et concaténation
  SELECT @result = ISNULL(@result, '') + UPPER(SUBSTRING(SUBSTRING(@str, Deb, Fin), 1, 1)) + LOWER(SUBSTRING(SUBSTRING(@str, Deb, Fin - Deb + 1), 2, Fin))
  FROM CTE_Espaces
 
  RETURN @result
END
GO
 
-----------------------------------------------------------------------------
-- Nicolas SOUQUET - 08/10/2008 - Créer une plage de dates entre deux dates -
-----------------------------------------------------------------------------
DECLARE @DateDeb DATETIME SET @DateDeb = '20080810'
DECLARE @DateFin DATETIME SET @DateFin = '20080820';
WITH DATES AS
(
    SELECT @DateDeb Date, 1 Indice
  UNION ALL
    SELECT @DateDeb + Indice Date, Indice + 1 Indice
    FROM DATES
    WHERE Date < @DateFin
)
SELECT * FROM DATES
 
-------------------------------------------------------------------------
-- Nicolas SOUQUET - 08/10/2008 - Faire la somme de nombres d'une plage -
-------------------------------------------------------------------------
DECLARE @nMinBound INT,
    @nMaxBound INT
 
SET @nMinBound = 1
SET @nMaxBound = 10000;
 
WITH SommeDeNombres AS
(
    SELECT 0 Somme, @nMinBound minBound, @nMaxBound maxBound
  UNION ALL
    SELECT Somme + minBound, minBound + 1, maxBound
    FROM SommeDeNombres
    WHERE minBound <= maxBound
)
SELECT Somme
FROM SommeDeNombres
WHERE minBound = maxBound + 1
OPTION (MAXRECURSION 0)
 
----------------------------------------------------------------------------------------------------
-- Nicolas SOUQUET - 08/10/2008 - Générer la liste de tous les jours d'un mois de l'année courante -
----------------------------------------------------------------------------------------------------
DECLARE @month TINYINT
SET @month = 11;
 
WITH
  CTE_Days AS
  (
      SELECT DATEADD(month, @month, DATEADD(month, -MONTH(GETDATE()), DATEADD(day, -DAY(GETDATE()) + 1, CAST(FLOOR(CAST(GETDATE() AS FLOAT)) AS DATETIME)))) D
    UNION ALL
      SELECT DATEADD(day, 1, D)
      FROM CTE_Days
      WHERE D < DATEADD(day, -1, DATEADD(month, 1, DATEADD(month, @month, DATEADD(month, -MONTH(GETDATE()), DATEADD(day, -DAY(GETDATE()) + 1, CAST(FLOOR(CAST(GETDATE() AS FLOAT)) AS DATETIME))))))
  )
SELECT D
FROM CTE_Days

ElSuket

4 réflexions au sujet de « Snippets – Utilité des CTE récursives »

  1. Je reprends ton idée en tout dynamique (je déteste les chaînes pour créer des dates) :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    WITH&nbsp;<br />
    &nbsp;&nbsp;CTE_JOURS AS&nbsp;<br />
    &nbsp;&nbsp;(&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SELECT CAST(FLOOR(CAST(DATEADD(day, -DATEPART(dayofyear, GETDATE()) + 1, GETDATE()) AS FLOAT)) AS DATETIME) Jour&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;UNION ALL&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SELECT DATEADD(day, 1, Jour)&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FROM CTE_JOURS&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WHERE Jour &lt;= DATEADD&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;day,&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DATEPART(dayofyear, DATEADD(day, -1, DATEADD(year, 1, CAST(FLOOR(CAST(DATEADD(day, -DATEPART(dayofyear, GETDATE()) + 1, GETDATE()) AS FLOAT)) AS DATETIME)))) - 2,&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CAST(FLOOR(CAST(DATEADD(day, -DATEPART(dayofyear, GETDATE()) + 1, GETDATE()) AS FLOAT)) AS DATETIME)&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)&nbsp;<br />
    &nbsp;<br />
    &nbsp;&nbsp;)&nbsp;<br />
    SELECT Jour&nbsp;<br />
    FROM CTE_JOURS&nbsp;<br />
    OPTION (MAXRECURSION 366)&nbsp;<br />

    As-tu regardé la réponse que je t’ai faite ici ?
    Est-ce que c’était ce qui te fallait ?

    @++ ;)

  2. Encore un essai dont je voudrais partager le résultat

    DECLARE @Year Int
    SET @Year=2009;

    WITH CTE_Days AS
    (
    SELECT Cast (Cast (@Year As Char(4)) + ‘-01-01′ As DateTime) As [Day]
    UNION ALL
    SELECT DateAdd(day, 1, [Day])
    FROM CTE_Days
    WHERE Year(CTE_Days.[Day]) = @Year
    )

    SELECT [Day]
    FROM CTE_Days
    WHERE Year([Day])=@Year
    OPTION (MAXRECURSION 366);

    Le résultat sera un table avec une colonne nommée Day et listant chaque jour de l’année spécifiée. Simple et efficace pour générer un calendrier

    Day
    ———————–
    2009-01-01 00:00:00.000
    2009-01-02 00:00:00.000
    2009-01-03 00:00:00.000
    2009-01-04 00:00:00.000



    2009-12-30 00:00:00.000
    2009-12-31 00:00:00.000

  3. Je me suis inspiré de ton exemple CTE_Days pour l’adapter à mes besoins : je devais créer une table ayant un record pour chaque mois de l’année avec la date de début qui est toujours au premier jour de l’année et la date de fin de la période le dernier jour du mois soit fin Janvier, fin Février, fin Mars, … Ceci pour des calculs cumulatifs.

    Voici ma contribution :

    DECLARE @Year Int
    SET @Year = 2009;

    WITH
    CTE_CumulPeriods AS
    (
    SELECT Cast(Cast(@Year as char(4))+ ‘-01-01′ As DateTime) As StartDate, Cast(Cast(@Year as char(4)) + ‘-01-31 23:59:59.600′ As DateTime) As EndDate
    UNION ALL
    SELECT StartDate, DateAdd(day, -1, DateAdd(month, 1, DateAdd(day, 1, EndDate)))
    FROM CTE_CumulPeriods
    WHERE Month(EndDate) < 12
    )

    SELECT StartDate, EndDate
    FROM CTE_CumulPeriods;

    Le résultat sera
    StartDate EndDate
    ———————– ———————–
    2009-01-01 00:00:00.000 2009-01-31 23:59:59.600
    2009-01-01 00:00:00.000 2009-02-28 23:59:59.600
    2009-01-01 00:00:00.000 2009-03-31 23:59:59.600
    2009-01-01 00:00:00.000 2009-04-30 23:59:59.600
    2009-01-01 00:00:00.000 2009-05-31 23:59:59.600
    2009-01-01 00:00:00.000 2009-06-30 23:59:59.600
    2009-01-01 00:00:00.000 2009-07-31 23:59:59.600
    2009-01-01 00:00:00.000 2009-08-31 23:59:59.600
    2009-01-01 00:00:00.000 2009-09-30 23:59:59.600
    2009-01-01 00:00:00.000 2009-10-31 23:59:59.600
    2009-01-01 00:00:00.000 2009-11-30 23:59:59.600
    2009-01-01 00:00:00.000 2009-12-31 23:59:59.600

Laisser un commentaire