Tage aus dem Datumsbereich generieren


135

Ich möchte eine Abfrage wie ausführen

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

Und Daten zurückgeben wie:

Tage
----------
2010-01-20
2010-01-21
2010-01-22
2010-01-23
24.01.2010

10
Mit dieser Frage ist kein weiteres Problem verbunden. Die obige Frage ist das Problem beim Beherrschen von SQL-Kursen.
Pentium10

Benötigen Sie nur eine Reihe von Daten, die auf einem ausgewählten Datumsbereich basieren?
Derek Adair

1
Ich denke an eine Verwendung, um ein Problem für Sie zu finden ... Wenn Sie die Aufgabe haben, einige fehlende Datensätze in Ihrer Tabelle auszufüllen. Und Sie müssen eine Abfrage für jeden Tag insert into table select ... as days date between '' and ''
ausführen, an dem

13
Ein Beispiel für die Verwendung wäre das Generieren von Statistiken und das Einfügen einer Zeile für Daten, zu denen Sie keine Daten haben. Wenn Sie eine Art Gruppierung durchführen, kann es viel schneller sein, alle Informationen in SQL zu generieren und in das von Ihnen benötigte Format hinzuzufügen, anstatt Ihre Daten unverändert in Ihre Sprache zu übertragen und mit dem Schleifen und Hinzufügen Ihrer Daten zu beginnen leert sich.
Nanne

1
@Nanne genau deshalb habe ich diese Frage gespeichert. Ich benötige die oben genannten Informationen, um mich Daten anzuschließen, die für bestimmte Daten möglicherweise nicht vorhanden sind.
Josh Diehl

Antworten:


318

Diese Lösung verwendet keine Schleifen, Prozeduren oder temporären Tabellen . Die Unterabfrage generiert Daten für die letzten 10.000 Tage und kann erweitert werden, um so weit vorwärts oder rückwärts zu gehen, wie Sie möchten.

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' 

Ausgabe:

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

Hinweise zur Leistung

Wenn Sie es hier testen , ist die Leistung überraschend gut: Die obige Abfrage dauert 0,0009 Sekunden.

Wenn wir die Unterabfrage erweitern, um ca. 100.000 Nummern (und damit Daten im Wert von 274 Jahren) laufen in 0,0458 Sekunden.

Im Übrigen ist dies eine sehr portable Technik, die mit den meisten Datenbanken mit geringfügigen Anpassungen funktioniert.

SQL Fiddle-Beispiel, das 1.000 Tage zurückgibt


6
Wenn Sie zu wechseln UNION, wird eine bessere Leistung UNION ALLerzielt. Es wird Zeit verschwendet, nach Duplikaten zu suchen, die nicht vorhanden sind. Es ist jedoch eine überkomplizierte IMO - wenn Sie eine Ergebnismenge mit UNIONs erstellen möchten, geben Sie das Datum an und machen Sie es fertig.
OMG Ponys

7
Warum nicht einfach das Datum angeben und damit fertig sein? Da die oben beschriebene Methode es Ihnen ermöglicht, beliebig große Mengen von Zahlen (und Datumsangaben) zu erstellen, für die keine Tabellenerstellung erforderlich ist, wäre es schmerzhaft, in der von Ihnen vorgeschlagenen Weise hart zu codieren. Offensichtlich ist es für 5 Dates übertrieben; Aber selbst dann, wenn Sie sich einer Tabelle anschließen, bei der Sie die Daten nicht im Voraus kennen, sondern nur die potenziellen Min- und Max-Werte, ist dies sinnvoll.
RedFilter

2
Es ist "schmerzhaft", nur die DATETIME-Funktion anstelle der bereits erstellten UNION-Anweisung zu verwenden? Es verringert die Notwendigkeit für die Logik, die Sie hinzufügen mussten . Daher haben Sie die Abfrage überkompliziert . Die UNION-Anweisung ist in beiden Fällen nicht skalierbar. Wenn Sie ein Datum oder eine Nummer angeben, wer möchte sie aktualisieren, um beispielsweise 20 oder 30 Daten aufzunehmen?
OMG Ponys

