Claramente, hay muchas maneras diferentes de obtener los mismos resultados, su pregunta parece ser cuál es una forma eficiente de obtener los últimos resultados en cada grupo en MySQL. Si está trabajando con grandes cantidades de datos y suponiendo que está usando InnoDB incluso con las últimas versiones de MySQL (como 5.7.21 y 8.0.4-rc), entonces puede que no haya una forma eficiente de hacerlo.
A veces necesitamos hacer esto con tablas con incluso más de 60 millones de filas.
Para estos ejemplos, usaré datos con solo alrededor de 1.5 millones de filas donde las consultas tendrían que encontrar resultados para todos los grupos en los datos. En nuestros casos reales, a menudo necesitaríamos devolver datos de aproximadamente 2,000 grupos (lo que hipotéticamente no requeriría examinar gran parte de los datos).
Usaré las siguientes tablas:
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 tabla de temperatura se rellena con aproximadamente 1,5 millones de registros aleatorios y con 100 grupos diferentes. El grupo seleccionado se rellena con esos 100 grupos (en nuestros casos, normalmente sería menos del 20% para todos los grupos).
Como estos datos son aleatorios, significa que varias filas pueden tener las mismas marcas de tiempo registradas. Lo que queremos es obtener una lista de todos los grupos seleccionados en orden de ID de grupo con la última marca de tiempo registrada para cada grupo, y si el mismo grupo tiene más de una fila coincidente como esa, entonces la última identificación coincidente de esas filas.
Si hipotéticamente MySQL tenía una función last () que devolvía valores de la última fila en una cláusula especial ORDER BY, entonces simplemente podríamos hacer:
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;
que solo necesitaría examinar unas 100 filas en este caso, ya que no utiliza ninguna de las funciones normales de GROUP BY. Esto se ejecutaría en 0 segundos y, por lo tanto, sería altamente eficiente. Tenga en cuenta que normalmente en MySQL veríamos una cláusula ORDER BY después de la cláusula GROUP BY, sin embargo, esta cláusula ORDER BY se usa para determinar el ORDER para la última función (), si fuera después de GROUP BY, estaría ordenando los GRUPOS. Si no hay una cláusula GROUP BY, los últimos valores serán los mismos en todas las filas devueltas.
Sin embargo, MySQL no tiene esto, así que echemos un vistazo a las diferentes ideas de lo que tiene y demostremos que ninguno de estos es eficiente.
Ejemplo 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
);
Esto examinó 3,009,254 filas y tomó ~ 0.859 segundos en 5.7.21 y un poco más de tiempo en 8.0.4-rc
Ejemplo 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;
Esto examinó 1,505,331 filas y tomó ~ 1.25 segundos en 5.7.21 y un poco más de tiempo en 8.0.4-rc
Ejemplo 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;
Esto examinó 3,009,685 filas y tomó ~ 1.95 segundos en 5.7.21 y un poco más de tiempo en 8.0.4-rc
Ejemplo 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
)
);
Esto examinó 6.137.810 filas y tomó ~ 2.2 segundos en 5.7.21 y un poco más de tiempo en 8.0.4-rc
Ejemplo 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;
Esto examinó 6.017.808 filas y tomó ~ 4.2 segundos en 8.0.4-rc
Ejemplo 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;
Esto examinó 6.017.908 filas y tomó ~ 17.5 segundos en 8.0.4-rc
Ejemplo 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;
Este me estaba tomando una eternidad, así que tuve que matarlo.