Зрозуміло, що існує багато різних способів отримання однакових результатів. Здається, ваше питання полягає в тому, що є ефективним способом отримання останніх результатів для кожної групи в MySQL. Якщо ви працюєте з величезною кількістю даних і припускаєте, що використовуєте InnoDB навіть із останніми версіями MySQL (такими як 5.7.21 та 8.0.4-rc), то це може бути не ефективним способом цього зробити.
Нам іноді потрібно робити це за допомогою таблиць із ще більше 60 мільйонами рядків.
Для цих прикладів я буду використовувати дані лише з приблизно 1,5 мільйона рядків, де запити повинні знайти результати для всіх груп у даних. У наших фактичних випадках нам часто потрібно повертати дані приблизно з 2000 груп (що гіпотетично не вимагає вивчення дуже багатьох даних).
Я буду використовувати наступні таблиці:
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));
Температурна таблиця заповнена приблизно 1,5 мільйона випадкових записів і зі 100 різними групами. Вибрана_група складається з цих 100 груп (у наших випадках це, як правило, менше 20% для всіх груп).
Оскільки ці дані є випадковими, це означає, що кілька рядків можуть мати однакові записані мітки часу. Ми хочемо отримати список усіх виділених груп у порядку groupID з останньою записаною міткою часу для кожної групи, і якщо одна і та ж група має більше одного відповідного рядка, як той, то останній ідентифікаційний збіг цих рядків.
Якщо гіпотетично MySQL мав функцію останнього (), яка повертала значення з останнього рядка в спеціальному пункті ЗАМОВЛЕННЯ ПО, то ми могли б просто зробити:
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;
що в цьому випадку потрібно буде вивчити лише кілька 100 рядків, оскільки він не використовує жодної з звичайних функцій GROUP BY. Це виконується за 0 секунд і, отже, буде дуже ефективним. Зауважте, що зазвичай у MySQL ми бачимо пункт ORDER BY за пунктом GROUP BY, однак цей пункт ORDER BY використовується для визначення ORDER для останньої функції (), якби це було після GROUP BY, тоді він би замовляв групи. Якщо немає пропозиції BY BY, то останні значення будуть однаковими у всіх повернених рядках.
Однак у MySQL цього немає, тому давайте розглянемо різні ідеї того, що він має, і докажемо, що жодне з них не є ефективним.
Приклад 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
);
Це вивчило 3 099 254 рядків і зайняло ~ 0,859 секунди 5,7,21 і трохи довше 8,0,4-rc
Приклад 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;
Це вивчило 1505,331 рядків і зайняло ~ 1,25 секунди 5,7,21 і трохи довше 8,0,4-rc
Приклад 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;
Це досліджувало 3 009 685 рядків і зайняло ~ 1,95 секунди 5,7,21 і трохи довше 8,0,4-rc
Приклад 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
)
);
Це досліджувало 6,137,810 рядків і займало ~ 2,2 секунди 5,7,21 і трохи довше 8,0,4-rc
Приклад 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;
Це досліджувало 617808 рядків і займало ~ 4,2 секунди на 8,0,4-rc
Приклад 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;
Це досліджувало 617908 рядків і займало ~ 17,5 секунди на 8,0,4-rc
Приклад 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;
Цей приймав назавжди, тому мені довелося його вбити.