Ich habe eine Tabelle mit 2 Spalten, Datum und Punktzahl. Es hat höchstens 30 Einträge für jeden der letzten 30 Tage.
date score
-----------------
1.8.2010 19
2.8.2010 21
4.8.2010 14
7.8.2010 10
10.8.2010 14
Mein Problem ist, dass einige Daten fehlen - ich möchte sehen:
date score
-----------------
1.8.2010 19
2.8.2010 21
3.8.2010 0
4.8.2010 14
5.8.2010 0
6.8.2010 0
7.8.2010 10
...
Was ich von der einzelnen Abfrage brauche, ist: 19,21,9,14,0,0,10,0,0,14 ... Das bedeutet, dass die fehlenden Daten mit 0 gefüllt sind.
Ich weiß, wie man alle Werte und die serverseitige Sprache durch Datumsangaben iteriert und die Leerzeichen vermisst. Aber ist dies in MySQL möglich, so dass ich das Ergebnis nach Datum sortiere und die fehlenden Teile erhalte.
BEARBEITEN: In dieser Tabelle gibt es eine andere Spalte mit dem Namen UserID, also habe ich 30.000 Benutzer und einige von ihnen haben die Punktzahl in dieser Tabelle. Ich lösche die Daten jeden Tag, wenn das Datum <30 Tage her ist, da ich für jeden Benutzer eine Punktzahl von 30 Tagen benötige. Der Grund dafür ist, dass ich ein Diagramm der Benutzeraktivität in den letzten 30 Tagen erstelle und zum Zeichnen eines Diagramms die 30 durch Komma getrennten Werte benötige. Ich kann also in der Abfrage sagen, dass ich die Aktivität USERID = 10203 erhalten habe, und die Abfrage würde mir die 30 Punkte bringen, eine für jeden der letzten 30 Tage. Ich hoffe ich bin jetzt klarer.
Antworten:
MySQL verfügt nicht über rekursive Funktionen. Sie müssen also den NUMBERS-Tabellentrick verwenden.
Erstellen Sie eine Tabelle, die nur inkrementierende Zahlen enthält - einfach mit einem auto_increment:
DROP TABLE IF EXISTS `example`.`numbers`; CREATE TABLE `example`.`numbers` ( `id` int(10) unsigned NOT NULL auto_increment, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Füllen Sie die Tabelle mit:
INSERT INTO `example`.`numbers` ( `id` ) VALUES ( NULL )
... für so viele Werte wie Sie brauchen.
Verwenden Sie DATE_ADD , um eine Liste mit Datumsangaben zu erstellen und die Tage basierend auf dem Wert NUMBERS.id zu erhöhen. Ersetzen Sie "2010-06-06" und "2010-06-14" durch Ihre jeweiligen Start- und Enddaten (verwenden Sie jedoch dasselbe Format, JJJJ-MM-TT) -
SELECT `x`.* FROM (SELECT DATE_ADD('2010-06-06', INTERVAL `n`.`id` - 1 DAY) FROM `numbers` `n` WHERE DATE_ADD('2010-06-06', INTERVAL `n`.`id` -1 DAY) <= '2010-06-14' ) x
LINKS VERBINDEN Sie Ihre Datentabelle basierend auf dem Zeitanteil:
SELECT `x`.`ts` AS `timestamp`, COALESCE(`y`.`score`, 0) AS `cnt` FROM (SELECT DATE_FORMAT(DATE_ADD('2010-06-06', INTERVAL `n`.`id` - 1 DAY), '%m/%d/%Y') AS `ts` FROM `numbers` `n` WHERE DATE_ADD('2010-06-06', INTERVAL `n`.`id` - 1 DAY) <= '2010-06-14') x LEFT JOIN TABLE `y` ON STR_TO_DATE(`y`.`date`, '%d.%m.%Y') = `x`.`ts`
Wenn Sie das Datumsformat beibehalten möchten, verwenden Sie die Funktion DATE_FORMAT :
quelle
WHERE n.id < DATEDIFF('2010-06-14', '2010-06-06')
undLEFT JOIN TABLE y ON y.date = DATE_FORMAT(x.ts, '%d.%m.%Y')
WHERE 'y'.'score' = 2
, werden nicht mehr alle ausgefüllten Daten angezeigtIch bin kein Fan der anderen Antworten, da Tabellen erstellt werden müssen und so weiter. Diese Abfrage erledigt dies effizient ohne Hilfstabellen.
SELECT IF(score IS NULL, 0, score) AS score, b.Days AS date FROM (SELECT a.Days FROM ( SELECT curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY AS Days 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 ) a WHERE a.Days >= curdate() - INTERVAL 30 DAY) b LEFT JOIN your_table ON date = b.Days ORDER BY b.Days;
Also lasst uns das analysieren.
SELECT IF(score IS NULL, 0, score) AS score, b.Days AS date
Das if erkennt Tage ohne Punktzahl und setzt sie auf 0. b.Days ist die konfigurierte Anzahl von Tagen, die Sie ab dem aktuellen Datum ausgewählt haben, bis zu 1000.
(SELECT a.Days FROM ( SELECT curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY AS Days 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 ) a WHERE a.Days >= curdate() - INTERVAL 30 DAY) b
Diese Unterabfrage habe ich beim Stackoverflow gesehen. Ab dem aktuellen Datum wird effizient eine Liste der letzten 1000 Tage erstellt. Das Intervall (derzeit 30) in der WHERE-Klausel am Ende bestimmt, welche Tage zurückgegeben werden. Das Maximum ist 1000. Diese Abfrage kann leicht geändert werden, um Daten im Wert von 100 Jahren zurückzugeben, aber 1000 sollte für die meisten Dinge gut sein.
Dies ist der Teil, der Ihre Tabelle mit der Partitur enthält. Sie vergleichen mit dem ausgewählten Datumsbereich aus der Datumsgeneratorabfrage, um bei Bedarf Nullen eingeben zu können (die Punktzahl wird
NULL
anfänglich auf gesetzt, da es sich um eine handeltLEFT JOIN
; dies ist in der select-Anweisung festgelegt). Ich bestelle es auch nach Datum, nur weil. Dies ist eine Präferenz, Sie können auch nach Punktzahl bestellen.Vorher
ORDER BY
konnten Sie sich leicht mit Ihrer Tabelle über Benutzerinformationen verbinden, die Sie bei Ihrer Bearbeitung erwähnt haben, um diese letzte Anforderung hinzuzufügen.Ich hoffe, diese Version der Abfrage hilft jemandem. Danke fürs Lesen.
quelle
Sie können dies mithilfe einer Kalendertabelle erreichen . Dies ist eine Tabelle, die Sie einmal erstellen und mit einem Datumsbereich füllen (z. B. ein Datensatz für jeden Tag 2000-2050; das hängt von Ihren Daten ab). Anschließend können Sie eine äußere Verknüpfung Ihrer Tabelle mit der Kalendertabelle herstellen. Wenn in Ihrer Tabelle ein Datum fehlt, geben Sie 0 für die Punktzahl zurück.
quelle
SELECT c.date, COALESCE(y.score, 0) AS cnt FROM calendar c LEFT JOIN y ON y.date = c.date WHERE c.date BETWEEN '2010-06-06' AND '2010-06-14'
Die Zeit verging, seit diese Frage gestellt wurde. MySQL 8.0 wurde 2018 veröffentlicht und unterstützt rekursive allgemeine Tabellenausdrücke , die eine elegante und hochmoderne Möglichkeit bieten, diese Frage zu lösen.
Die folgende Abfrage kann verwendet werden, um eine Liste von Daten zu erstellen, beispielsweise für die ersten 15 Tage im August 2010:
with recursive all_dates(dt) as ( -- anchor select '2010-08-01' dt union all -- recursion with stop condition select dt + interval 1 day from all_dates where dt + interval 1 day <= '2010-08-15' ) select * from all_dates
Sie können
left join
diese Ergebnismenge dann mit Ihrer Tabelle erstellen, um die erwartete Ausgabe zu generieren:with recursive all_dates(dt) as ( -- anchor select '2010-08-01' dt union all -- recursion with stop condition select dt + interval 1 day from all_dates where dt + interval 1 day <= '2010-08-15' ) select d.dt date, coalesce(t.score, 0) score from all_dates d left join mytable t on t.date = d.dt order by d.dt
Demo zu DB Fiddle :
quelle
Die Antwort von Michael Conard ist großartig, aber ich brauchte Intervalle von 15 Minuten, in denen die Zeit immer am Anfang jeder 15. Minute beginnen muss:
SELECT a.Days FROM ( SELECT FROM_UNIXTIME( FLOOR( UNIX_TIMESTAMP() / (15 * 60) ) * (15 * 60)) - INTERVAL 15 * (a.a + (10 * b.a) + (100 * c.a)) MINUTE AS Days 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 ) a WHERE a.Days >= curdate() - INTERVAL 30 DAY
Dadurch wird die aktuelle Zeit auf die 15. Runde der vorherigen Runde eingestellt:
Und dies wird die Zeit mit einem 15-minütigen Schritt verkürzen:
Wenn es einen einfacheren Weg gibt, lassen Sie es mich bitte wissen.
quelle