générer des jours à partir de la plage de dates

135

Je souhaite lancer une requête du type

select ... as days where `date` is between '2010-01-20' and '2010-01-24'

Et renvoyez des données comme:

journées
----------
20/01/2010
21/01/2010
22/01/2010
23/01/2010
24/01/2010
Pentium10
la source
10
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 (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' 

Production:

Date
----------
2010-01-24
2010-01-23
2010-01-22
2010-01-21
2010-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.

Exemple SQL Fiddle retournant 1000 jours

RedFilter
la source
6
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:

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 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'
ORDER BY
  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:

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;
Stéphane
la source
1
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-20
 2010-01-21
 2010-01-22
 2010-01-23
 2010-01-24
(5 rows)
Dmitry Gusev
la source
14

À 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.

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 prenait 1:08. Générer cent ans a pris moins d'une seconde.

Joshua
la source
7

Requête MSSQL

select datetable.Date 
from (
    select DATEADD(day,-(a.a + (10 * b.a) + (100 * c.a)),getdate()) 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
) datetable
where datetable.Date between '2014-01-20' and '2014-01-24' 
order by datetable.Date DESC

Production

Date
-----
2014-01-23 12:35:25.250
2014-01-22 12:35:25.250
2014-01-21 12:35:25.250
2014-01-20 12:35:25.250
SUHAIL AG
la source
2
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.

CREATE TABLE  `example`.`numbers` (
  `id` int(10) unsigned NOT NULL auto_increment,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Vous devez remplir la table avec suffisamment d'enregistrements pour couvrir vos besoins:

INSERT INTO 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'

La solution low-tech absolue 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

À 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.

Poneys OMG
la source
1
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:

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 union nommée "TodayPlusMinus1KQ" (pour des dates +/- 1000 jours):

SELECT MyDate
FROM TodayMinus1KQ
UNION
SELECT MyDate
FROM TodayPlus1KQ;

Vous pouvez maintenant utiliser la requête:

SELECT MyDate
FROM TodayPlusMinus1KQ
WHERE MyDate BETWEEN #05/01/2014# and #05/30/2014#
Travis
la source
3

Procédure + table temporaire:

DELIMITER $$

CREATE DEFINER=`root`@`localhost` PROCEDURE `days`(IN dateStart DATE, IN dateEnd DATE)
BEGIN

    CREATE TEMPORARY TABLE IF NOT EXISTS date_range (day DATE);

    WHILE dateStart <= dateEnd DO
      INSERT INTO date_range VALUES (dateStart);
      SET dateStart = DATE_ADD(dateStart, INTERVAL 1 DAY);
    END WHILE;

    SELECT * FROM date_range;
    DROP TEMPORARY TABLE IF EXISTS date_range;

END
Márcio Souza Júnior
la source
3

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 
    (
    select top 1 "0" as espr1 from MSysObjects
    union all
    select top 1 "1" as espr2 from MSysObjects
    union all
    select top 1 "2" as espr3 from MSysObjects
    union all
    select top 1 "3" as espr4 from MSysObjects
    union all
    select top 1 "4" as espr5 from MSysObjects
    union all
    select top 1 "5" as espr6 from MSysObjects
    union all
    select top 1 "6" as espr7 from MSysObjects
    union all
    select top 1 "7" as espr8 from MSysObjects
    union all
    select top 1 "8" as espr9 from MSysObjects
    union all
    select top 1 "9" as espr9 from MSysObjects
    ) as a,
    (
    select top 1 "0" as espr1 from MSysObjects
    union all
    select top 1 "1" as espr2 from MSysObjects
    union all
    select top 1 "2" as espr3 from MSysObjects
    union all
    select top 1 "3" as espr4 from MSysObjects
    union all
    select top 1 "4" as espr5 from MSysObjects
    union all
    select top 1 "5" as espr6 from MSysObjects
    union all
    select top 1 "6" as espr7 from MSysObjects
    union all
    select top 1 "7" as espr8 from MSysObjects
    union all
    select top 1 "8" as espr9 from MSysObjects
    union all
    select top 1 "9" as espr9 from MSysObjects
    ) as b,
    (
    select top 1 "0" as espr1 from MSysObjects
    union all
    select top 1 "1" as espr2 from MSysObjects
    union all
    select top 1 "2" as espr3 from MSysObjects
    union all
    select top 1 "3" as espr4 from MSysObjects
    union all
    select top 1 "4" as espr5 from MSysObjects
    union all
    select top 1 "5" as espr6 from MSysObjects
    union all
    select top 1 "6" as espr7 from MSysObjects
    union all
    select top 1 "7" as espr8 from MSysObjects
    union all
    select top 1 "8" as espr9 from MSysObjects
    union all
    select top 1 "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.

utilisateur3780177
la source
2

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 N
WHERE 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:

    -- 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 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(ORDER BY (SELECT NULL)) - 1
    FROM (SELECT 'A' AS S UNION ALL SELECT 'A' UNION ALL SELECT '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.

CREATE FUNCTION dbo.GenerateRangeDate (   
    @date1 DATE,   
    @date2 DATE   
)   
RETURNS TABLE
WITH SCHEMABINDING   
AS   
RETURN (
    SELECT D = DATEADD(d, N + 32768, CASE WHEN @date1 <= @date2 THEN @date1 ELSE @date2 END)
    FROM dbo.GenerateRangeSmallInt(-32768, ABS(DATEDIFF(d, @date1, @date2)) - 32768)
);

GO

CREATE FUNCTION dbo.GenerateRangeSmallInt (
    @num1 SMALLINT = -32768
  , @num2 SMALLINT = 32767
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN (
    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)
    )
    SELECT TOP(ABS(CAST(@num1 AS INT) - CAST(@num2 AS INT)) + 1)
           N = ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) + CASE WHEN @num1 <= @num2 THEN @num1 ELSE @num2 END - 1
    FROM Numbers A
       , Numbers B
);
Kittoes0124
la source
2

essaye ça.

SELECT TO_DATE('20160210','yyyymmdd') - 1 + LEVEL AS start_day 
from DUAL
connect by level <= (TO_DATE('20160228','yyyymmdd') + 1) - TO_DATE('20160210','yyyymmdd') ;
loalexzzzz
la source
2

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:

 select date_add('2010-01-20', interval row day) from
 ( 
    SELECT @row := @row + 1 as row FROM 
    (select 0 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 6 union all select 7 union all select 8 union all select 9) t,
    (select 0 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 6 union all select 7 union all select 8 union all select 9) t2, 
    (select 0 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 6 union all select 7 union all select 8 union all select 9) t3, 
    (select 0 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 6 union all select 7 union all select 8 union all select 9) t4, 
    (SELECT @row:=-1) r
 ) sequence
 where date_add('2010-01-20', interval row day) <= '2010-01-24'

Explication

MySQL a une fonction date_add donc

select date_add('2010-01-20', interval 1 day)

te donnera

2010-01-21

La fonction dateiff vous permet de savoir souvent que vous devez répéter cela

select datediff('2010-01-24', '2010-01-20')

qui retourne

 4

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:

SELECT @row := @row + 1 as row FROM 
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t,
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t2, 
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t3, 
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t4, 
(SELECT @row:=0) r
limit 4

ce qui se traduira par

row
1.0
2.0
3.0
4.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 + 1 as row FROM 
    (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t,
    (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t2, 
    (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t3, 
    (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t4, 
    (SELECT @row:=-1) r
 ) sequence
 where date_add('2010-01-20', interval row day) <= '2010-01-24'
Wolfgang Fahl
la source
1

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,

select from days.day, count(mytable.field) as fields from days left join mytable on day=date where date between x and y;
gcb
la source
3
pourquoi avez-vous posté cela, puisque la réponse ci-dessus n'a pas besoin d'un tableau et fournit la solution?
Pentium10 du
1

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"

STARTDATE   ENDDATE
10/24/2012  10/24/2012
10/27/2012  10/29/2012
10/30/2012  10/30/2012

Exiger un résultat:

STARTDATE
10/24/2012
10/27/2012
10/28/2012
10/29/2012
10/30/2012

Solution:

WITH CTE AS
  (SELECT DISTINCT convert(varchar(10),StartTime, 101) AS StartTime,
                   datediff(dd,StartTime, endTime) AS diff
   FROM dbo.testdate
   UNION ALL SELECT StartTime,
                    diff - 1 AS diff
   FROM CTE
   WHERE diff<> 0)
SELECT DISTINCT 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

Par exemple

STARTDATE   DIFF
10/24/2012  0
10/27/2012  0
10/27/2012  1
10/27/2012  2
10/30/2012  0

Spécification du résultat

STARTDATE       Specification
10/24/2012  --> From Record 1
10/27/2012  --> From Record 2
10/27/2012  --> From Record 2
10/27/2012  --> From Record 2
10/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

Résultat

STARTDATE
10/24/2012
10/27/2012
10/28/2012
10/29/2012
10/30/2012
Tarun Harkinia
la source
1

Réponse plus courte que acceptée, même idée:

(SELECT TRIM('2016-01-05' + INTERVAL a + b DAY) date
FROM
(SELECT 0 a UNION SELECT 1 a UNION SELECT 2 UNION SELECT 3
UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7
UNION SELECT 8 UNION SELECT 9 ) d,
(SELECT 0 b UNION SELECT 10 UNION SELECT 20
UNION SELECT 30 UNION SELECT 40) m
WHERE '2016-01-05' + INTERVAL a + b DAY  <=  '2016-01-21')
Daniherculano
la source
1

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):

create view zero_to_nine as
    select 0 as n 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 date_range as
    select curdate() - INTERVAL (a.n + (10 * b.n) + (100 * c.n)) DAY as date
    from zero_to_nine as a
    cross join zero_to_nine as b
    cross join zero_to_nine as c;

Vous pouvez alors faire

select * from date_range

obtenir

date
---
2017-06-06
2017-06-05
2017-06-04
2017-06-03
2017-06-02
...
Tom G
la source
1

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
  UNION
    SELECT 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.

Brad
la source
1

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:

  1. Création d'une table "DatesNumbers" qui contiendra les nombres utilisés pour le calcul des dates:
CREATE TABLE DatesNumbers (
    i MEDIUMINT NOT NULL,
    PRIMARY KEY (i)
)
COMMENT='Used by Dates view'
;
  1. 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:
INSERT INTO DatesNumbers
SELECT 
    a.i + (10 * b.i) + (100 * c.i) + (1000 * d.i) + (10000 * e.i) - 59999 AS i
FROM 
  (SELECT 0 AS i 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,
  (SELECT 0 AS i 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,
  (SELECT 0 AS i 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,
  (SELECT 0 AS i 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,
  (SELECT 0 AS i 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 e
;
  1. 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
fifonik
la source
0

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

Trevoke
la source
0

Pour Oracle, ma solution est:

select trunc(sysdate-dayincrement, 'DD') 
  from dual, (select level as dayincrement 
                from dual connect by level <= 30)

Sysdate peut être changé en date spécifique et le numéro de niveau peut être changé pour donner plus de dates.

mousetwentytwo
la source
0

si vous voulez la liste des dates entre deux dates:

create table #dates ([date] smalldatetime)
while @since < @to
begin
     insert into #dates(dateadd(day,1,@since))
     set @since = dateadd(day,1,@since)
end
select [date] from #dates

* violon ici: http://sqlfiddle.com/#!6/9eecb/3469

celerno
la source
0
set language  'SPANISH'
DECLARE @table table(fechaDesde datetime , fechaHasta datetime ) 
INSERT @table VALUES('20151231' , '20161231');
WITH x AS 
    (
        SELECT   DATEADD( m , 1 ,fechaDesde ) as fecha  FROM @table
        UNION ALL
        SELECT  DATEADD( m , 1 ,fecha )
        FROM @table t INNER JOIN x ON  DATEADD( m , 1 ,x.fecha ) <= t.fechaHasta
    )
SELECT LEFT( 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)
Benigno Geronimo
la source
0
DELIMITER $$
CREATE PROCEDURE GenerateRangeDates(IN dateStart DATE, IN dateEnd DATE)
BEGIN

    CREATE TEMPORARY TABLE IF NOT EXISTS dates (day DATE);

    loopDate: LOOP
        INSERT INTO dates(day) VALUES (dateStart); 
        SET dateStart = DATE_ADD(dateStart, INTERVAL 1 DAY);

        IF dateStart <= dateEnd 
            THEN ITERATE loopDate;
            ELSE LEAVE loopDate;
        END IF;
    END LOOP loopDate;

    SELECT day FROM dates;
    DROP TEMPORARY TABLE IF EXISTS dates;

END 
$$

-- Call procedure
call GenerateRangeDates( 
        now() - INTERVAL 40 DAY,
        now()
    );
Victor Souza da Silva
la source
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 (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
) d
where 
d.Date between '2010-01-20' and '2010-01-24' 
order by d.Date
Martin
la source
0

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

with [dates] as (
    select convert(datetime, '2016-01-01') as [date] --start
    union all
    select dateadd(day, 1, [date])
    from [dates]
    where [date] < '2018-01-01' --end
)
select [date]
, DATEPART (dw,[date]) as Wochentag
, (select holidayname from holidaytable 
where holidaytable.hdate = [date]) 
as Feiertag
from [dates]
where [date] between '2016-01-01' and '2016-31-12'
option (maxrecursion 0)
josy1024
la source
0
WITH
  Digits AS (SELECT 0 D 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),
  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'
Norbert Madarász
la source
0

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

2019-01-22 08:45:00
2019-01-22 09:00:00
2019-01-22 09:15:00
2019-01-22 09:30:00
2019-01-22 09:45:00
2019-01-22 10:00:00

vous pouvez utiliser

CREATE DEFINER=`root`@`localhost` PROCEDURE `generate_calendar_table`()
BEGIN

select unix_timestamp('2014-01-01 00:00:00') into @startts;
select unix_timestamp('2025-01-01 00:00:00') into @endts;

if ( @startts < @endts ) then

    DROP TEMPORARY TABLE IF EXISTS calendar_table_tmp;

    CREATE TEMPORARY TABLE calendar_table_tmp (ts int, dt datetime); 

    WHILE ( @startts < @endts)
        DO 
        SET @startts = @startts + 900;
        INSERT calendar_table_tmp VALUES (@startts, from_unixtime(@startts));
    END WHILE;

END if;

END

puis manipuler à travers

select ts, dt from calendar_table_tmp;

qui te donnent aussi ts

'1548143100', '2019-01-22 08:45:00'
'1548144000', '2019-01-22 09:00:00'
'1548144900', '2019-01-22 09:15:00'
'1548145800', '2019-01-22 09:30:00'
'1548146700', '2019-01-22 09:45:00'
'1548147600', '2019-01-22 10:00:00'

à partir de là, vous pouvez commencer à ajouter d'autres informations telles que

select ts, dt, weekday(dt) as wd from calendar_table_tmp;

ou créez une vraie table avec l' instruction create table

Gianluca D'Ardia
la source
0

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 (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
) datetable
where datetable.Date between now() - INTERVAL 14 Day and Now()
order by datetable.Date DESC
Eric
la source
-1

Une autre solution pour mysql 8.0.1 et mariadb 10.2.2 utilisant des expressions de table communes récursives:

with recursive dates as (
    select '2010-01-20' as date
    union all
    select date + interval 1 day from dates where date < '2010-01-24'
)
select * from dates;
Слаффка Островский
la source