Natürlich gibt es viele verschiedene Möglichkeiten, um die gleichen Ergebnisse zu erzielen. Ihre Frage scheint zu sein, wie Sie die letzten Ergebnisse in jeder Gruppe in MySQL effizient erzielen können. Wenn Sie mit großen Datenmengen arbeiten und davon ausgehen, dass Sie InnoDB auch mit den neuesten Versionen von MySQL (wie 5.7.21 und 8.0.4-rc) verwenden, gibt es möglicherweise keine effiziente Möglichkeit, dies zu tun.
Manchmal müssen wir dies mit Tabellen mit sogar mehr als 60 Millionen Zeilen tun.
Für diese Beispiele verwende ich Daten mit nur etwa 1,5 Millionen Zeilen, in denen die Abfragen Ergebnisse für alle Gruppen in den Daten finden müssten. In unseren tatsächlichen Fällen müssten wir häufig Daten von etwa 2.000 Gruppen zurückgeben (was hypothetisch nicht erfordern würde, sehr viele Daten zu untersuchen).
Ich werde die folgenden Tabellen verwenden:
CREATE TABLE temperature(
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
groupID INT UNSIGNED NOT NULL,
recordedTimestamp TIMESTAMP NOT NULL,
recordedValue INT NOT NULL,
INDEX groupIndex(groupID, recordedTimestamp),
PRIMARY KEY (id)
);
CREATE TEMPORARY TABLE selected_group(id INT UNSIGNED NOT NULL, PRIMARY KEY(id));
Die Temperaturtabelle enthält etwa 1,5 Millionen zufällige Datensätze und 100 verschiedene Gruppen. Die selected_group wird mit diesen 100 Gruppen gefüllt (in unseren Fällen wären dies normalerweise weniger als 20% für alle Gruppen).
Da diese Daten zufällig sind, bedeutet dies, dass mehrere Zeilen dieselben aufgezeichneten Zeitstempel haben können. Wir möchten eine Liste aller ausgewählten Gruppen in der Reihenfolge der Gruppen-ID mit dem zuletzt aufgezeichneten Zeitstempel für jede Gruppe erhalten. Wenn dieselbe Gruppe mehr als eine übereinstimmende Zeile hat, dann die letzte übereinstimmende ID dieser Zeilen.
Wenn MySQL hypothetisch eine last () -Funktion hätte, die Werte aus der letzten Zeile in einer speziellen ORDER BY-Klausel zurückgibt, könnten wir einfach Folgendes tun:
SELECT
last(t1.id) AS id,
t1.groupID,
last(t1.recordedTimestamp) AS recordedTimestamp,
last(t1.recordedValue) AS recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.groupID = g.id
ORDER BY t1.recordedTimestamp, t1.id
GROUP BY t1.groupID;
In diesem Fall müssten nur einige 100 Zeilen untersucht werden, da keine der normalen GROUP BY-Funktionen verwendet wird. Dies würde in 0 Sekunden ausgeführt und wäre daher hocheffizient. Beachten Sie, dass in MySQL normalerweise eine ORDER BY-Klausel nach der GROUP BY-Klausel angezeigt wird. Diese ORDER BY-Klausel wird jedoch verwendet, um die ORDER für die last () -Funktion zu bestimmen. Wenn sie nach GROUP BY liegt, werden die GROUPS bestellt. Wenn keine GROUP BY-Klausel vorhanden ist, sind die letzten Werte in allen zurückgegebenen Zeilen gleich.
MySQL hat dies jedoch nicht. Schauen wir uns also verschiedene Ideen an und beweisen, dass keines davon effizient ist.
Beispiel 1
SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.id = (
SELECT t2.id
FROM temperature t2
WHERE t2.groupID = g.id
ORDER BY t2.recordedTimestamp DESC, t2.id DESC
LIMIT 1
);
Dies untersuchte 3.009.254 Reihen und dauerte am 5.7.21 ~ 0,859 Sekunden und am 8.0.4-rc etwas länger
Beispiel 2
SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM temperature t1
INNER JOIN (
SELECT max(t2.id) AS id
FROM temperature t2
INNER JOIN (
SELECT t3.groupID, max(t3.recordedTimestamp) AS recordedTimestamp
FROM selected_group g
INNER JOIN temperature t3 ON t3.groupID = g.id
GROUP BY t3.groupID
) t4 ON t4.groupID = t2.groupID AND t4.recordedTimestamp = t2.recordedTimestamp
GROUP BY t2.groupID
) t5 ON t5.id = t1.id;
Dies untersuchte 1.505.331 Reihen und dauerte am 5.7.21 ~ 1,25 Sekunden und am 8.0.4-rc etwas länger
Beispiel 3
SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM temperature t1
WHERE t1.id IN (
SELECT max(t2.id) AS id
FROM temperature t2
INNER JOIN (
SELECT t3.groupID, max(t3.recordedTimestamp) AS recordedTimestamp
FROM selected_group g
INNER JOIN temperature t3 ON t3.groupID = g.id
GROUP BY t3.groupID
) t4 ON t4.groupID = t2.groupID AND t4.recordedTimestamp = t2.recordedTimestamp
GROUP BY t2.groupID
)
ORDER BY t1.groupID;
Dies untersuchte 3.009.685 Reihen und dauerte am 5.7.21 ~ 1,95 Sekunden und am 8.0.4-rc etwas länger
Beispiel 4
SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.id = (
SELECT max(t2.id)
FROM temperature t2
WHERE t2.groupID = g.id AND t2.recordedTimestamp = (
SELECT max(t3.recordedTimestamp)
FROM temperature t3
WHERE t3.groupID = g.id
)
);
Dies untersuchte 6.137.810 Reihen und dauerte am 5.7.21 ~ 2,2 Sekunden und am 8.0.4-rc etwas länger
Beispiel 5
SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM (
SELECT
t2.id,
t2.groupID,
t2.recordedTimestamp,
t2.recordedValue,
row_number() OVER (
PARTITION BY t2.groupID ORDER BY t2.recordedTimestamp DESC, t2.id DESC
) AS rowNumber
FROM selected_group g
INNER JOIN temperature t2 ON t2.groupID = g.id
) t1 WHERE t1.rowNumber = 1;
Dies untersuchte 6.017.808 Reihen und dauerte bei 8,0,4-rc ~ 4,2 Sekunden
Beispiel 6
SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM (
SELECT
last_value(t2.id) OVER w AS id,
t2.groupID,
last_value(t2.recordedTimestamp) OVER w AS recordedTimestamp,
last_value(t2.recordedValue) OVER w AS recordedValue
FROM selected_group g
INNER JOIN temperature t2 ON t2.groupID = g.id
WINDOW w AS (
PARTITION BY t2.groupID
ORDER BY t2.recordedTimestamp, t2.id
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
)
) t1
GROUP BY t1.groupID;
Dies untersuchte 6.017.908 Reihen und dauerte bei 8,0,4-rc ~ 17,5 Sekunden
Beispiel 7
SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.groupID = g.id
LEFT JOIN temperature t2
ON t2.groupID = g.id
AND (
t2.recordedTimestamp > t1.recordedTimestamp
OR (t2.recordedTimestamp = t1.recordedTimestamp AND t2.id > t1.id)
)
WHERE t2.id IS NULL
ORDER BY t1.groupID;
Dieser dauerte ewig, also musste ich ihn töten.