Je voudrais exécuter une requête comme
select ... as days where `date` is between '2010-01-20' and '2010-01-24'
Et retourner des données comme :
days
----------
2010-01-20
2010-01-21
2010-01-22
2010-01-23
2010-01-24
Je voudrais exécuter une requête comme
select ... as days where `date` is between '2010-01-20' and '2010-01-24'
Et retourner des données comme :
days
----------
2010-01-20
2010-01-21
2010-01-22
2010-01-23
2010-01-24
Cette solution utilise pas de boucles, de procédures ou de tables temporaires . La sous-requête génère des dates pour les 10 000 derniers jours, et peut être étendue pour remonter ou avancer 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 (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as a
cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as b
cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as c
cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as d
) a
where a.Date between '2010-01-20' and '2010-01-24'
Sortie :
Date
----------
2010-01-24
2010-01-23
2010-01-22
2010-01-21
2010-01-20
Notes sur la performance
L'essai aquí la performance est étonnamment bonne : la requête ci-dessus prend 0.0009 sec.
Si nous étendons la sous-requête pour générer environ 100 000 numéros (et donc environ 274 années de dates), elle s'exécute en 0,0458 seconde.
Soit dit en passant, il s'agit d'une technique très portable qui fonctionne avec la plupart des bases de données moyennant quelques ajustements mineurs.
Vous obtiendrez de meilleures performances si vous changez UNION
a UNION ALL
- c'est perdre du temps à chercher des doublons à supprimer qui n'existent pas. Mais c'est trop compliqué - si vous devez construire un jeu de résultats en utilisant des UNIONs, pourquoi ne pas simplement spécifier la date et en finir avec elle ?
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 de nombres (et de dates) de taille arbitraire ne nécessitant aucune création de table, ce qui 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 effectuez une jointure avec une table dont vous ne connaissez pas les dates à l'avance, mais seulement les valeurs min et max potentielles, cela a du sens.
Il est "douloureux" d'utiliser la fonction DATETIME à la place de l'instruction UNION que vous avez déjà créée ? C'est supprime tout besoin de la logique que vous avez dû ajouter. . Donc - vous avez surcompliqué la requête. L'instruction UNION, quelle que soit la façon dont elle est utilisée, n'est pas extensible - en spécifiant une date ou un nombre, qui veut le mettre à jour pour tenir compte de 20 ou 30 dates ?
Voici une autre variante utilisant des vues :
CREATE VIEW digits AS
SELECT 0 AS digit UNION ALL
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4 UNION ALL
SELECT 5 UNION ALL
SELECT 6 UNION ALL
SELECT 7 UNION ALL
SELECT 8 UNION ALL
SELECT 9;
CREATE VIEW numbers AS
SELECT
ones.digit + tens.digit * 10 + hundreds.digit * 100 + thousands.digit * 1000 AS number
FROM
digits as ones,
digits as tens,
digits as hundreds,
digits as thousands;
CREATE VIEW dates AS
SELECT
SUBDATE(CURRENT_DATE(), number) AS date
FROM
numbers;
Et ensuite, vous pouvez simplement faire (vous voyez comme c'est élégant ?) :
SELECT
date
FROM
dates
WHERE
date BETWEEN '2010-01-20' AND '2010-01-24'
ORDER BY
date
Mise à jour
Il convient de noter que vous ne pourrez générer que les dates passées à partir de la date actuelle . Si vous voulez générer n'importe quel type de plage de dates (passé, futur, et entre les deux), vous devrez utiliser cette vue à la place :
CREATE VIEW dates AS
SELECT
SUBDATE(CURRENT_DATE(), number) AS date
FROM
numbers
UNION ALL
SELECT
ADDDATE(CURRENT_DATE(), number + 1) AS date
FROM
numbers;
Cela ne fonctionne pas dans tous les cas. SELECT date FROM dates WHERE date BETWEEN '2014-12-01' AND '2014-12-28' ORDER BY date
Bon appel @user927258. C'est parce que la première vue dates
mentionnée ci-dessus calcule les dates à partir de la date actuelle, c'est pourquoi vous ne pourrez pas récupérer des dates fixées dans le futur. La réponse de @RedFilter souffre du même défaut de conception. J'ai cependant ajouté une solution de contournement dans ma réponse.
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 vues UNION
semblent bizarres dans une seule instruction SQL.
À l'aide d'une expression de table commune (CTE) récursive, vous pouvez générer une liste de dates, puis effectuer une sélection dans cette liste. Il est évident que vous ne voudriez pas créer trois millions de dates, mais ceci n'est qu'une illustration des possibilités. Vous pourriez simplement limiter la plage de dates dans l'expression de table commune et omettre la clause where de l'instruction de sélection utilisant l'expression de table commune.
with [dates] as (
select convert(datetime, '1753-01-01') as [date] --start
union all
select dateadd(day, 1, [date])
from [dates]
where [date] < '9999-12-31' --end
)
select [date]
from [dates]
where [date] between '2013-01-01' and '2013-12-31'
option (maxrecursion 0)
Sur Microsoft SQL Server 2005, la génération de la liste CTE de toutes les dates possibles a pris 1:08. La génération de cent années a pris moins d'une seconde.
La solution traditionnelle pour réaliser cette opération sans boucle/curseur consiste à créer un fichier de type NUMBERS
qui comporte une seule colonne "Integer" dont les valeurs commencent à 1.
CREATE TABLE `example`.`numbers` (
`id` int(10) unsigned NOT NULL auto_increment,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Vous devez alimenter la table avec suffisamment d'enregistrements pour couvrir vos besoins :
INSERT INTO NUMBERS (id) VALUES (NULL);
Une fois que vous avez le NUMBERS
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'
La solution la plus simple serait :
SELECT STR_TO_DATE('2010-01-20', '%Y-%m-%d')
FROM DUAL
UNION ALL
SELECT STR_TO_DATE('2010-01-21', '%Y-%m-%d')
FROM DUAL
UNION ALL
SELECT STR_TO_DATE('2010-01-22', '%Y-%m-%d')
FROM DUAL
UNION ALL
SELECT STR_TO_DATE('2010-01-23', '%Y-%m-%d')
FROM DUAL
UNION ALL
SELECT STR_TO_DATE('2010-01-24', '%Y-%m-%d')
FROM DUAL
Pour générer des listes de dates ou de nombres afin de les joindre à gauche (LEFT JOIN). Vous pouvez le faire pour voir où il y a des lacunes dans les données, car vous effectuez un LEFT JOIN sur une liste de données séquentielles - les valeurs nulles rendent évidentes les lacunes.
El DUAL
est prise en charge par Oracle et MySQL pour être utilisée comme table de remplacement dans la base de données de l'utilisateur. FROM
clause. Elle n'existe pas, la sélection de valeurs à partir de cette clause renverra la valeur choisie. L'idée était d'avoir un substitut parce qu'une requête SELECT nécessite une clause FROM
spécifiant au moins une table.
Pour Access 2010 - plusieurs étapes nécessaires ; j'ai suivi le même schéma que celui posté ci-dessus, mais j'ai pensé que je pourrais aider quelqu'un en Access. Cela a très bien fonctionné pour moi, je n'ai pas eu besoin de conserver un tableau de dates ensemencé.
Créez une table appelée DUAL (similaire au fonctionnement de la table DUAL d'Oracle).
Créez une requête nommée "ZeroThru9Q" ; entrez manuellement la syntaxe suivante :
SELECT 0 AS a
FROM dual
UNION ALL
SELECT 1
FROM dual
UNION ALL
SELECT 2
FROM dual
UNION ALL
SELECT 3
FROM dual
UNION ALL
SELECT 4
FROM dual
UNION ALL
SELECT 5
FROM dual
UNION ALL
SELECT 6
FROM dual
UNION ALL
SELECT 7
FROM dual
UNION ALL
SELECT 8
FROM dual
UNION ALL
SELECT 9
FROM dual;
Créez une requête nommée "TodayMinus1KQ" (pour les dates antérieures à aujourd'hui) ; entrez manuellement la syntaxe suivante :
SELECT date() - (a.a + (10 * b.a) + (100 * c.a)) AS MyDate
FROM
(SELECT *
FROM ZeroThru9Q) AS a,
(SELECT *
FROM ZeroThru9Q) AS b,
(SELECT *
FROM ZeroThru9Q) AS c
Créez une requête nommée "TodayPlus1KQ" (pour les dates postérieures à aujourd'hui) ; entrez manuellement la syntaxe suivante :
SELECT date() + (a.a + (10 * b.a) + (100 * c.a)) AS MyDate
FROM
(SELECT *
FROM ZeroThru9Q) AS a,
(SELECT *
FROM ZeroThru9Q) AS b,
(SELECT *
FROM ZeroThru9Q) AS c;
Créez une requête d'union nommée "TodayPlusMinus1KQ" (pour les dates de +/- 1000 jours) :
SELECT MyDate
FROM TodayMinus1KQ
UNION
SELECT MyDate
FROM TodayPlus1KQ;
Maintenant vous pouvez utiliser la requête :
SELECT MyDate
FROM TodayPlusMinus1KQ
WHERE MyDate BETWEEN #05/01/2014# and #05/30/2014#
Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.
11 votes
Il n'y a pas d'autre problème lié à cette question. La question ci-dessus est le problème, les cours de maîtrise de SQL.
0 votes
Avez-vous simplement besoin d'un tableau de dates basé sur une plage de dates sélectionnée ?
1 votes
Je pense à une utilisation, pour vous trouver un problème... Si vous avez la tâche de remplir des enregistrements manquants dans votre table. Et vous devez exécuter une requête pour chaque jour, je pense à quelque chose comme
insert into table select ... as days date between '' and ''
16 votes
Un exemple de son utilisation serait de générer des statistiques, et d'inclure une ligne pour les dates pour lesquelles vous n'avez pas de données. Si vous effectuez une sorte de regroupement, il peut être beaucoup plus rapide de générer réellement toutes les informations en SQL et de les ajouter dans le format dont vous avez besoin, au lieu de transférer vos données telles quelles dans votre langage, et de commencer à boucler et à ajouter vos vides.
1 votes
@Nanne c'est précisément la raison pour laquelle j'ai enregistré cette question. J'ai besoin de ce qui précède pour effectuer un LEFT JOIN dans des données qui peuvent ne pas exister pour certaines dates.