23
Es ist wirklich schön, eine Antwort auf die Frage zu sehen, nicht endlose Kommentare, wie es nicht gemacht werden kann oder sollte. Die meisten Dinge können getan werden, und "sollte" ist nur im Kontext von Bedeutung, der für jeden unterschiedlich ist. Diese Antwort hat mir geholfen, obwohl ich mir bewusst bin, dass es in den meisten Situationen bessere Möglichkeiten gibt.
Joe

7
Diejenigen von Ihnen, die diese Abfrage nicht zum Laufen bringen können: Bitte schlagen Sie sich ins Gesicht und lesen Sie dann den Kommentar des OP zu dieser Abfrage erneut, der 1000 Daten generiert. Da 2010 mehr als 1000 Tage zurückliegt, müssen Sie die Abfrage entsprechend anpassen.
Noel Baron

32

Hier ist eine weitere Variante mit Ansichten:

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;

Und dann können Sie einfach tun (sehen, wie elegant es ist?):

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

Aktualisieren

Es ist zu beachten, dass Sie nur vergangene Daten ab dem aktuellen Datum generieren können . Wenn Sie einen beliebigen Datumsbereich (Vergangenheit, Zukunft und dazwischen) generieren möchten, müssen Sie stattdessen diese Ansicht verwenden:

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
Dies funktioniert nicht in allen Fällen. SELECT date FROM date WHERE date ZWISCHEN '2014-12-01' UND '2014-12-28' ORDER BY date
vasanth

3
Guter Anruf @ user927258. Dies liegt daran, dass in der datesoben genannten ersten Ansicht die Daten ab dem aktuellen Datum berechnet werden. Aus diesem Grund können Sie in Zukunft keine festgelegten Daten mehr abrufen. Die Antwort von @RedFilter weist denselben Konstruktionsfehler auf. Ich habe meiner Antwort jedoch eine Problemumgehung hinzugefügt.
Stéphane

Die Verwendung einiger Ansichten vereinfacht die Abfragen definitiv und macht sie wiederverwendbar. Obwohl sie im Wesentlichen dasselbe tun, sehen alle diese UNIONKlauseln in einer einzelnen SQL-Anweisung seltsam aus.
Stewart

24

Akzeptierte Antwort funktionierte nicht für PostgreSQL (Syntaxfehler bei oder in der Nähe von "a").

Die Art und Weise, wie Sie dies in PostgreSQL tun, ist die Verwendung der generate_seriesFunktion, dh:

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

Mithilfe eines rekursiven Common Table Expression (CTE) können Sie eine Liste mit Datumsangaben erstellen und diese dann auswählen. Natürlich möchten Sie normalerweise nicht drei Millionen Daten erstellen. Dies zeigt nur die Möglichkeiten. Sie können einfach den Datumsbereich innerhalb des CTE einschränken und die where-Klausel in der select-Anweisung mithilfe des CTE weglassen.

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)

Unter Microsoft SQL Server 2005 dauerte das Generieren der CTE-Liste aller möglichen Daten 1:08. Die Generierung von hundert Jahren dauerte weniger als eine Sekunde.


7

MSSQL-Abfrage

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

Ausgabe

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
Wenn ich nur ein bisschen mehr nach unten gescrollt hätte ... seufz. Trotzdem danke. Ich habe ein CAST (<Ausdruck> ALS DATUM) hinzugefügt, um die Zeit in meiner Version zu entfernen. Wird auch verwendet, wenn a.Date zwischen GETDATE () - 365 UND GETDATE () ... wenn Sie Ihre Abfrage heute ausführen, werden keine Zeilen angezeigt, wenn Sie die Daten in WHERE = P
Ricardo C

4

Die alte Lösung, um dies ohne Schleife / Cursor zu tun, besteht darin, eine NUMBERSTabelle zu erstellen , die eine einzelne Integer-Spalte mit Werten ab 1 enthält.

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

Sie müssen die Tabelle mit genügend Datensätzen füllen, um Ihre Anforderungen zu erfüllen:

INSERT INTO NUMBERS (id) VALUES (NULL);

Sobald Sie die NUMBERSTabelle haben, können Sie verwenden:

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'

