Il n'y a pas d'autre problème lié à cette question. La question ci-dessus est le problème, la maîtrise des cours SQL.
Pentium10 du
Avez-vous juste besoin d'un tableau de dates basé sur une plage de dates sélectionnée?
Derek Adair
1
Je pense à un usage, pour vous trouver un problème ... Si vous obtenez une tâche pour remplir certains enregistrements manquants dans votre tableau. Et vous devez exécuter une requête pour chaque jour où je pense à quelque chose commeinsert into table select ... as days date between '' and ''
Pentium10
13
Un exemple d'utilisation serait de générer des statistiques et d'inclure une ligne pour les dates sur lesquelles vous n'avez aucune donnée. Si vous faites une sorte de group-by, il peut être beaucoup plus rapide de générer toutes les informations en SQL et de les ajouter dans le format dont vous avez besoin, au lieu de vider vos données telles quelles dans votre langue, et de commencer à boucler et à ajouter votre se vide.
Nanne
1
@Nanne c'est précisément pourquoi j'ai sauvegardé cette question. J'ai besoin de ce qui précède pour LEFT JOIN dans des données qui peuvent ne pas exister à certaines dates.
Josh Diehl
Réponses:
318
Cette solution n'utilise aucune boucle, procédure ou table temporaire . La sous-requête génère des dates pour les 10 000 derniers jours et peut être étendue pour aller aussi loin que vous le souhaitez.
select a.Date
from(select curdate()- INTERVAL (a.a +(10* b.a)+(100* c.a)+(1000* d.a)) DAY as Date
from(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as a
crossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as b
crossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as c
crossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as d
) a
where a.Date between'2010-01-20'and'2010-01-24'
Production:
Date
----------2010-01-242010-01-232010-01-222010-01-212010-01-20
Remarques sur les performances
En le testant ici , les performances sont étonnamment bonnes: la requête ci-dessus prend 0,0009 sec.
Si nous étendons la sous-requête pour générer env. 100 000 numéros (et donc environ 274 ans de dates), il s'exécute en 0,0458 sec.
Incidemment, c'est une technique très portable qui fonctionne avec la plupart des bases de données avec des ajustements mineurs.
Vous verrez de meilleures performances si vous passez UNIONà UNION ALL- c'est perdre du temps à vérifier les doublons à supprimer qui n'existent pas. Cependant, l'OMI est trop compliquée - si vous allez construire un jeu de résultats à l'aide d'UNION, pourquoi ne pas simplement spécifier la date et en finir?
OMG Ponies
7
pourquoi ne pas simplement spécifier la date et en finir avec elle - parce que la méthode ci-dessus vous permet de créer des ensembles arbitrairement grands de nombres (et de dates) ne nécessitant aucune création de table, ce serait pénible à coder en dur de la manière que vous suggérez. Évidemment, pour 5 dates, c'est exagéré; mais même dans ce cas, si vous vous joignez à une table dont vous ne connaissez pas les dates à l'avance, mais uniquement les valeurs potentielles min et max, cela a du sens.
RedFilter le
2
Il est "pénible" d'utiliser simplement la fonction DATETIME à la place de l'instruction UNION que vous avez déjà créée? Cela élimine tout besoin de logique que vous deviez ajouter . Par conséquent, vous avez trop compliqué la requête. La déclaration UNION, de toute façon, n'est pas évolutive - spécifiant une date ou un nombre, qui veut la mettre à jour pour accueillir, disons, 20 ou 30 dates?
OMG Ponies
23
C'est vraiment agréable de voir une réponse à la question, pas des commentaires interminables sur la façon dont cela ne peut pas ou ne devrait pas être fait. La plupart des choses peuvent être faites, et «devrait» n'a de sens que dans le contexte, qui diffère pour tout le monde. Cette réponse m'a aidé, même si je suis bien conscient qu'il existe de meilleures façons dans la plupart des situations.
joe
7
Ceux d'entre vous qui ne peuvent pas faire fonctionner cette requête: Veuillez vous gifler et relire le commentaire du PO sur cette requête générant 1000 dates. Étant donné que 2010 remonte à plus de 1000 jours, vous devrez ajuster la requête en conséquence.
Noel Baron
32
Voici une autre variante utilisant des vues:
CREATEVIEW digits ASSELECT0AS digit UNIONALLSELECT1UNIONALLSELECT2UNIONALLSELECT3UNIONALLSELECT4UNIONALLSELECT5UNIONALLSELECT6UNIONALLSELECT7UNIONALLSELECT8UNIONALLSELECT9;CREATEVIEW numbers ASSELECT
ones.digit + tens.digit *10+ hundreds.digit *100+ thousands.digit *1000AS number
FROM
digits as ones,
digits as tens,
digits as hundreds,
digits as thousands;CREATEVIEW dates ASSELECT
SUBDATE(CURRENT_DATE(), number)AS date
FROM
numbers;
Et puis vous pouvez simplement faire (voir à quel point c'est élégant?):
SELECT
date
FROM
dates
WHERE
date BETWEEN'2010-01-20'AND'2010-01-24'ORDERBY
date
Mettre à jour
Il est à noter que vous ne pourrez générer des dates passées qu'à partir de la date actuelle . Si vous souhaitez générer n'importe quel type de plage de dates (passé, futur et intermédiaire), vous devrez utiliser cette vue à la place:
CREATEVIEW dates ASSELECT
SUBDATE(CURRENT_DATE(), number)AS date
FROM
numbers
UNIONALLSELECT
ADDDATE(CURRENT_DATE(), number +1)AS date
FROM
numbers;
Cela ne fonctionne pas dans tous les cas. CHOISIR la date FROM dates WHERE date ENTRE '2014-12-01' ET '2014-12-28' ORDER BY date
vasanth
3
Bon appel @ user927258. En effet, la première vue datesmentionnée ci-dessus calcule les dates à partir de la date actuelle, c'est pourquoi vous ne pourrez pas récupérer les dates définies dans le futur. La réponse de @RedFilter souffre du même défaut de conception. J'ai ajouté une solution de contournement dans ma réponse.
Stéphane
L'utilisation de certaines vues simplifie définitivement les requêtes et les rend réutilisables. Bien qu'elles fassent essentiellement la même chose, toutes ces UNIONclauses semblent étranges dans une seule instruction SQL.
Stewart le
24
La réponse acceptée ne fonctionnait pas pour PostgreSQL (erreur de syntaxe à ou près de "a").
La façon dont vous faites cela dans PostgreSQL est d'utiliser la generate_seriesfonction, c'est-à-dire:
SELECT day::date
FROM generate_series('2010-01-20','2010-01-24', INTERVAL '1 day') day;
day
------------2010-01-202010-01-212010-01-222010-01-232010-01-24(5rows)
À l'aide d'une expression de table commune (CTE) récursive, vous pouvez générer une liste de dates, puis en sélectionner. Évidemment, vous ne voudriez normalement pas créer trois millions de dates, donc cela illustre simplement les possibilités. Vous pouvez simplement limiter la plage de dates à l'intérieur du CTE et omettre la clause where de l'instruction select à l'aide du CTE.
select datetable.Date
from(select DATEADD(day,-(a.a +(10* b.a)+(100* c.a)),getdate())AS Date
from(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as a
crossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as b
crossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as c
) datetable
where datetable.Date between'2014-01-20'and'2014-01-24'orderby datetable.Date DESC
Production
Date
-----2014-01-2312:35:25.2502014-01-2212:35:25.2502014-01-2112:35:25.2502014-01-2012:35:25.250
Si j'avais seulement descendu un peu plus ... soupir. Bref, merci. J'ai ajouté un CAST (<expression> AS DATE) pour supprimer l'heure sur ma version. Également utilisé où a.Date entre GETDATE () - 365 AND GETDATE () ... si vous exécutez votre requête aujourd'hui, il ne donnerait aucune ligne si vous ne remarquez pas les dates dans le WHERE = P
Ricardo C
4
La solution à l'ancienne pour faire cela sans boucle / curseur est de créer une NUMBERStable, qui a une seule colonne Integer avec des valeurs commençant à 1.
Vous devez remplir la table avec suffisamment d'enregistrements pour couvrir vos besoins:
INSERTINTO NUMBERS (id)VALUES(NULL);
Une fois que vous avez le NUMBERStableau, vous pouvez utiliser:
SELECT x.start_date + INTERVAL n.id-1 DAY
FROM NUMBERS n
JOIN(SELECT STR_TO_DATE('2010-01-20','%Y-%m-%d')AS start_date
FROM DUAL) x
WHERE x.start_date + INTERVAL n.id-1 DAY <='2010-01-24'
Pour générer des listes de dates ou de nombres afin de LEFT JOIN sur. Vous le feriez pour voir où il y a des lacunes dans les données, parce que vous êtes GAUCHE JOINing sur une liste de données séquentielles - les valeurs nulles rendront évident les lacunes.
La DUALtable est prise en charge par Oracle et MySQL pour être utilisée comme table de remplacement dans la FROMclause. Il n'existe pas, en sélectionnant des valeurs, il retournera quelle que soit la valeur. L'idée était d'avoir le remplaçant car une requête SELECT nécessite une FROMclause spécifiant au moins une table.
OMG Ponies
1
+1 pour créer réellement une table de nombres permanents au lieu de faire en sorte que le SGBDR la crée à chaque fois que vous avez besoin de la requête. Les tables auxiliaires ne sont pas maléfiques, les gens!
Bacon Bits
4
Pour Access 2010 - plusieurs étapes requises; J'ai suivi le même schéma que celui publié ci-dessus, mais j'ai pensé que je pourrais aider quelqu'un dans Access. Cela a très bien fonctionné pour moi, je n'ai pas eu à garder une table de dates prédéfinie.
Créez une table appelée DUAL (similaire au fonctionnement de la table Oracle DUAL)
ID (numéro automatique)
DummyColumn (texte)
Ajouter une valeur de ligne (1, "DummyRow")
Créez une requête nommée "ZeroThru9Q"; entrez manuellement la syntaxe suivante:
thx Pentium10 - vous m'avez fait rejoindre stackoverflow :) - c'est mon portage vers msaccess - je pense que cela fonctionnera sur n'importe quelle version:
SELECT date_value
FROM(SELECT a.espr1+(10*b.espr1)+(100*c.espr1)AS integer_value,
dateadd("d",integer_value,dateserial([start_year],[start_month],[start_day]))as date_value
FROM(select*from(selecttop1"0"as espr1 from MSysObjects
unionallselecttop1"1"as espr2 from MSysObjects
unionallselecttop1"2"as espr3 from MSysObjects
unionallselecttop1"3"as espr4 from MSysObjects
unionallselecttop1"4"as espr5 from MSysObjects
unionallselecttop1"5"as espr6 from MSysObjects
unionallselecttop1"6"as espr7 from MSysObjects
unionallselecttop1"7"as espr8 from MSysObjects
unionallselecttop1"8"as espr9 from MSysObjects
unionallselecttop1"9"as espr9 from MSysObjects
)as a,(selecttop1"0"as espr1 from MSysObjects
unionallselecttop1"1"as espr2 from MSysObjects
unionallselecttop1"2"as espr3 from MSysObjects
unionallselecttop1"3"as espr4 from MSysObjects
unionallselecttop1"4"as espr5 from MSysObjects
unionallselecttop1"5"as espr6 from MSysObjects
unionallselecttop1"6"as espr7 from MSysObjects
unionallselecttop1"7"as espr8 from MSysObjects
unionallselecttop1"8"as espr9 from MSysObjects
unionallselecttop1"9"as espr9 from MSysObjects
)as b,(selecttop1"0"as espr1 from MSysObjects
unionallselecttop1"1"as espr2 from MSysObjects
unionallselecttop1"2"as espr3 from MSysObjects
unionallselecttop1"3"as espr4 from MSysObjects
unionallselecttop1"4"as espr5 from MSysObjects
unionallselecttop1"5"as espr6 from MSysObjects
unionallselecttop1"6"as espr7 from MSysObjects
unionallselecttop1"7"as espr8 from MSysObjects
unionallselecttop1"8"as espr9 from MSysObjects
unionallselecttop1"9"as espr9 from MSysObjects
)as c
)as d)WHERE date_value
between dateserial([start_year],[start_month],[start_day])and dateserial([end_year],[end_month],[end_day]);
référencé MSysObjects juste 'parce que l'accès a besoin d'une table comptant au moins 1 enregistrement, dans une clause from - n'importe quelle table avec au moins 1 enregistrement ferait l'affaire.
Comme indiqué (ou du moins mentionné) dans de nombreuses réponses merveilleuses déjà données, ce problème est facilement résolu une fois que vous avez un ensemble de nombres avec lesquels travailler.
Remarque: Ce qui suit est T-SQL mais c'est simplement ma mise en œuvre particulière des concepts généraux déjà mentionnés ici et sur Internet en général. Il devrait être relativement simple de convertir le code dans le dialecte de votre choix.
Comment? Considérez cette requête:
SELECT DATEADD(d, N,'0001-01-22')FROM Numbers -- A table containing the numbers 0 through NWHERE N <=5;
Ce qui précède produit la plage de dates 1/22/0001 - 1/27/0001 et est extrêmement trivial. La requête ci-dessus contient 2 informations clés: la date de début0001-01-22 et le décalage de 5. Si nous combinons ces deux informations, nous avons évidemment notre date de fin. Ainsi, étant donné deux dates, la génération d'une plage peut être décomposée comme suit:
Trouvez la différence entre deux dates données (le décalage), facile:
Utiliser ABS()ici garantit que l'ordre des dates n'est pas pertinent.
Générez un ensemble limité de nombres, également facile:
-- Returns the numbers 0-2
SELECT N = ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) - 1
FROM(SELECT 'A' AS S UNION ALL SELECT 'A' UNION ALL SELECT 'A')
Notez que nous ne nous soucions pas vraiment de ce que nous sélectionnons FROMici. Nous avons juste besoin d'un ensemble avec lequel travailler afin de compter le nombre de lignes qu'il contient. J'utilise personnellement un TVF, certains utilisent un CTE, d'autres utilisent une table de nombres à la place, vous voyez l'idée. Je préconise d'utiliser la solution la plus performante que vous comprenez également.
La combinaison de ces deux méthodes résoudra notre problème:
DECLARE@date1 DATE ='9001-11-21';DECLARE@date2 DATE ='9001-11-23';SELECT D = DATEADD(d, N,@date1)FROM(SELECT N = ROW_NUMBER()OVER(ORDERBY(SELECTNULL))-1FROM(SELECT'A'AS S UNIONALLSELECT'A'UNIONALLSELECT'A') S
) Numbers
WHERE N <= ABS(DATEDIFF(d,@date1,@date2));
L'exemple ci-dessus est un code horrible mais montre comment tout se réunit.
Plus amusant
J'ai besoin de beaucoup faire ce genre de chose, alors j'ai encapsulé la logique dans deux TVF. Le premier génère une plage de nombres et le second utilise cette fonctionnalité pour générer une plage de dates. Le calcul est de s'assurer que l'ordre d'entrée n'a pas d'importance et parce que je voulais utiliser toute la gamme de nombres disponibles dans GenerateRangeSmallInt.
La fonction suivante prend ~ 16 ms de temps CPU pour renvoyer la plage maximale de 65536 dates.
CREATEFUNCTION dbo.GenerateRangeDate (@date1 DATE,@date2 DATE
)
RETURNS TABLEWITH SCHEMABINDING
ASRETURN(SELECT D = DATEADD(d, N +32768,CASEWHEN@date1 <=@date2 THEN@date1 ELSE@date2 END)FROM dbo.GenerateRangeSmallInt(-32768, ABS(DATEDIFF(d,@date1,@date2))-32768));
GO
CREATEFUNCTION dbo.GenerateRangeSmallInt (@num1 SMALLINT =-32768,@num2 SMALLINT =32767)
RETURNS TABLEWITH SCHEMABINDING
ASRETURN(WITH Numbers(N)AS(SELECT N FROM(VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 16,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 32,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 48,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 64,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 80,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 96,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 112,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 128,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 144,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 160,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 176,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 192,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 208,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 224,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 240,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 256) V (N))SELECTTOP(ABS(CAST(@num1 AS INT)- CAST(@num2 AS INT))+1)
N = ROW_NUMBER()OVER(ORDERBY(SELECTNULL))+CASEWHEN@num1 <=@num2 THEN@num1 ELSE@num2 END-1FROM Numbers A
, Numbers B
);
SELECT@row:=@row+1asrowFROM(select0unionallselect1unionallselect3unionallselect4unionallselect5unionallselect6unionallselect6unionallselect7unionallselect8unionallselect9) t,(select0unionallselect1unionallselect3unionallselect4unionallselect5unionallselect6unionallselect6unionallselect7unionallselect8unionallselect9) t2,(select0unionallselect1unionallselect3unionallselect4unionallselect5unionallselect6unionallselect6unionallselect7unionallselect8unionallselect9) t3,(select0unionallselect1unionallselect3unionallselect4unionallselect5unionallselect6unionallselect6unionallselect7unionallselect8unionallselect9) t4,(SELECT@row:=0) r
limit 4
ce qui se traduira par
row1.02.03.04.0
Les lignes peuvent maintenant être utilisées pour créer une liste de dates à partir de la date de début donnée. Pour inclure la date de début, nous commençons par la ligne -1;
select date_add('2010-01-20', interval row day)from(SELECT@row:=@row+1asrowFROM(select0unionallselect1unionallselect3unionallselect4unionallselect5unionallselect6unionallselect6unionallselect7unionallselect8unionallselect9) t,(select0unionallselect1unionallselect3unionallselect4unionallselect5unionallselect6unionallselect6unionallselect7unionallselect8unionallselect9) t2,(select0unionallselect1unionallselect3unionallselect4unionallselect5unionallselect6unionallselect6unionallselect7unionallselect8unionallselect9) t3,(select0unionallselect1unionallselect3unionallselect4unionallselect5unionallselect6unionallselect6unionallselect7unionallselect8unionallselect9) t4,(SELECT@row:=-1) r
) sequence
where date_add('2010-01-20', interval row day)<='2010-01-24'
WITH CTE AS(SELECTDISTINCTconvert(varchar(10),StartTime,101)AS StartTime,
datediff(dd,StartTime, endTime)AS diff
FROM dbo.testdate
UNIONALLSELECT StartTime,
diff -1AS diff
FROM CTE
WHERE diff<>0)SELECTDISTINCT DateAdd(dd,diff, StartTime)AS StartTime
FROM CTE
Explication: Explication de la requête récursive CTE
Première partie de la requête:
SELECT DISTINCT convert(varchar(10), StartTime, 101) AS StartTime, datediff(dd, StartTime, endTime) AS diff FROM dbo.testdate
Explication: la première colonne correspond à "date de début", la deuxième colonne correspond à la différence entre les dates de début et de fin en jours et sera considérée comme une colonne "diff"
Deuxième partie de la requête:
UNION ALL SELECT StartTime, diff-1 AS diff FROM CTE WHERE diff<>0
Explication: Union all héritera du résultat de la requête ci-dessus jusqu'à ce que le résultat devienne nul, donc le résultat «StartTime» est hérité de la requête CTE générée, et de diff, diminue - 1, donc cela ressemble à 3, 2 et 1 jusqu'à 0
STARTDATE Specification
10/24/2012--> From Record 110/27/2012--> From Record 210/27/2012--> From Record 210/27/2012--> From Record 210/30/2012--> From Record 3
3e partie de la requête
SELECT DISTINCT DateAdd(dd,diff, StartTime) AS StartTime FROM CTE
Il ajoutera le jour «diff» dans «startdate» donc le résultat devrait être comme ci-dessous
(SELECT TRIM('2016-01-05'+ INTERVAL a + b DAY) date
FROM(SELECT0 a UNIONSELECT1 a UNIONSELECT2UNIONSELECT3UNIONSELECT4UNIONSELECT5UNIONSELECT6UNIONSELECT7UNIONSELECT8UNIONSELECT9) d,(SELECT0 b UNIONSELECT10UNIONSELECT20UNIONSELECT30UNIONSELECT40) m
WHERE'2016-01-05'+ INTERVAL a + b DAY <='2016-01-21')
Pour tous ceux qui le souhaitent en tant que vue enregistrée (MySQL ne prend pas en charge les instructions de sélection imbriquées dans les vues):
createview zero_to_nine asselect0as n unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9;createview date_range asselect curdate()- INTERVAL (a.n +(10* b.n)+(100* c.n)) DAY as date
from zero_to_nine as a
crossjoin zero_to_nine as b
crossjoin zero_to_nine as c;
Vous pouvez alors faire
select*from date_range
obtenir
date
---2017-06-062017-06-052017-06-042017-06-032017-06-02...
Solution élégante utilisant la nouvelle fonctionnalité récursive (Common Table Expressions) dans MariaDB> = 10.3 et MySQL> = 8.0.
WITH RECURSIVE t as(select'2019-01-01'as dt
UNIONSELECT DATE_ADD(t.dt, INTERVAL 1 DAY)FROM t WHERE DATE_ADD(t.dt, INTERVAL 1 DAY)<='2019-04-30')select*FROM t;
Ce qui précède renvoie un tableau des dates entre '2019-01-01' et '2019-04-30'. C'est aussi décemment rapide. Le retour de 1000 ans de dates (~ 365 000 jours) prend environ 400 ms sur ma machine.
C'est une bonne idée de générer ces dates à la volée. Cependant, je ne me sens pas à l'aise pour faire cela avec une gamme assez large, donc j'ai fini avec la solution suivante:
Création d'une table "DatesNumbers" qui contiendra les nombres utilisés pour le calcul des dates:
CREATETABLE DatesNumbers (
i MEDIUMINT NOTNULL,PRIMARYKEY(i))
COMMENT='Used by Dates view';
Rempli le tableau en utilisant les techniques ci-dessus avec des nombres de -59999 à 40000. Cette plage me donnera des dates de 59999 jours (~ 164 ans) en arrière à 40000 jours (109 ans) à l'avance:
INSERTINTO DatesNumbers
SELECT
a.i +(10* b.i)+(100* c.i)+(1000* d.i)+(10000* e.i)-59999AS i
FROM(SELECT0AS i UNIONALLSELECT1UNIONALLSELECT2UNIONALLSELECT3UNIONALLSELECT4UNIONALLSELECT5UNIONALLSELECT6UNIONALLSELECT7UNIONALLSELECT8UNIONALLSELECT9)AS a,(SELECT0AS i UNIONALLSELECT1UNIONALLSELECT2UNIONALLSELECT3UNIONALLSELECT4UNIONALLSELECT5UNIONALLSELECT6UNIONALLSELECT7UNIONALLSELECT8UNIONALLSELECT9)AS b,(SELECT0AS i UNIONALLSELECT1UNIONALLSELECT2UNIONALLSELECT3UNIONALLSELECT4UNIONALLSELECT5UNIONALLSELECT6UNIONALLSELECT7UNIONALLSELECT8UNIONALLSELECT9)AS c,(SELECT0AS i UNIONALLSELECT1UNIONALLSELECT2UNIONALLSELECT3UNIONALLSELECT4UNIONALLSELECT5UNIONALLSELECT6UNIONALLSELECT7UNIONALLSELECT8UNIONALLSELECT9)AS d,(SELECT0AS i UNIONALLSELECT1UNIONALLSELECT2UNIONALLSELECT3UNIONALLSELECT4UNIONALLSELECT5UNIONALLSELECT6UNIONALLSELECT7UNIONALLSELECT8UNIONALLSELECT9)AS e
;
Création d'une vue "Dates":
SELECT
i,CURRENT_DATE()+ INTERVAL i DAY AS Date
FROM
DatesNumbers
C'est tout.
(+) Requêtes faciles à lire
(+) Pas de génération à la volée
(+) Donne des dates dans le passé et dans le futur et il n'y a AUCUNE UNION en vue pour cela comme dans ce post .
(+) Les dates "Dans le passé uniquement" ou "Dans le futur uniquement" peuvent être filtrées à l'aide de WHERE i < 0ouWHERE i > 0 (PK)
(-) la table et la vue 'temporaires' sont utilisées
Utilisez-le pour, par exemple, générer une table temporaire, puis effectuez une sélection * sur la table temporaire. Ou affichez les résultats un par un. Ce que vous dites vouloir faire ne peut pas être fait avec une instruction SELECT , mais cela peut être faisable avec des choses spécifiques à MySQL.
Là encore, peut-être avez-vous besoin de curseurs: http://dev.mysql.com/doc/refman/5.0/en/cursors.html
set language 'SPANISH'DECLARE@tabletable(fechaDesde datetime , fechaHasta datetime )INSERT@tableVALUES('20151231','20161231');WITH x AS(SELECT DATEADD( m ,1,fechaDesde )as fecha FROM@tableUNIONALLSELECT DATEADD( m ,1,fecha )FROM@table t INNERJOIN x ON DATEADD( m ,1,x.fecha )<= t.fechaHasta
)SELECTLEFT(CONVERT( VARCHAR, fecha ,112),6)as Periodo_Id
,DATEPART ( dd, DATEADD(dd,-(DAY(fecha)-1),fecha)) Num_Dia_Inicio
,DATEADD(dd,-(DAY(fecha)-1),fecha) Fecha_Inicio
,DATEPART ( mm , fecha ) Mes_Id
,DATEPART ( yy , fecha ) Anio
,DATEPART ( dd, DATEADD(dd,-(DAY(DATEADD(mm,1,fecha))),DATEADD(mm,1,fecha))) Num_Dia_Fin
,DATEADD(dd,-(DAY(DATEADD(mm,1,fecha))),DATEADD(mm,1,fecha)) ultimoDia
,datename(MONTH, fecha) mes
,'Q'+convert(varchar(10), DATEPART(QUARTER, fecha)) Trimestre_Name
FROM x
OPTION(MAXRECURSION 0)
Version SQLite de la meilleure solution RedFilters
select d.Date
from(select
date(julianday('2010-01-20')+(a.a +(10* b.a)+(100* c.a)))as Date
from(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as a
crossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as b
crossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as c
) d
where
d.Date between'2010-01-20'and'2010-01-24'orderby d.Date
WITH
Digits AS(SELECT0 D UNIONSELECT1UNIONSELECT2UNIONSELECT3UNIONSELECT4UNIONSELECT5UNIONSELECT6UNIONSELECT7UNIONSELECT8UNIONSELECT9),
Dates AS(SELECT adddate('1970-01-01',t4.d*10000+ t3.d*1000+ t2.d*100+ t1.d*10+t0.d)AS date FROM Digits AS t0, Digits AS t1, Digits AS t2, Digits AS t3, Digits AS t4)SELECT*FROM Dates WHERE date BETWEEN'2017-01-01'AND'2017-12-31'
Peut créer une procédure également pour créer une table de calendrier avec une chronologie différente du jour.
Si vous voulez une table pour chaque trimestre
Une réponse plus générique qui fonctionne dans AWS MySQL.
select datetable.Date
from(select date_format(adddate(now(),-(a.a +(10* b.a)+(100* c.a))),'%Y-%m-%d')AS Date
from(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as a
crossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as b
crossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as c
) datetable
where datetable.Date between now()- INTERVAL 14 Day and Now()orderby datetable.Date DESC
insert into table select ... as days date between '' and ''
Réponses:
Cette solution n'utilise aucune boucle, procédure ou table temporaire . La sous-requête génère des dates pour les 10 000 derniers jours et peut être étendue pour aller aussi loin que vous le souhaitez.
Production:
Remarques sur les performances
En le testant ici , les performances sont étonnamment bonnes: la requête ci-dessus prend 0,0009 sec.
Si nous étendons la sous-requête pour générer env. 100 000 numéros (et donc environ 274 ans de dates), il s'exécute en 0,0458 sec.
Incidemment, c'est une technique très portable qui fonctionne avec la plupart des bases de données avec des ajustements mineurs.
Exemple SQL Fiddle retournant 1000 jours
la source
UNION
àUNION ALL
- c'est perdre du temps à vérifier les doublons à supprimer qui n'existent pas. Cependant, l'OMI est trop compliquée - si vous allez construire un jeu de résultats à l'aide d'UNION, pourquoi ne pas simplement spécifier la date et en finir?Voici une autre variante utilisant des vues:
Et puis vous pouvez simplement faire (voir à quel point c'est élégant?):
Mettre à jour
Il est à noter que vous ne pourrez générer des dates passées qu'à partir de la date actuelle . Si vous souhaitez générer n'importe quel type de plage de dates (passé, futur et intermédiaire), vous devrez utiliser cette vue à la place:
la source
dates
mentionnée ci-dessus calcule les dates à partir de la date actuelle, c'est pourquoi vous ne pourrez pas récupérer les dates définies dans le futur. La réponse de @RedFilter souffre du même défaut de conception. J'ai ajouté une solution de contournement dans ma réponse.UNION
clauses semblent étranges dans une seule instruction SQL.La réponse acceptée ne fonctionnait pas pour PostgreSQL (erreur de syntaxe à ou près de "a").
La façon dont vous faites cela dans PostgreSQL est d'utiliser la
generate_series
fonction, c'est-à-dire:la source
À l'aide d'une expression de table commune (CTE) récursive, vous pouvez générer une liste de dates, puis en sélectionner. Évidemment, vous ne voudriez normalement pas créer trois millions de dates, donc cela illustre simplement les possibilités. Vous pouvez simplement limiter la plage de dates à l'intérieur du CTE et omettre la clause where de l'instruction select à l'aide du CTE.
Sur Microsoft SQL Server 2005, la génération de la liste CTE de toutes les dates possibles prenait 1:08. Générer cent ans a pris moins d'une seconde.
la source
Requête MSSQL
Production
la source
La solution à l'ancienne pour faire cela sans boucle / curseur est de créer une
NUMBERS
table, qui a une seule colonne Integer avec des valeurs commençant à 1.Vous devez remplir la table avec suffisamment d'enregistrements pour couvrir vos besoins:
Une fois que vous avez le
NUMBERS
tableau, vous pouvez utiliser:La solution low-tech absolue serait:
À quoi l'utiliseriez-vous?
Pour générer des listes de dates ou de nombres afin de LEFT JOIN sur. Vous le feriez pour voir où il y a des lacunes dans les données, parce que vous êtes GAUCHE JOINing sur une liste de données séquentielles - les valeurs nulles rendront évident les lacunes.
la source
DUAL
table est prise en charge par Oracle et MySQL pour être utilisée comme table de remplacement dans laFROM
clause. Il n'existe pas, en sélectionnant des valeurs, il retournera quelle que soit la valeur. L'idée était d'avoir le remplaçant car une requête SELECT nécessite uneFROM
clause spécifiant au moins une table.Pour Access 2010 - plusieurs étapes requises; J'ai suivi le même schéma que celui publié ci-dessus, mais j'ai pensé que je pourrais aider quelqu'un dans Access. Cela a très bien fonctionné pour moi, je n'ai pas eu à garder une table de dates prédéfinie.
Créez une table appelée DUAL (similaire au fonctionnement de la table Oracle DUAL)
Créez une requête nommée "ZeroThru9Q"; entrez manuellement la syntaxe suivante:
Créez une requête nommée "TodayMinus1KQ" (pour les dates antérieures à aujourd'hui); entrez manuellement la syntaxe suivante:
Créez une requête nommée "TodayPlus1KQ" (pour les dates postérieures à aujourd'hui); entrez manuellement la syntaxe suivante:
Créez une requête union nommée "TodayPlusMinus1KQ" (pour des dates +/- 1000 jours):
Vous pouvez maintenant utiliser la requête:
la source
Procédure + table temporaire:
la source
thx Pentium10 - vous m'avez fait rejoindre stackoverflow :) - c'est mon portage vers msaccess - je pense que cela fonctionnera sur n'importe quelle version:
référencé MSysObjects juste 'parce que l'accès a besoin d'une table comptant au moins 1 enregistrement, dans une clause from - n'importe quelle table avec au moins 1 enregistrement ferait l'affaire.
la source
Comme indiqué (ou du moins mentionné) dans de nombreuses réponses merveilleuses déjà données, ce problème est facilement résolu une fois que vous avez un ensemble de nombres avec lesquels travailler.
Remarque: Ce qui suit est T-SQL mais c'est simplement ma mise en œuvre particulière des concepts généraux déjà mentionnés ici et sur Internet en général. Il devrait être relativement simple de convertir le code dans le dialecte de votre choix.
Comment? Considérez cette requête:
Ce qui précède produit la plage de dates 1/22/0001 - 1/27/0001 et est extrêmement trivial. La requête ci-dessus contient 2 informations clés: la date de début
0001-01-22
et le décalage de5
. Si nous combinons ces deux informations, nous avons évidemment notre date de fin. Ainsi, étant donné deux dates, la génération d'une plage peut être décomposée comme suit:Trouvez la différence entre deux dates données (le décalage), facile:
-- Returns 125 SELECT ABS(DATEDIFF(d, '2014-08-22', '2014-12-25'))
Utiliser
ABS()
ici garantit que l'ordre des dates n'est pas pertinent.Générez un ensemble limité de nombres, également facile:
-- Returns the numbers 0-2 SELECT N = ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) - 1 FROM(SELECT 'A' AS S UNION ALL SELECT 'A' UNION ALL SELECT 'A')
Notez que nous ne nous soucions pas vraiment de ce que nous sélectionnons
FROM
ici. Nous avons juste besoin d'un ensemble avec lequel travailler afin de compter le nombre de lignes qu'il contient. J'utilise personnellement un TVF, certains utilisent un CTE, d'autres utilisent une table de nombres à la place, vous voyez l'idée. Je préconise d'utiliser la solution la plus performante que vous comprenez également.La combinaison de ces deux méthodes résoudra notre problème:
L'exemple ci-dessus est un code horrible mais montre comment tout se réunit.
Plus amusant
J'ai besoin de beaucoup faire ce genre de chose, alors j'ai encapsulé la logique dans deux TVF. Le premier génère une plage de nombres et le second utilise cette fonctionnalité pour générer une plage de dates. Le calcul est de s'assurer que l'ordre d'entrée n'a pas d'importance et parce que je voulais utiliser toute la gamme de nombres disponibles dans
GenerateRangeSmallInt
.La fonction suivante prend ~ 16 ms de temps CPU pour renvoyer la plage maximale de 65536 dates.
la source
essaye ça.
la source
Vous souhaitez obtenir une plage de dates.
Dans votre exemple, vous souhaitez obtenir les dates comprises entre "2010-01-20" et "2010-01-24"
solution possible:
Explication
MySQL a une fonction date_add donc
te donnera
La fonction dateiff vous permet de savoir souvent que vous devez répéter cela
qui retourne
Obtenir une liste de dates dans une plage de dates revient à créer une séquence de nombres entiers voir générer une séquence d'entiers dans MySQL
La réponse la plus votée ici a adopté une approche similaire à celle de https://stackoverflow.com/a/2652051/1497139 comme base:
ce qui se traduira par
Les lignes peuvent maintenant être utilisées pour créer une liste de dates à partir de la date de début donnée. Pour inclure la date de début, nous commençons par la ligne -1;
la source
si jamais vous avez besoin de plus de quelques jours, vous avez besoin d'une table.
Créer une plage de dates dans mysql
puis,
la source
Générer des dates entre deux champs de date
Si vous connaissez la requête SQL CTE, cette solution vous aidera à résoudre votre question
Voici un exemple
Nous avons des dates dans une table
Nom de la table: "testdate"
Exiger un résultat:
Solution:
Explication: Explication de la requête récursive CTE
Première partie de la requête:
SELECT DISTINCT convert(varchar(10), StartTime, 101) AS StartTime, datediff(dd, StartTime, endTime) AS diff FROM dbo.testdate
Explication: la première colonne correspond à "date de début", la deuxième colonne correspond à la différence entre les dates de début et de fin en jours et sera considérée comme une colonne "diff"
Deuxième partie de la requête:
UNION ALL SELECT StartTime, diff-1 AS diff FROM CTE WHERE diff<>0
Explication: Union all héritera du résultat de la requête ci-dessus jusqu'à ce que le résultat devienne nul, donc le résultat «StartTime» est hérité de la requête CTE générée, et de diff, diminue - 1, donc cela ressemble à 3, 2 et 1 jusqu'à 0
Par exemple
Spécification du résultat
3e partie de la requête
SELECT DISTINCT DateAdd(dd,diff, StartTime) AS StartTime FROM CTE
Il ajoutera le jour «diff» dans «startdate» donc le résultat devrait être comme ci-dessous
Résultat
la source
Réponse plus courte que acceptée, même idée:
la source
Pour tous ceux qui le souhaitent en tant que vue enregistrée (MySQL ne prend pas en charge les instructions de sélection imbriquées dans les vues):
Vous pouvez alors faire
obtenir
la source
Solution élégante utilisant la nouvelle fonctionnalité récursive (Common Table Expressions) dans MariaDB> = 10.3 et MySQL> = 8.0.
Ce qui précède renvoie un tableau des dates entre '2019-01-01' et '2019-04-30'. C'est aussi décemment rapide. Le retour de 1000 ans de dates (~ 365 000 jours) prend environ 400 ms sur ma machine.
la source
C'est une bonne idée de générer ces dates à la volée. Cependant, je ne me sens pas à l'aise pour faire cela avec une gamme assez large, donc j'ai fini avec la solution suivante:
C'est tout.
WHERE i < 0
ouWHERE i > 0
(PK)la source
Très bien. Essayez ceci: http://www.devshed.com/c/a/MySQL/Delving-Deeper-into-MySQL-50/
http://dev.mysql.com/doc/refman/5.0/en/ loop-statement.html
http://www.roseindia.net/sql/mysql-example/mysql-loop.shtml
Utilisez-le pour, par exemple, générer une table temporaire, puis effectuez une sélection * sur la table temporaire. Ou affichez les résultats un par un.
Ce que vous dites vouloir faire ne peut pas être fait avec une instruction SELECT , mais cela peut être faisable avec des choses spécifiques à MySQL.
Là encore, peut-être avez-vous besoin de curseurs: http://dev.mysql.com/doc/refman/5.0/en/cursors.html
la source
Pour Oracle, ma solution est:
Sysdate peut être changé en date spécifique et le numéro de niveau peut être changé pour donner plus de dates.
la source
si vous voulez la liste des dates entre deux dates:
* violon ici: http://sqlfiddle.com/#!6/9eecb/3469
la source
la source
la source
Version SQLite de la meilleure solution RedFilters
la source
amélioré avec le jour de la semaine et rejoindre une table de vacances personnalisée microsoft MSSQL 2012 pour la table de date powerpivot https://gist.github.com/josy1024/cb1487d66d9e0ccbd420bc4a23b6e90e
la source
la source
Peut créer une procédure également pour créer une table de calendrier avec une chronologie différente du jour. Si vous voulez une table pour chaque trimestre
par exemple
vous pouvez utiliser
puis manipuler à travers
qui te donnent aussi ts
à partir de là, vous pouvez commencer à ajouter d'autres informations telles que
ou créez une vraie table avec l' instruction create table
la source
Une réponse plus générique qui fonctionne dans AWS MySQL.
la source
Une autre solution pour mysql 8.0.1 et mariadb 10.2.2 utilisant des expressions de table communes récursives:
la source