Je voudrais créer une procédure stockée qui créera une ligne dans un tableau pour chaque jour dans une plage de dates donnée. La procédure stockée accepte deux entrées: une date de début et une date de fin de la plage de dates souhaitée par l'utilisateur.
Alors, disons que j'ai une table comme ça:
SELECT Day, Currency
FROM ConversionTable
Le jour est un DateTime et la devise n'est qu'un entier.
Pour simplifier les choses, disons simplement que je veux toujours que la colonne Devise soit 1 pour chacune de ces lignes insérées. Donc, si quelqu'un saisit '5 mars 2017' comme date de début et '11 avril 2017' comme date de fin, je voudrais que les lignes suivantes soient créées:
2017-03-05 00:00:00, 1
2017-03-06 00:00:00, 1
...
2017-04-11 00:00:00, 1
Quelle est la meilleure façon de coder la procédure stockée pour ce faire? J'utilise SQL Server 2008 R2 dans mon environnement de test, mais notre environnement réel utilise SQL Server 2012, donc je peux mettre à niveau ma machine de test si de nouvelles fonctionnalités introduites en 2012 facilitent cette tâche.
Une option est un CTE récursif:
DECLARE @StartDate datetime = '2017-03-05'
,@EndDate datetime = '2017-04-11'
;
WITH theDates AS
(SELECT @StartDate as theDate
UNION ALL
SELECT DATEADD(day, 1, theDate)
FROM theDates
WHERE DATEADD(day, 1, theDate) <= @EndDate
)
SELECT theDate, 1 as theValue
FROM theDates
OPTION (MAXRECURSION 0)
;
(MAXRECURSION
indice ajouté grâce au commentaire de Scott Hodgin, ci-dessous.)
Une autre option consiste à utiliser une fonction de valeur de table. Cette approche est très rapide et offre un peu plus de flexibilité. Vous fournissez la plage de date/heure, la partie de date et l'incrément. Offre également l'avantage de l'inclure dans une APPLICATION CROISÉE
Par exemple
Select * from [dbo].[udf-Range-Date]('2017-03-05','2017-04-11','DD',1)
retourne
RetSeq RetVal
1 2017-03-05 00:00:00.000
2 2017-03-06 00:00:00.000
3 2017-03-07 00:00:00.000
4 2017-03-08 00:00:00.000
5 2017-03-09 00:00:00.000
...
36 2017-04-09 00:00:00.000
37 2017-04-10 00:00:00.000
38 2017-04-11 00:00:00.000
l'UDF si intéressé
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<=@R2
)
/*
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)
*/
En utilisant le post d'Aaron Bertrand sur comment créer un tableau de dimension de date à titre d'exemple, j'ai trouvé ceci:
DECLARE @StartDate DATE ='2017-03-05 00:00:00'
DECLARE @EndDate DATE ='2017-04-11 00:00:00'
Declare @DateTable table ([date] DATE PRIMARY KEY);
-- use the catalog views to generate as many rows as we need
INSERT @DateTable ([date])
SELECT d
FROM (
SELECT d = DATEADD(DAY, rn - 1, @StartDate)
FROM (
SELECT TOP (DATEDIFF(DAY, @StartDate, @EndDate)) rn = ROW_NUMBER() OVER (
ORDER BY s1.[object_id]
)
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
-- on my system this would support > 5 million days
ORDER BY s1.[object_id]
) AS x
) AS y;
SELECT *
FROM @DateTable
ORDER BY [date]
Vous devriez pouvoir mettre ce type de logique dans votre procédure stockée et ajouter tout ce dont vous avez besoin.
J'ai dû résoudre un problème similaire récemment sur Redshift où je n'avais qu'un accès en lecture et j'avais donc besoin d'une solution purement SQL (pas de procédures stockées) pour obtenir une ligne pour chaque heure dans une plage de dates comme point de départ pour mon jeu de résultats. Je suis sûr que d'autres peuvent rendre cela plus élégant et le modifier à leurs fins, mais pour ceux qui en ont besoin, voici ma solution piratée:
with hours as
(select 0 clockhour union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9 union select 10 union select 11 union select 12
union select 13 union select 14 union select 15 union select 16 union select 17 union select 18 union select 19 union select 20 union select 21 union select 22 union select 23)
, days as
(select *
from
(select to_number(n0.number || n1.number, '99') daynum
from
(select 0 as number union select 1 union select 2 union select 3) as n0
cross join
(select 1 as number union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9 union select 0) as n1)
where daynum between 1 and 31)
, months as
(select 1 as monthnum, 'jan' as themonth, 31 as numdays union select 2, 'feb', 28 union select 3, 'mar', 31 union select 4, 'apr', 30 union select 5, 'may', 31 union select 6, 'jun', 30
union select 7, 'jul', 31 union select 8, 'aug', 31 union select 9, 'sep', 30 union select 10, 'oct', 31 union select 11, 'nov', 30 union select 12, 'dec', 31)
, years as
(select century || decade || yr as yr
from
(select 19 century union select 20)
cross join
(select 0 decade union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9)
cross join
(select 0 yr union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9))
select cast(daynum || '-' || themonth || '-' || yr || ' ' || clockhour || ':00:00' as timestamp) dayhour
from hours
cross join days
cross join months
cross join years
where cast(daynum || '-' || themonth || '-' || yr || ' ' || clockhour || ':00:00' as timestamp)
between date_trunc('month', dateadd('month', -$MONTHS_AGO, getdate()))
and date_trunc('month', dateadd('month', $MONTHS_AHEAD, getdate()))
and daynum <= numdays
order by yr, monthnum, daynum, clockhour;