Die absolute Low-Tech-Lösung wäre:

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

Wofür würden Sie es verwenden?


Erstellen von Listen mit Datums- oder Zahlenangaben, um die Verbindung mit zu verlassen. Sie würden dies tun, um zu sehen, wo es Lücken in den Daten gibt, weil Sie sich links einer Liste von sequenziellen Daten anschließen - Nullwerte machen deutlich, wo Lücken bestehen.


1
Die DUALTabelle wird von Oracle und MySQL unterstützt und als Ersatztabelle in der FROMKlausel verwendet. Es existiert nicht. Wenn Sie Werte auswählen, wird der Wert zurückgegeben. Die Idee war, den Stand-In zu haben, da für eine SELECT-Abfrage eine FROMKlausel erforderlich ist, die mindestens eine Tabelle angibt.
OMG Ponys

1
+1 für das tatsächliche Erstellen einer permanenten Zahlentabelle, anstatt das RDBMS jedes Mal erstellen zu lassen, wenn Sie die Abfrage benötigen. Hilfstische sind nicht böse, Leute!
Bacon Bits

4

Für Access 2010 sind mehrere Schritte erforderlich. Ich folgte dem gleichen Muster wie oben, dachte aber, ich könnte jemandem in Access helfen. Hat super für mich funktioniert, ich musste keine gesetzte Datteltabelle führen.

Erstellen Sie eine Tabelle mit dem Namen DUAL (ähnlich wie die Oracle DUAL-Tabelle funktioniert).

  • ID (AutoNumber)
  • DummyColumn (Text)
  • Fügen Sie Werte für eine Zeile hinzu (1, "DummyRow")

Erstellen Sie eine Abfrage mit dem Namen "ZeroThru9Q". Geben Sie die folgende Syntax manuell ein:

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;

Erstellen Sie eine Abfrage mit dem Namen "TodayMinus1KQ" (für Daten vor dem heutigen Tag). Geben Sie die folgende Syntax manuell ein:

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

Erstellen Sie eine Abfrage mit dem Namen "TodayPlus1KQ" (für Daten nach dem heutigen Tag). Geben Sie die folgende Syntax manuell ein:

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;

Erstellen Sie eine Gewerkschaftsabfrage mit dem Namen "TodayPlusMinus1KQ" (für Daten +/- 1000 Tage):

SELECT MyDate
FROM TodayMinus1KQ
UNION
SELECT MyDate
FROM TodayPlus1KQ;

Jetzt können Sie die Abfrage verwenden:

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

3

Prozedur + temporäre Tabelle:

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 - du hast mich dazu gebracht, mich dem Stackoverflow anzuschließen :) - dies ist meine Portierung auf msaccess - denke, es wird auf jeder Version funktionieren:

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

referenzierte MSysObjects nur, weil für den Zugriff eine Tabelle erforderlich ist, die mindestens 1 Datensatz in einer from-Klausel zählt - jede Tabelle mit mindestens 1 Datensatz würde dies tun.


2

Wie in vielen der bereits gegebenen wunderbaren Antworten erwähnt (oder zumindest angedeutet), ist dieses Problem leicht zu lösen, sobald Sie eine Reihe von Zahlen haben, mit denen Sie arbeiten können.

Hinweis: Das Folgende ist T-SQL, aber es ist einfach meine spezielle Implementierung allgemeiner Konzepte, die hier und im Internet insgesamt bereits erwähnt wurden. Es sollte relativ einfach sein, den Code in den Dialekt Ihrer Wahl umzuwandeln.

Wie? Betrachten Sie diese Abfrage:

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

