generare giorni dall'intervallo di date


135

Vorrei eseguire una query come

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

E restituisci dati come:

giorni
----------
2010-01-20
2010-01-21
2010-01-22
2010-01-23
2010-01-24

10
Non ci sono altri problemi collegati a questa domanda. La domanda sopra è il problema, padroneggiare i corsi SQL.
Pentium10,

Hai solo bisogno di una serie di date basate su un intervallo di date selezionato?
Derek Adair,

1
Sto pensando a un utilizzo, a trovarti un problema ... Se ti viene richiesto di compilare alcuni record mancanti nella tabella. E devi eseguire una query per ogni giorno che sto pensando a qualcosa del genereinsert into table select ... as days date between '' and ''
Pentium10,

13
Un esempio per il suo utilizzo potrebbe essere quello di generare statistiche e includere una riga per le date in cui non si dispone di dati. Se stai facendo una sorta di raggruppamento, può essere molto più veloce generare effettivamente tutte le informazioni in SQL e aggiungerle nel formato che preferisci, invece di scaricare i dati così come sono nella tua lingua e iniziare a eseguire il ciclo e aggiungere il tuo svuota.
Nanne,

1
@Nanne questo è esattamente il motivo per cui ho salvato questa domanda. Ho bisogno di quanto sopra per SINISTRA UNISCITI a dati che potrebbero non esistere per determinate date.
Josh Diehl

Risposte:


318

Questa soluzione non utilizza loop, procedure o tabelle temporanee . La sottoquery genera date per gli ultimi 10.000 giorni e potrebbe essere estesa per andare avanti o indietro come desideri.

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' 

Produzione:

Date
----------
2010-01-24
2010-01-23
2010-01-22
2010-01-21
2010-01-20

Note sulle prestazioni

Provandolo qui , le prestazioni sono sorprendentemente buone: la query sopra richiede 0,0009 sec.

Se estendiamo la sottoquery per generare ca. 100.000 numeri (e quindi circa 274 anni di date), corre in 0,0458 sec.

Per inciso, questa è una tecnica molto portatile che funziona con la maggior parte dei database con piccole modifiche.

Esempio di SQL Fiddle che restituisce 1.000 giorni


6
Vedrai prestazioni migliori se cambi UNIONa UNION ALL: perdere tempo a controllare i duplicati da rimuovere che non esistono. È IMO troppo complicato - se hai intenzione di costruire un set di risultati utilizzando UNION, perché non specificare semplicemente la data e finirla?
OMG Pony il

7
perché non specificare semplicemente la data e finirla con essa - perché il metodo sopra ti permette di creare insiemi di numeri (e date) arbitrariamente grandi che non richiedono la creazione di tabelle, che sarebbe doloroso codificare nel modo che stai suggerendo. Ovviamente per 5 date è eccessivo; ma anche in questo caso, se ti unisci a una tabella in cui non conosci le date in anticipo, ma solo i potenziali valori min e max, ha senso.
RedFilter,

2
È "doloroso" usare semplicemente la funzione DATETIME al posto dell'istruzione UNION che hai già creato? E ' allevia la necessità per la logica si doveva aggiungere . Quindi - hai complicato la query. La dichiarazione UNION, in entrambi i casi, non è scalabile - specificando una data o un numero, chi vuole aggiornarlo per ospitare 20 o 30 date?
OMG Pony il

23
È davvero bello vedere una risposta alla domanda, non infiniti commenti su come non possa o non debba essere fatto. Molte cose possono essere fatte e "dovrebbe" è significativo solo nel contesto, che differisce per tutti. Questa risposta mi ha aiutato, anche se sono consapevole che ci sono modi migliori nella maggior parte delle situazioni.
joe

7
Quelli di voi che non riescono a far funzionare questa query: si prega di dare uno schiaffo in faccia e quindi rileggere il commento del PO su questa query generando 1000 date. Dal 2010, oltre 1000 giorni fa, dovrai adattare la query di conseguenza.
Noel Baron,

32

Ecco un'altra variante che utilizza le viste:

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;

E poi puoi semplicemente fare (vedi quanto è elegante?):

SELECT
  date
FROM
  dates
WHERE
  date BETWEEN '2010-01-20' AND '2010-01-24'
ORDER BY
  date

Aggiornare

Vale la pena notare che sarai in grado di generare solo date passate a partire dalla data corrente . Se desideri generare qualsiasi tipo di intervallo di date (passato, futuro e intermedio), dovrai invece utilizzare questa vista:

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;

