Natürlich gibt es viele verschiedene Möglichkeiten, die gleichen Ergebnisse zu erhalten. Ihre Frage scheint zu sein, was eine effiziente Möglichkeit ist, die letzten Ergebnisse in jeder Gruppe in MySQL zu erhalten. Wenn Sie mit großen Datenmengen arbeiten und davon ausgehen, dass Sie InnoDB selbst mit den neuesten Versionen von MySQL (wie 5.7.21 und 8.0.4-rc) verwenden, gibt es möglicherweise keine effiziente Methode, dies zu tun.
Manchmal müssen wir dies bei Tabellen mit mehr als 60 Millionen Zeilen tun.
Für diese Beispiele verwende ich Daten mit nur etwa 1,5 Millionen Zeilen, bei denen die Abfragen Ergebnisse für alle Gruppen in den Daten finden müssen. In unseren tatsächlichen Fällen müssten wir oft Daten von etwa 2.000 Gruppen zurückgeben (was hypothetisch gesehen nicht erfordert, 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 Tabelle temperature wird mit etwa 1,5 Millionen zufälligen Datensätzen und 100 verschiedenen Gruppen gefüllt. Die selected_group wird mit diesen 100 Gruppen befüllt (in unserem Fall wären das normalerweise weniger als 20 % für alle Gruppen).
Da es sich um Zufallsdaten handelt, können mehrere Zeilen denselben Zeitstempel haben. Was wir wollen, ist eine Liste aller ausgewählten Gruppen in der Reihenfolge der groupID mit dem letzten recordedTimestamp für jede Gruppe zu erhalten, und wenn die gleiche Gruppe mehr als eine übereinstimmende Zeile wie das dann die letzte übereinstimmende id dieser Zeilen hat.
Wenn MySQL hypothetisch eine last()-Funktion hätte, die Werte aus der letzten Zeile in einer speziellen ORDER BY-Klausel zurückliefert, könnten wir das einfach 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;
die in diesem Fall nur ein paar 100 Zeilen untersuchen müsste, da sie keine der normalen GROUP BY-Funktionen verwendet. Dies würde in 0 Sekunden ausgeführt werden und wäre daher äußerst effizient. Beachten Sie, dass in MySQL normalerweise eine ORDER BY-Klausel auf die GROUP BY-Klausel folgt. Diese ORDER BY-Klausel wird jedoch verwendet, um die ORDNUNG für die last()-Funktion zu bestimmen; wenn sie nach der GROUP BY-Klausel stünde, würde sie die GRUPPEN ordnen. Wenn keine GROUP BY-Klausel vorhanden ist, werden die letzten Werte in allen zurückgegebenen Zeilen gleich sein.
MySQL verfügt jedoch nicht über diese Möglichkeit, so dass wir uns verschiedene Ideen ansehen, was es hat, und beweisen, dass keine 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 Zeilen und dauerte ~0,859 Sekunden auf 5.7.21 und etwas länger auf 8.0.4-rc
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 Zeilen und dauerte ~1,25 Sekunden auf 5.7.21 und etwas länger auf 8.0.4-rc
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 Zeilen und dauerte ~1,95 Sekunden auf 5.7.21 und etwas länger auf 8.0.4-rc
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 Zeilen und dauerte ~2,2 Sekunden auf 5.7.21 und etwas länger auf 8.0.4-rc
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 Zeilen und dauerte ~4,2 Sekunden auf 8.0.4-rc
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 Zeilen und dauerte ~17,5 Sekunden auf 8.0.4-rc
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;
Das hier hat ewig gedauert, also musste ich es beenden.
4 Stimmen
Siehe akzeptierte Antwort in stackoverflow.com/questions/1379565/ für eine effizientere Lösung
2 Stimmen
Duplikat von stackoverflow.com/q/121387/684229
12 Stimmen
Warum können Sie nicht einfach DESC hinzufügen, d. h. select * from messages group by name DESC
0 Stimmen
Mögliches Duplikat von Wie kann ich SELECT Zeilen mit MAX(Spaltenwert), DISTINCT durch eine andere Spalte in SQL?
5 Stimmen
@KimPrince Es scheint, dass die von Ihnen vorgeschlagene Antwort nicht das tut, was erwartet wird! Ich habe gerade Ihre Methode ausprobiert, und sie hat die ERSTE Zeile für jede Gruppe genommen und DESC geordnet. Es wird NICHT die letzte Zeile jeder Gruppe genommen.
0 Stimmen
Für mehr Effizienz, siehe mysql.rjweb.org/doc.php/groupwise_max
0 Stimmen
Siehe dies: paulund.de/get-last-record-in-each-mysql-group
0 Stimmen
@DatsunBing Ihre Lösung ist die einfachste und perfekteste.
0 Stimmen
Ich habe eine interessante Lösung für dieses Problem gefunden: stackoverflow.com/a/68894680/13457138
0 Stimmen
"letzter Datensatz" ist nicht zuverlässig, wenn Sie nicht ausdrücklich einen
ORDER BY
.0 Stimmen
@DatsunBing - Die innere
ORDER BY
wird vom Optimierer ignoriert. Die äußereGROUP BY
verstößt gegenONLY_FULL_GROUP_BY
.0 Stimmen
Kann mir bitte jemand sagen, warum nicht alle Datenbanken das einzige (meiner Meinung nach) vernünftige Ergebnis verwenden, wenn sie eine Aggregatfunktion verwenden, nämlich die anderen Felder auszuwählen aus derselben Reihe ? Dies nicht zu tun, macht nicht nur für mich keinen Sinn, sondern macht auch all diese anderen Methoden notwendig, die viel komplizierter .