Das obige ergibt den Datumsbereich 1/22/0001 - 1/27/0001 und ist äußerst trivial. Es gibt 2 wichtige Informationen in der obigen Abfrage: das Startdatum von 0001-01-22und die Offset von 5. Wenn wir diese beiden Informationen kombinieren, haben wir offensichtlich unser Enddatum. Bei zwei Daten kann die Generierung eines Bereichs folgendermaßen unterteilt werden:

  • Finden Sie den Unterschied zwischen zwei angegebenen Daten (dem Versatz) ganz einfach:

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

    Durch die Verwendung ABS()hier wird sichergestellt, dass die Datumsreihenfolge irrelevant ist.

  • Generieren Sie eine begrenzte Anzahl von Zahlen, auch einfach:

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

    Beachten Sie, dass es uns eigentlich egal ist, was wir hier auswählen FROM. Wir brauchen nur einen Satz, mit dem wir arbeiten können, damit wir die Anzahl der Zeilen darin zählen können. Ich persönlich benutze einen TVF, einige verwenden einen CTE, andere verwenden stattdessen eine Zahlentabelle, Sie haben die Idee. Ich befürworte die Verwendung der leistungsstärksten Lösung, die Sie auch verstehen.

Die Kombination dieser beiden Methoden löst unser Problem:

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

Das obige Beispiel ist schrecklicher Code, zeigt aber, wie alles zusammenkommt.

Mehr Spaß

Ich muss so etwas oft machen, also habe ich die Logik in zwei TVFs zusammengefasst. Der erste generiert einen Zahlenbereich und der zweite verwendet diese Funktion, um einen Datumsbereich zu generieren. Die Mathematik besteht darin, sicherzustellen, dass die Eingabereihenfolge keine Rolle spielt und ich den gesamten Bereich der verfügbaren Zahlen verwenden wollte GenerateRangeSmallInt.

Die folgende Funktion benötigt ~ 16 ms CPU-Zeit, um den maximalen Bereich von 65536 Daten zurückzugeben.

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

Versuche dies.

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

Sie möchten einen Datumsbereich erhalten.

In Ihrem Beispiel möchten Sie die Daten zwischen "2010-01-20" und "2010-01-24" erhalten.

mögliche Lösung:

 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'

Erläuterung

MySQL hat also eine date_add Funktion

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

werde dir geben

2010-01-21

Die Datediff- Funktion lässt Sie oft wissen, dass Sie dies wiederholen müssen

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

was zurückkehrt

 4

Das Abrufen einer Liste von Datumsangaben in einem Datumsbereich läuft darauf hinaus, eine Folge von Ganzzahlen zu erstellen. Siehe Generieren einer Ganzzahlfolge in MySQL

Die am besten bewertete Antwort hier hat einen ähnlichen Ansatz wie https://stackoverflow.com/a/2652051/1497139 als Grundlage:

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

was dazu führen wird

row
1.0
2.0
3.0
4.0

Die Zeilen können jetzt verwendet werden, um eine Liste von Daten ab dem angegebenen Startdatum zu erstellen. Um das Startdatum einzuschließen, beginnen wir mit Zeile -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

Wenn Sie jemals mehr als ein paar Tage brauchen, brauchen Sie einen Tisch.

Erstellen Sie einen Datumsbereich in MySQL

dann,

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

3
Warum haben Sie dies gepostet, da die obige Antwort keine Tabelle benötigt und die Lösung bietet?
Pentium10

1

Generieren Sie Daten zwischen zwei Datumsfeldern

Wenn Sie mit SQL CTE-Abfragen vertraut sind, hilft Ihnen diese Lösung bei der Lösung Ihrer Frage

Hier ist ein Beispiel

Wir haben Daten in einer Tabelle

Tabellenname: "Testdatum"

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

Ergebnis erforderlich:

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

Lösung:

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

Erläuterung: Erklärung der rekursiven CTE-Abfrage

  • Erster Teil der Abfrage:

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

    Erläuterung: Die erste Spalte ist "Startdatum", die zweite Spalte gibt die Differenz zwischen Start- und Enddatum in Tagen an und wird als "diff" -Spalte betrachtet

  • Zweiter Teil der Abfrage:

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

    Erläuterung: Union all erbt das Ergebnis der obigen Abfrage, bis das Ergebnis null wird. Das Ergebnis "StartTime" wird also von der generierten CTE-Abfrage und von diff, verkleinern - 1 geerbt, sodass es wie 3, 2 und 1 aussieht bis 0

Beispielsweise

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

Ergebnisspezifikation

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
  • 3. Teil der Abfrage

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

    Es wird Tag "diff" in "Startdatum" hinzugefügt, daher sollte das Ergebnis wie folgt sein