1
Questo non funziona in tutti i casi. SELEZIONA data DA date DOVE data TRA '2014-12-01' E '2014-12-28' ORDINA PER data
vasanth

3
Buona chiamata @ user927258. Questo perché la prima vista datessopra menzionata calcola le date a partire dalla data corrente, motivo per cui non sarai in grado di recuperare le date impostate in futuro. La risposta di @RedFilter soffre dello stesso difetto di progettazione. Ho aggiunto una soluzione alternativa nella mia risposta però.
Stéphane,

L'uso di alcune viste semplifica decisamente le query e le rende riutilizzabili. Sebbene sostanzialmente stiano facendo la stessa cosa, tutte quelle UNIONclausole sembrano strane in una singola istruzione SQL.
Stewart,

24

La risposta accettata non ha funzionato per PostgreSQL (errore di sintassi vicino o "a").

Il modo in cui lo fai in PostgreSQL è usando la generate_seriesfunzione, cioè:

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)

14

Utilizzando un'espressione di tabella comune (CTE) ricorsiva, è possibile generare un elenco di date, quindi selezionare da esso. Ovviamente normalmente non vorrai creare tre milioni di date, quindi questo illustra solo le possibilità. È possibile semplicemente limitare l'intervallo di date all'interno del CTE e omettere la clausola where dall'istruzione select utilizzando il 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)

Su Microsoft SQL Server 2005, la generazione dell'elenco CTE di tutte le date possibili ha richiesto 1:08. La generazione di cento anni ha richiesto meno di un secondo.


7

Query 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

Produzione

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

2
Se solo avessi fatto scorrere un po 'più in basso ... sospiro. Comunque grazie. Ho aggiunto un CAST (<espressione> COME DATA) per rimuovere l'ora sulla mia versione. Utilizzato anche dove a.Date tra GETDATE () - 365 AND GETDATE () ... se esegui la tua query oggi non darebbe righe se non noti le date in WHERE = P
Ricardo C

4

La soluzione della vecchia scuola per farlo senza un ciclo / cursore è quella di creare una NUMBERStabella, che ha una singola colonna Integer con valori che iniziano da 1.

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

Devi compilare la tabella con abbastanza record per soddisfare le tue esigenze:

INSERT INTO NUMBERS (id) VALUES (NULL);

Una volta che hai il NUMBERStavolo, puoi usare:

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 soluzione assoluta a bassa tecnologia sarebbe:

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

Per cosa lo useresti?


Per generare elenchi di date o numeri al fine di SINISTRA UNISCITI su. Lo faresti per vedere dove ci sono degli spazi vuoti nei dati, perché ti stai unendo a SINISTRA su un elenco di dati sequenziali - i valori null renderanno ovvio dove esistono degli spazi vuoti.


1
La DUALtabella è supportata da Oracle e MySQL da utilizzare come tabella autonoma nella FROMclausola. Non esiste, la selezione di valori da esso restituirà qualunque sia il valore. L'idea era di avere lo stand-in perché una query SELECT richiede una FROMclausola che specifica almeno una tabella.
OMG Pony il

1
+1 per la creazione di una tabella di numeri permanenti anziché per la creazione da parte di RDBMS ogni volta che è necessaria la query. Le tabelle ausiliarie non sono cattive, gente!
Pezzi di pancetta dal

4

Per Access 2010 : sono necessari più passaggi; Ho seguito lo stesso modello descritto sopra, ma ho pensato di poter aiutare qualcuno in Access. Ha funzionato alla grande per me, non ho dovuto tenere una tabella delle date seminata.

Crea una tabella chiamata DUAL (simile a come funziona la tabella Oracle DUAL)

  • ID (AutoNumber)
  • DummyColumn (Testo)
  • Aggiungi valori di una riga (1, "DummyRow")

Creare una query denominata "ZeroThru9Q"; inserire manualmente la seguente sintassi:

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;

Creare una query denominata "TodayMinus1KQ" (per le date precedenti a oggi); inserire manualmente la seguente sintassi:

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

Creare una query denominata "TodayPlus1KQ" (per le date successive a oggi); inserire manualmente la seguente sintassi:

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;

Creare una query di unione denominata "TodayPlusMinus1KQ" (per date +/- 1000 giorni):

SELECT MyDate
FROM TodayMinus1KQ
UNION
SELECT MyDate
FROM TodayPlus1KQ;

Ora puoi usare la query:

SELECT MyDate
FROM TodayPlusMinus1KQ
WHERE MyDate BETWEEN #05/01/2014# and #05/30/2014#

