Chiaramente ci sono molti modi diversi per ottenere gli stessi risultati, la tua domanda sembra essere quale sia un modo efficace per ottenere gli ultimi risultati in ciascun gruppo in MySQL. Se stai lavorando con enormi quantità di dati e supponendo che tu stia utilizzando InnoDB anche con le ultime versioni di MySQL (come 5.7.21 e 8.0.4-rc), potrebbe non esserci un modo efficace per farlo.
A volte dobbiamo farlo con tabelle con anche più di 60 milioni di righe.
Per questi esempi userò i dati con solo circa 1,5 milioni di righe in cui le query dovrebbero trovare risultati per tutti i gruppi nei dati. Nei nostri casi reali avremmo spesso bisogno di restituire dati da circa 2.000 gruppi (che ipoteticamente non richiederebbero di esaminare gran parte dei dati).
Userò le seguenti tabelle:
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));
La tabella delle temperature è popolata con circa 1,5 milioni di record casuali e con 100 gruppi diversi. Il gruppo selezionato è popolato con quei 100 gruppi (nei nostri casi questo sarebbe normalmente inferiore al 20% per tutti i gruppi).
Poiché questi dati sono casuali, significa che più righe possono avere gli stessi Timestamp registrati. Ciò che vogliamo è ottenere un elenco di tutti i gruppi selezionati in ordine di ID gruppo con l'ultimo TestTest registrato per ciascun gruppo e se lo stesso gruppo ha più di una riga corrispondente come quella, allora l'ultimo ID corrispondente di quelle righe.
Se ipoteticamente MySQL avesse una funzione last () che restituiva valori dall'ultima riga in una speciale clausola ORDER BY, allora potremmo semplicemente fare:
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;
che dovrebbe solo esaminare alcune 100 righe in questo caso in quanto non utilizza nessuna delle normali funzioni GROUP BY. Ciò verrebbe eseguito in 0 secondi e quindi altamente efficiente. Nota che normalmente in MySQL vedremmo una clausola ORDER BY che segue la clausola GROUP BY, tuttavia questa clausola ORDER BY viene usata per determinare l'ORDINE per l'ultima funzione (), se fosse dopo il GROUP BY, ordinerebbe i GRUPPI. Se non è presente alcuna clausola GROUP BY, gli ultimi valori saranno gli stessi in tutte le righe restituite.
Tuttavia MySQL non ha questo, quindi diamo un'occhiata a diverse idee su ciò che ha e dimostriamo che nessuno di questi è efficiente.
Esempio 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
);
Questo ha esaminato 3.009.254 file e ha impiegato ~ 0,859 secondi su 5.7.21 e leggermente più lungo su 8.0.4-rc
Esempio 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;
Questo ha esaminato 1.505.331 file e ha impiegato ~ 1,25 secondi su 5.7.21 e leggermente più lungo su 8.0.4-rc
Esempio 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;
Questo ha esaminato 3.009.685 file e ha impiegato ~ 1,95 secondi su 5.7.21 e leggermente più lungo su 8.0.4-rc
Esempio 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
)
);
Questo ha esaminato 6.137.810 file e ha impiegato ~ 2,2 secondi su 5.7.21 e leggermente più lungo su 8.0.4-rc
Esempio 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;
Questo ha esaminato 6.017.808 righe e ha impiegato ~ 4,2 secondi su 8.0.4-rc
Esempio 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;
Questo ha esaminato 6.017.908 righe e ha impiegato ~ 17,5 secondi su 8.0.4-rc
Esempio 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;
Questo è durato un'eternità, quindi ho dovuto ucciderlo.