Ergebnis

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

1

Kürzere als akzeptierte Antwort, gleiche Idee:

(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

Für alle, die dies als gespeicherte Ansicht wünschen (MySQL unterstützt keine verschachtelten select-Anweisungen in Ansichten):

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;

Sie können dann tun

select * from date_range

bekommen

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

1

Elegante Lösung mit neuen rekursiven Funktionen (Common Table Expressions) in MariaDB> = 10.3 und 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;

Das Obige gibt eine Tabelle mit Daten zwischen '2019-01-01' und '2019-04-30' zurück. Es ist auch anständig schnell. Die Rückgabe von Daten im Wert von 1000 Jahren (~ 365.000 Tage) dauert auf meinem Computer ungefähr 400 ms.


1

Es ist eine gute Idee, diese Daten im laufenden Betrieb zu generieren. Ich fühle mich jedoch nicht wohl, dies mit einer ziemlich großen Reichweite zu tun, so dass ich die folgende Lösung gefunden habe:

  1. Erstellt eine Tabelle "DatesNumbers", die Zahlen enthält, die für die Datumsberechnung verwendet werden:
CREATE TABLE DatesNumbers (
    i MEDIUMINT NOT NULL,
    PRIMARY KEY (i)
)
COMMENT='Used by Dates view'
;
  1. Füllen Sie die Tabelle mit den oben genannten Techniken mit Zahlen von -59999 bis 40000. In diesem Bereich werden Daten von 59999 Tagen (~ 164 Jahre) bis 40000 Tage (109 Jahre) angegeben:
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. Erstellt eine Ansicht "Termine":
SELECT
      i,
      CURRENT_DATE() + INTERVAL i DAY AS Date
FROM
    DatesNumbers

Das ist es.

  • (+) Einfach zu lesende Abfragen
  • (+) Nein, im laufenden Betrieb werden Generationen generiert
  • (+) Gibt Daten in der Vergangenheit und in der Zukunft an und es ist KEINE UNION in Sicht, wie in diesem Beitrag .
  • (+) "Nur in der Vergangenheit" oder "Nur in der Zukunft" Daten können mit WHERE i < 0oder WHERE i > 0(PK) gefiltert werden.
  • (-) 'temporäre' Tabelle & Ansicht wird verwendet

0

Okay .. Versuchen Sie dies: 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

Verwenden Sie dies beispielsweise, um eine temporäre Tabelle zu generieren, und wählen Sie dann * für die temporäre Tabelle aus. Oder geben Sie die Ergebnisse einzeln aus.
Was Sie sagen, dass Sie tun möchten, kann nicht mit einer SELECT-Anweisung getan werden , aber es kann mit Dingen durchgeführt werden, die für MySQL spezifisch sind.
Andererseits benötigen Sie möglicherweise Cursor: http://dev.mysql.com/doc/refman/5.0/en/cursors.html


0

Für Oracle lautet meine Lösung:

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

Sysdate kann auf ein bestimmtes Datum geändert werden, und die Ebenennummer kann geändert werden, um weitere Daten anzugeben.


0

Wenn Sie die Liste der Daten zwischen zwei Daten wünschen:

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

* Geige hier: 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

SQLite- Version der RedFilters-Top-Lösung

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

Verbessert mit Wochentag und Beitritt zu einer benutzerdefinierten Feiertagstabelle Microsoft MSSQL 2012 für Powerpivot-Datumstabelle 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

Kann eine Prozedur erstellen, um auch eine Kalendertabelle mit einer vom Tag abweichenden Zeitkarte zu erstellen . Wenn Sie eine Tabelle für jedes Quartal möchten

z.B

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

Sie können verwenden

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

und dann durch manipulieren

select ts, dt from calendar_table_tmp;

das gibt dir auch 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'

Von hier aus können Sie weitere Informationen hinzufügen, z

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

oder erstellen Sie eine echte Tabelle mit der Anweisung create table


0

Eine allgemeinere Antwort, die in AWS MySQL funktioniert.

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

Eine weitere Lösung für MySQL 8.0.1 und Mariadb 10.2.2 mit rekursiven allgemeinen Tabellenausdrücken:

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;
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.