Claramente, existem muitas maneiras diferentes de obter os mesmos resultados, sua pergunta parece ser o que é uma maneira eficiente de obter os últimos resultados em cada grupo no MySQL. Se você estiver trabalhando com grandes quantidades de dados e assumindo que está usando o InnoDB até mesmo com as versões mais recentes do MySQL (como 5.7.21 e 8.0.4-rc), pode não haver uma maneira eficiente de fazer isso.
Às vezes, precisamos fazer isso com tabelas com mais de 60 milhões de linhas.
Para esses exemplos, usarei dados com apenas cerca de 1,5 milhão de linhas em que as consultas precisariam encontrar resultados para todos os grupos nos dados. Em nossos casos reais, muitas vezes precisaríamos retornar dados de cerca de 2.000 grupos (o que, hipoteticamente, não seria necessário examinar muito dos dados).
Vou usar as seguintes tabelas:
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));
A tabela de temperatura é preenchida com cerca de 1,5 milhão de registros aleatórios e com 100 grupos diferentes. O grupo selected_ é preenchido com esses 100 grupos (em nossos casos, normalmente seria inferior a 20% para todos os grupos).
Como esses dados são aleatórios, significa que várias linhas podem ter os mesmos registros de data e hora registrados. O que queremos é obter uma lista de todos os grupos selecionados na ordem do groupID com o último timestamp registrado para cada grupo e, se o mesmo grupo tiver mais de uma linha correspondente assim, o último ID correspondente dessas linhas.
Se, hipoteticamente, o MySQL tivesse uma função last () que retornasse valores da última linha em uma cláusula ORDER BY especial, poderíamos simplesmente fazer:
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 precisaria examinar apenas algumas 100 linhas nesse caso, pois não usa nenhuma das funções normais de GROUP BY. Isso seria executado em 0 segundos e, portanto, seria altamente eficiente. Note que normalmente no MySQL veríamos uma cláusula ORDER BY seguindo a cláusula GROUP BY, no entanto, esta cláusula ORDER BY é usada para determinar a ORDER da última função (), se fosse depois do GROUP BY, ela estaria ordenando os GROUPS. Se nenhuma cláusula GROUP BY estiver presente, os últimos valores serão os mesmos em todas as linhas retornadas.
No entanto, o MySQL não possui isso, então vamos examinar diferentes idéias do que ele possui e provar que nenhuma delas é eficiente.
Exemplo 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
);
Isso examinou 3.009.254 linhas e levou ~ 0,859 segundos em 5.7.21 e um pouco mais em 8.0.4-rc
Exemplo 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;
Isso examinou 1.505.331 linhas e levou ~ 1,25 segundos em 5.7.21 e um pouco mais em 8.0.4-rc
Exemplo 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;
Isso examinou 3.009.685 linhas e levou ~ 1,95 segundos em 5.7.21 e um pouco mais em 8.0.4-rc
Exemplo 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
)
);
Isso examinou 6.137.810 linhas e levou ~ 2,2 segundos em 5.7.21 e um pouco mais em 8.0.4-rc
Exemplo 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;
Isso examinou 6.017.808 linhas e levou ~ 4,2 segundos no 8.0.4-rc
Exemplo 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;
Este examinou 6.017.908 linhas e levou ~ 17,5 segundos no 8.0.4-rc
Exemplo 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 estava levando uma eternidade, então eu tive que matá-lo.