3

Procedura + tabella temporanea:

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

3

thx Pentium10 - mi hai fatto unire stackoverflow :) - questo è il mio porting su msaccess - penso che funzionerà su qualsiasi versione:

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]);

riferimenti a MSysObjects solo perché l'accesso necessita di una tabella che contenga almeno 1 record, in una clausola from - qualsiasi tabella con almeno 1 record farebbe.


2

Come affermato (o almeno accennato) in molte delle meravigliose risposte già fornite, questo problema è facilmente risolvibile una volta che hai una serie di numeri con cui lavorare.

Nota: Quanto segue è T-SQL, ma è semplicemente la mia particolare implementazione di concetti generali già menzionati qui e su Internet in generale. Dovrebbe essere relativamente semplice convertire il codice nel dialetto che preferisci.

Come? Considera questa query:

SELECT DATEADD(d, N, '0001-01-22')
FROM Numbers -- A table containing the numbers 0 through N
WHERE N <= 5;

Quanto sopra produce l'intervallo di date 1/22/0001 - 1/27/0001 ed è estremamente banale. Ci sono 2 informazioni chiave nella query sopra: la data di inizio di 0001-01-22e l' offset di 5. Se combiniamo queste due informazioni, ovviamente abbiamo la nostra data di fine. Pertanto, date due date, la generazione di un intervallo può essere suddivisa in questo modo:

  • Trova la differenza tra due date (l'offset), facile:

    -- Returns 125 SELECT ABS(DATEDIFF(d, '2014-08-22', '2014-12-25'))

    L'uso ABS()qui garantisce che l'ordine della data sia irrilevante.

  • Genera un numero limitato di numeri, anche 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')

    Si noti che in realtà non ci interessa quello che stiamo selezionando FROMqui. Abbiamo solo bisogno di un set con cui lavorare in modo da contare il numero di righe al suo interno. Personalmente uso un TVF, alcuni usano un CTE, altri usano invece una tabella numerica, hai capito. Sostengo per l'utilizzo della soluzione più performante che capisci anche tu.

La combinazione di questi due metodi risolverà il nostro problema:

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'esempio sopra è un codice orribile ma dimostra come tutto si combina.

Più divertimento

Devo fare molto questo genere di cose, quindi ho incapsulato la logica in due TVF. Il primo genera un intervallo di numeri e il secondo utilizza questa funzionalità per generare un intervallo di date. La matematica è garantire che l'ordine di input non abbia importanza e perché volevo utilizzare l'intera gamma di numeri disponibili inGenerateRangeSmallInt .

La seguente funzione richiede ~ 16ms di tempo CPU per restituire l'intervallo massimo di 65536 date.

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

2

prova questo.

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

2

Ti piacerebbe ottenere un intervallo di date.

Nel tuo esempio desideri ottenere le date tra "20/01/2010" e "24/01/2010"

possibile soluzione:

 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'

Spiegazione

MySQL ha una funzione date_add così

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

ti darà

2010-01-21

La funzione Dateiff ti farebbe sapere spesso che dovresti ripetere questo

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

che ritorna

 4

Ottenere un elenco di date in un intervallo di date si riduce alla creazione di una sequenza di numeri interi, vedere generare interi in MySQL

La risposta più votata qui ha adottato un approccio simile a quello https://stackoverflow.com/a/2652051/1497139 come 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

che si tradurrà in

row
1.0
2.0
3.0
4.0

Le righe possono ora essere utilizzate per creare un elenco di date dalla data di inizio specificata. Per includere la data di inizio, iniziamo con la riga -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'

1

se avrai mai bisogno di più di un paio di giorni, avrai bisogno di un tavolo.

Crea un intervallo di date in mysql

poi,

select from days.day, count(mytable.field) as fields from days left join mytable on day=date where date between x and y;

3
perché l'hai pubblicato, poiché la risposta sopra non ha bisogno di una tabella e fornisce la soluzione?
Pentium10

1

Genera date tra due campi data

Se sei a conoscenza della query CTE SQL, questa soluzione ti aiuterà a risolvere la tua domanda

Ecco un esempio

Abbiamo date in una tabella

Nome tabella: "testdate"

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

Richiedi risultato:

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

Soluzione:

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

Spiegazione: spiegazione della query ricorsiva CTE

  • Prima parte della query:

    SELECT DISTINCT convert(varchar(10), StartTime, 101) AS StartTime, datediff(dd, StartTime, endTime) AS diff FROM dbo.testdate

    Spiegazione: firstcolumn è "startdate", la seconda colonna è la differenza della data di inizio e fine in giorni e verrà considerata come colonna "diff"

  • Seconda parte della query:

    UNION ALL SELECT StartTime, diff-1 AS diff FROM CTE WHERE diff<>0

    Spiegazione: Union tutti erediteranno il risultato della query precedente fino a quando il risultato non sarà nullo, quindi il risultato "StartTime" viene ereditato dalla query CTE generata e da diff, diminuisce - 1, quindi appare come 3, 2 e 1 fino a 0

Per esempio

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

Specifica del risultato

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
  • Terza parte della query

    SELECT DISTINCT DateAdd(dd,diff, StartTime) AS StartTime FROM CTE

    Aggiungerà il giorno "diff" in "startdate", quindi il risultato dovrebbe essere il seguente

Risultato

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

1

Risposta più breve di quella accettata, stessa idea:

(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')

1

Per chiunque lo desideri come vista salvata (MySQL non supporta le istruzioni selezionate nidificate nelle viste):

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;

Quindi puoi farlo

select * from date_range

ottenere

date
---
2017-06-06
2017-06-05
2017-06-04
2017-06-03
2017-06-02
...

1

Soluzione elegante che utilizza la nuova funzionalità ricorsiva (Common Table Expressions) in MariaDB> = 10.3 e 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;

Quanto sopra restituisce una tabella di date tra '01-01-2019' e '30-04-2019'. È anche abbastanza veloce. Restituire 1000 anni di date (~ 365.000 giorni) richiede circa 400 ms sulla mia macchina.


1

È una buona idea generare queste date al volo. Tuttavia, non mi sento a mio agio nel fare questo con una gamma abbastanza ampia, quindi ho finito con la seguente soluzione:

  1. Creata una tabella "DateNumbers" che conterrà i numeri utilizzati per il calcolo delle date:
CREATE TABLE DatesNumbers (
    i MEDIUMINT NOT NULL,
    PRIMARY KEY (i)
)
COMMENT='Used by Dates view'
;
  1. Popolata la tabella usando le tecniche sopra con numeri da -59999 a 40000. Questo intervallo mi darà date da 59999 giorni (~ 164 anni) indietro a 40000 giorni (109 anni) in anticipo:
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. Creato una vista "Date":
SELECT
      i,
      CURRENT_DATE() + INTERVAL i DAY AS Date
FROM
    DatesNumbers

Questo è tutto.

  • (+) Domande facili da leggere
  • (+) No al volo numeri generazioni
  • (+) Fornisce date nel passato e nel futuro e NON c'è UNIONE in vista per questo come in questo post .
  • (+) Le date "Solo nel passato" o "Solo nel futuro" possono essere filtrate utilizzando WHERE i < 0oWHERE i > 0 (PK)
  • (-) Viene utilizzata la tabella e la vista "temporanee"

0

Va bene .. Prova questo: 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

Usalo per, diciamo, generare una tabella temporanea, quindi seleziona * sulla tabella temporanea. Oppure emetti i risultati uno alla volta.
Quello che dici di voler fare non può essere fatto con un'istruzione SELECT , ma potrebbe essere fattibile con cose specifiche di MySQL.
Poi di nuovo, forse hai bisogno dei cursori: http://dev.mysql.com/doc/refman/5.0/en/cursors.html


0

Per Oracle, la mia soluzione è:

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

Sysdate può essere cambiato in una data specifica e il numero di livello può essere cambiato per dare più date.


0

se si desidera l'elenco di date tra due date:

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

* violino qui: http://sqlfiddle.com/#!6/9eecb/3469


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)

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()
    );

0

Versione SQLite della migliore soluzione 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

0

migliorata con giorni feriali e aggiunta a una tabella delle festività personalizzata microsoft MSSQL 2012 per la tabella delle date di 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)

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'

0

Può creare una procedura anche per creare una tabella del calendario con timestmap diversa dal giorno. Se vuoi un tavolo per ogni trimestre

per esempio

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

Puoi usare

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

e poi manipolare attraverso

select ts, dt from calendar_table_tmp;

che ti danno anche 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'

da qui puoi iniziare ad aggiungere altre informazioni come

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

o creare una tabella reale con l' istruzione create table


0

Una risposta più generica che funziona in MySQL AWS.

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

-1

Un'altra soluzione per mysql 8.0.1 e mariadb 10.2.2 usando espressioni di tabella comuni ricorsive:

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;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.