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 Datefrom(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as acrossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as bcrossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as ccrossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as d) awhere a.Date between'2010-01-20'and'2010-01-24'
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:
CREATEVIEW digits ASSELECT0AS digit UNIONALLSELECT1UNIONALLSELECT2UNIONALLSELECT3UNIONALLSELECT4UNIONALLSELECT5UNIONALLSELECT6UNIONALLSELECT7UNIONALLSELECT8UNIONALLSELECT9;CREATEVIEW numbers ASSELECT
ones.digit + tens.digit *10+ hundreds.digit *100+ thousands.digit *1000AS numberFROM
digits as ones,
digits as tens,
digits as hundreds,
digits as thousands;CREATEVIEW dates ASSELECT
SUBDATE(CURRENT_DATE(), number)AS dateFROM
numbers;
Und dann können Sie einfach tun (sehen, wie elegant es ist?):
SELECT
dateFROM
datesWHERE
date BETWEEN'2010-01-20'AND'2010-01-24'ORDERBY
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:
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:
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.
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.
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.
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.
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:
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(selecttop1"0"as espr1 from MSysObjects
unionallselecttop1"1"as espr2 from MSysObjects
unionallselecttop1"2"as espr3 from MSysObjects
unionallselecttop1"3"as espr4 from MSysObjects
unionallselecttop1"4"as espr5 from MSysObjects
unionallselecttop1"5"as espr6 from MSysObjects
unionallselecttop1"6"as espr7 from MSysObjects
unionallselecttop1"7"as espr8 from MSysObjects
unionallselecttop1"8"as espr9 from MSysObjects
unionallselecttop1"9"as espr9 from MSysObjects
)as a,(selecttop1"0"as espr1 from MSysObjects
unionallselecttop1"1"as espr2 from MSysObjects
unionallselecttop1"2"as espr3 from MSysObjects
unionallselecttop1"3"as espr4 from MSysObjects
unionallselecttop1"4"as espr5 from MSysObjects
unionallselecttop1"5"as espr6 from MSysObjects
unionallselecttop1"6"as espr7 from MSysObjects
unionallselecttop1"7"as espr8 from MSysObjects
unionallselecttop1"8"as espr9 from MSysObjects
unionallselecttop1"9"as espr9 from MSysObjects
)as b,(selecttop1"0"as espr1 from MSysObjects
unionallselecttop1"1"as espr2 from MSysObjects
unionallselecttop1"2"as espr3 from MSysObjects
unionallselecttop1"3"as espr4 from MSysObjects
unionallselecttop1"4"as espr5 from MSysObjects
unionallselecttop1"5"as espr6 from MSysObjects
unionallselecttop1"6"as espr7 from MSysObjects
unionallselecttop1"7"as espr8 from MSysObjects
unionallselecttop1"8"as espr9 from MSysObjects
unionallselecttop1"9"as espr9 from MSysObjects
)as c
)as d)WHERE date_value
between dateserial([start_year],[start_month],[start_day])and dateserial([end_year],[end_month],[end_day]);
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.
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 NWHERE 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:
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(ORDERBY(SELECTNULL))-1FROM(SELECT'A'AS S UNIONALLSELECT'A'UNIONALLSELECT'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.
CREATEFUNCTION dbo.GenerateRangeDate (@date1 DATE,@date2 DATE
)
RETURNS TABLEWITH SCHEMABINDING
ASRETURN(SELECT D = DATEADD(d, N +32768,CASEWHEN@date1 <=@date2 THEN@date1 ELSE@date2 END)FROM dbo.GenerateRangeSmallInt(-32768, ABS(DATEDIFF(d,@date1,@date2))-32768));
GO
CREATEFUNCTION dbo.GenerateRangeSmallInt (@num1 SMALLINT =-32768,@num2 SMALLINT =32767)
RETURNS TABLEWITH SCHEMABINDING
ASRETURN(WITH Numbers(N)AS(SELECT N FROM(VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 16,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 32,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 48,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 64,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 80,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 96,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 112,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 128,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 144,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 160,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 176,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 192,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 208,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 224,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 240,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)-- 256) V (N))SELECTTOP(ABS(CAST(@num1 AS INT)- CAST(@num2 AS INT))+1)
N = ROW_NUMBER()OVER(ORDERBY(SELECTNULL))+CASEWHEN@num1 <=@num2 THEN@num1 ELSE@num2 END-1FROM Numbers A
, Numbers B
);
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
SELECT@row:=@row+1asrowFROM(select0unionallselect1unionallselect3unionallselect4unionallselect5unionallselect6unionallselect6unionallselect7unionallselect8unionallselect9) t,(select0unionallselect1unionallselect3unionallselect4unionallselect5unionallselect6unionallselect6unionallselect7unionallselect8unionallselect9) t2,(select0unionallselect1unionallselect3unionallselect4unionallselect5unionallselect6unionallselect6unionallselect7unionallselect8unionallselect9) t3,(select0unionallselect1unionallselect3unionallselect4unionallselect5unionallselect6unionallselect6unionallselect7unionallselect8unionallselect9) t4,(SELECT@row:=0) r
limit 4
was dazu führen wird
row1.02.03.04.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+1asrowFROM(select0unionallselect1unionallselect3unionallselect4unionallselect5unionallselect6unionallselect6unionallselect7unionallselect8unionallselect9) t,(select0unionallselect1unionallselect3unionallselect4unionallselect5unionallselect6unionallselect6unionallselect7unionallselect8unionallselect9) t2,(select0unionallselect1unionallselect3unionallselect4unionallselect5unionallselect6unionallselect6unionallselect7unionallselect8unionallselect9) t3,(select0unionallselect1unionallselect3unionallselect4unionallselect5unionallselect6unionallselect6unionallselect7unionallselect8unionallselect9) t4,(SELECT@row:=-1) r
) sequence
where date_add('2010-01-20', interval row day)<='2010-01-24'
WITH CTE AS(SELECTDISTINCTconvert(varchar(10),StartTime,101)AS StartTime,
datediff(dd,StartTime, endTime)AS diff
FROM dbo.testdate
UNIONALLSELECT StartTime,
diff -1AS diff
FROM CTE
WHERE diff<>0)SELECTDISTINCT DateAdd(dd,diff, StartTime)AS StartTime
FROM CTE
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
STARTDATE Specification
10/24/2012--> From Record 110/27/2012--> From Record 210/27/2012--> From Record 210/27/2012--> From Record 210/30/2012--> From Record 3
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
(SELECT TRIM('2016-01-05'+ INTERVAL a + b DAY) date
FROM(SELECT0 a UNIONSELECT1 a UNIONSELECT2UNIONSELECT3UNIONSELECT4UNIONSELECT5UNIONSELECT6UNIONSELECT7UNIONSELECT8UNIONSELECT9) d,(SELECT0 b UNIONSELECT10UNIONSELECT20UNIONSELECT30UNIONSELECT40) m
WHERE'2016-01-05'+ INTERVAL a + b DAY <='2016-01-21')
Für alle, die dies als gespeicherte Ansicht wünschen (MySQL unterstützt keine verschachtelten select-Anweisungen in Ansichten):
createview zero_to_nine asselect0as n unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9;createview date_range asselect curdate()- INTERVAL (a.n +(10* b.n)+(100* c.n)) DAY as date
from zero_to_nine as a
crossjoin zero_to_nine as b
crossjoin zero_to_nine as c;
Sie können dann tun
select*from date_range
bekommen
date
---2017-06-062017-06-052017-06-042017-06-032017-06-02...
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
UNIONSELECT 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.
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:
Erstellt eine Tabelle "DatesNumbers", die Zahlen enthält, die für die Datumsberechnung verwendet werden:
CREATETABLE DatesNumbers (
i MEDIUMINT NOTNULL,PRIMARYKEY(i))
COMMENT='Used by Dates view';
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:
INSERTINTO DatesNumbers
SELECT
a.i +(10* b.i)+(100* c.i)+(1000* d.i)+(10000* e.i)-59999AS i
FROM(SELECT0AS i UNIONALLSELECT1UNIONALLSELECT2UNIONALLSELECT3UNIONALLSELECT4UNIONALLSELECT5UNIONALLSELECT6UNIONALLSELECT7UNIONALLSELECT8UNIONALLSELECT9)AS a,(SELECT0AS i UNIONALLSELECT1UNIONALLSELECT2UNIONALLSELECT3UNIONALLSELECT4UNIONALLSELECT5UNIONALLSELECT6UNIONALLSELECT7UNIONALLSELECT8UNIONALLSELECT9)AS b,(SELECT0AS i UNIONALLSELECT1UNIONALLSELECT2UNIONALLSELECT3UNIONALLSELECT4UNIONALLSELECT5UNIONALLSELECT6UNIONALLSELECT7UNIONALLSELECT8UNIONALLSELECT9)AS c,(SELECT0AS i UNIONALLSELECT1UNIONALLSELECT2UNIONALLSELECT3UNIONALLSELECT4UNIONALLSELECT5UNIONALLSELECT6UNIONALLSELECT7UNIONALLSELECT8UNIONALLSELECT9)AS d,(SELECT0AS i UNIONALLSELECT1UNIONALLSELECT2UNIONALLSELECT3UNIONALLSELECT4UNIONALLSELECT5UNIONALLSELECT6UNIONALLSELECT7UNIONALLSELECT8UNIONALLSELECT9)AS e
;
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.
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
set language 'SPANISH'DECLARE@tabletable(fechaDesde datetime , fechaHasta datetime )INSERT@tableVALUES('20151231','20161231');WITH x AS(SELECT DATEADD( m ,1,fechaDesde )as fecha FROM@tableUNIONALLSELECT DATEADD( m ,1,fecha )FROM@table t INNERJOIN x ON DATEADD( m ,1,x.fecha )<= t.fechaHasta
)SELECTLEFT(CONVERT( VARCHAR, fecha ,112),6)as Periodo_Id
,DATEPART ( dd, DATEADD(dd,-(DAY(fecha)-1),fecha)) Num_Dia_Inicio
,DATEADD(dd,-(DAY(fecha)-1),fecha) Fecha_Inicio
,DATEPART ( mm , fecha ) Mes_Id
,DATEPART ( yy , fecha ) Anio
,DATEPART ( dd, DATEADD(dd,-(DAY(DATEADD(mm,1,fecha))),DATEADD(mm,1,fecha))) Num_Dia_Fin
,DATEADD(dd,-(DAY(DATEADD(mm,1,fecha))),DATEADD(mm,1,fecha)) ultimoDia
,datename(MONTH, fecha) mes
,'Q'+convert(varchar(10), DATEPART(QUARTER, fecha)) Trimestre_Name
FROM x
OPTION(MAXRECURSION 0)
select d.Date
from(select
date(julianday('2010-01-20')+(a.a +(10* b.a)+(100* c.a)))as Date
from(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as a
crossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as b
crossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as c
) d
where
d.Date between'2010-01-20'and'2010-01-24'orderby d.Date
WITH
Digits AS(SELECT0 D UNIONSELECT1UNIONSELECT2UNIONSELECT3UNIONSELECT4UNIONSELECT5UNIONSELECT6UNIONSELECT7UNIONSELECT8UNIONSELECT9),
Dates AS(SELECT adddate('1970-01-01',t4.d*10000+ t3.d*1000+ t2.d*100+ t1.d*10+t0.d)AS date FROM Digits AS t0, Digits AS t1, Digits AS t2, Digits AS t3, Digits AS t4)SELECT*FROM Dates WHERE date BETWEEN'2017-01-01'AND'2017-12-31'
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
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(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as a
crossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as b
crossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as c
) datetable
where datetable.Date between now()- INTERVAL 14 Day and Now()orderby datetable.Date DESC
insert into table select ... as days date between '' and ''
Antworten:
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.
Ausgabe:
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
quelle
UNION
, wird eine bessere LeistungUNION ALL
erzielt. 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.Hier ist eine weitere Variante mit Ansichten:
Und dann können Sie einfach tun (sehen, wie elegant es ist?):
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:
quelle
dates
oben 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.UNION
Klauseln in einer einzelnen SQL-Anweisung seltsam aus.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_series
Funktion, dh:quelle
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.
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.
quelle
MSSQL-Abfrage
Ausgabe
quelle
Die alte Lösung, um dies ohne Schleife / Cursor zu tun, besteht darin, eine
NUMBERS
Tabelle zu erstellen , die eine einzelne Integer-Spalte mit Werten ab 1 enthält.Sie müssen die Tabelle mit genügend Datensätzen füllen, um Ihre Anforderungen zu erfüllen:
Sobald Sie die
NUMBERS
Tabelle haben, können Sie verwenden:Die absolute Low-Tech-Lösung wäre:
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.
quelle
DUAL
Tabelle wird von Oracle und MySQL unterstützt und als Ersatztabelle in derFROM
Klausel 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 eineFROM
Klausel erforderlich ist, die mindestens eine Tabelle angibt.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).
Erstellen Sie eine Abfrage mit dem Namen "ZeroThru9Q". Geben Sie die folgende Syntax manuell ein:
Erstellen Sie eine Abfrage mit dem Namen "TodayMinus1KQ" (für Daten vor dem heutigen Tag). Geben Sie die folgende Syntax manuell ein:
Erstellen Sie eine Abfrage mit dem Namen "TodayPlus1KQ" (für Daten nach dem heutigen Tag). Geben Sie die folgende Syntax manuell ein:
Erstellen Sie eine Gewerkschaftsabfrage mit dem Namen "TodayPlusMinus1KQ" (für Daten +/- 1000 Tage):
Jetzt können Sie die Abfrage verwenden:
quelle
Prozedur + temporäre Tabelle:
quelle
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:
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.
quelle
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:
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-22
und die Offset von5
. 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:
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.
quelle
Versuche dies.
quelle
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:
Erläuterung
MySQL hat also eine date_add Funktion
werde dir geben
Die Datediff- Funktion lässt Sie oft wissen, dass Sie dies wiederholen müssen
was zurückkehrt
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:
was dazu führen wird
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;
quelle
Wenn Sie jemals mehr als ein paar Tage brauchen, brauchen Sie einen Tisch.
Erstellen Sie einen Datumsbereich in MySQL
dann,
quelle
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"
Ergebnis erforderlich:
Lösung:
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
Ergebnisspezifikation
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
quelle
Kürzere als akzeptierte Antwort, gleiche Idee:
quelle
Für alle, die dies als gespeicherte Ansicht wünschen (MySQL unterstützt keine verschachtelten select-Anweisungen in Ansichten):
Sie können dann tun
bekommen
quelle
Elegante Lösung mit neuen rekursiven Funktionen (Common Table Expressions) in MariaDB> = 10.3 und MySQL> = 8.0.
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.
quelle
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:
Das ist es.
WHERE i < 0
oderWHERE i > 0
(PK) gefiltert werden.quelle
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
quelle
Für Oracle lautet meine Lösung:
Sysdate kann auf ein bestimmtes Datum geändert werden, und die Ebenennummer kann geändert werden, um weitere Daten anzugeben.
quelle
Wenn Sie die Liste der Daten zwischen zwei Daten wünschen:
* Geige hier: http://sqlfiddle.com/#!6/9eecb/3469
quelle
quelle
quelle
SQLite- Version der RedFilters-Top-Lösung
quelle
Verbessert mit Wochentag und Beitritt zu einer benutzerdefinierten Feiertagstabelle Microsoft MSSQL 2012 für Powerpivot-Datumstabelle https://gist.github.com/josy1024/cb1487d66d9e0ccbd420bc4a23b6e90e
quelle
quelle
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
Sie können verwenden
und dann durch manipulieren
das gibt dir auch ts
Von hier aus können Sie weitere Informationen hinzufügen, z
oder erstellen Sie eine echte Tabelle mit der Anweisung create table
quelle
Eine allgemeinere Antwort, die in AWS MySQL funktioniert.
quelle
Eine weitere Lösung für MySQL 8.0.1 und Mariadb 10.2.2 mit rekursiven allgemeinen Tabellenausdrücken:
quelle