De toute évidence, il existe de nombreuses façons d'obtenir les mêmes résultats, votre question semble être quelle est la manière efficace d'obtenir les derniers résultats dans chaque groupe dans MySQL. Si vous travaillez avec d'énormes quantités de données et en supposant que vous utilisez InnoDB avec même les dernières versions de MySQL (telles que 5.7.21 et 8.0.4-rc), il pourrait ne pas y avoir de moyen efficace de le faire.
Nous devons parfois le faire avec des tables contenant encore plus de 60 millions de lignes.
Pour ces exemples, j'utiliserai des données avec seulement environ 1,5 million de lignes où les requêtes devraient trouver des résultats pour tous les groupes dans les données. Dans nos cas réels, nous aurions souvent besoin de renvoyer les données d'environ 2 000 groupes (ce qui, en théorie, ne nécessiterait pas d'examiner une grande partie des données).
J'utiliserai les tableaux suivants:
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));
Le tableau des températures est peuplé d'environ 1,5 million d'enregistrements aléatoires et de 100 groupes différents. Le groupe sélectionné est peuplé de ces 100 groupes (dans nos cas, cela serait normalement inférieur à 20% pour tous les groupes).
Comme ces données sont aléatoires, cela signifie que plusieurs lignes peuvent avoir les mêmes horodatages enregistrés. Ce que nous voulons, c'est obtenir une liste de tous les groupes sélectionnés par ordre d'ID de groupe avec le dernier horodatage enregistré pour chaque groupe, et si le même groupe a plus d'une ligne correspondante comme celle-là, le dernier ID correspondant de ces lignes.
Si hypothétiquement MySQL avait une fonction last () qui renvoyait des valeurs de la dernière ligne d'une clause ORDER BY spéciale, alors nous pourrions simplement faire:
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;
qui aurait seulement besoin d'examiner quelques 100 lignes dans ce cas car il n'utilise aucune des fonctions GROUP BY normales. Cela s'exécuterait en 0 secondes et serait donc très efficace. Notez que normalement dans MySQL, nous verrions une clause ORDER BY suivant la clause GROUP BY mais cette clause ORDER BY est utilisée pour déterminer l'ORDRE de la fonction last (), si elle était après le GROUP BY, elle ordonnerait les GROUPES. Si aucune clause GROUP BY n'est présente, les dernières valeurs seront les mêmes dans toutes les lignes renvoyées.
Cependant, MySQL ne l'a pas, alors examinons différentes idées de ce qu'il a et prouvons qu'aucune de celles-ci n'est efficace.
Exemple 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
);
Cela a examiné 3 009 254 rangées et a pris ~ 0,859 seconde sur 5.7.21 et légèrement plus long sur 8.0.4-rc
Exemple 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;
Cela a examiné 1505331 rangées et a pris environ 1,25 seconde le 5.7.21 et légèrement plus long le 8.0.4-rc
Exemple 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;
Cela a examiné 3 009 685 rangées et a pris environ 1,95 seconde le 5.7.21 et légèrement plus long le 8.0.4-rc
Exemple 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
)
);
Cela a examiné 6 137 810 rangées et a pris ~ 2,2 secondes le 5.7.21 et légèrement plus long le 8.0.4-rc
Exemple 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;
Cela a examiné 6 017 808 rangées et a pris ~ 4,2 secondes sur 8.0.4-rc
Exemple 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;
Cela a examiné 6 017 908 rangées et a pris ~ 17,5 secondes sur 8.0.4-rc
Exemple 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;
Celui-ci prenait une éternité donc j'ai dû le tuer.