Jelas ada banyak cara berbeda untuk mendapatkan hasil yang sama, pertanyaan Anda sepertinya adalah cara yang efisien untuk mendapatkan hasil terakhir di setiap grup di MySQL. Jika Anda bekerja dengan data dalam jumlah besar dan menganggap Anda menggunakan InnoDB bahkan dengan versi terbaru MySQL (seperti 5.7.21 dan 8.0.4-rc) maka mungkin tidak ada cara yang efisien untuk melakukan ini.
Kita terkadang perlu melakukan ini dengan tabel dengan lebih dari 60 juta baris.
Untuk contoh-contoh ini saya akan menggunakan data dengan hanya sekitar 1,5 juta baris di mana kueri perlu menemukan hasil untuk semua grup dalam data. Dalam kasus kami yang sebenarnya, kami sering perlu mengembalikan data dari sekitar 2.000 kelompok (yang secara hipotetis tidak memerlukan pemeriksaan data yang sangat banyak).
Saya akan menggunakan tabel berikut:
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));
Tabel suhu diisi dengan sekitar 1,5 juta catatan acak, dan dengan 100 kelompok berbeda. Terpilih_group diisi dengan 100 grup (dalam kasus kami ini biasanya akan kurang dari 20% untuk semua grup).
Karena data ini acak, ini berarti bahwa beberapa baris dapat memiliki catatanTestestamp yang sama. Yang kami inginkan adalah mendapatkan daftar semua grup yang dipilih dalam urutan groupID denganTimestamp yang direkam terakhir untuk setiap grup, dan jika grup yang sama memiliki lebih dari satu baris yang cocok seperti itu, maka id terakhir yang cocok dari baris tersebut.
Jika secara hipotesis MySQL memiliki fungsi terakhir () yang mengembalikan nilai dari baris terakhir dalam klausa ORDER BY khusus maka kita bisa melakukan:
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;
yang hanya perlu memeriksa beberapa 100 baris dalam kasus ini karena tidak menggunakan fungsi GROUP BY yang normal. Ini akan dieksekusi dalam 0 detik dan karenanya sangat efisien. Perhatikan bahwa biasanya di MySQL kita akan melihat klausa ORDER BY mengikuti klausa GROUP BY namun klausa ORDER BY ini digunakan untuk menentukan ORDER untuk fungsi terakhir (), jika setelah GROUP BY maka akan memesan GROUPS. Jika tidak ada klausa GROUP BY yang hadir maka nilai terakhir akan sama di semua baris yang dikembalikan.
Namun MySQL tidak memiliki ini, jadi mari kita melihat ide-ide berbeda dari apa yang dimilikinya dan membuktikan bahwa tidak ada yang efisien.
Contoh 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
);
Ini memeriksa 3.009.254 baris dan mengambil ~ 0,859 detik pada 5.7.21 dan sedikit lebih lama pada 8.0.4-rc
Contoh 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;
Ini memeriksa 1.505.331 baris dan mengambil ~ 1,25 detik pada 5.7.21 dan sedikit lebih lama pada 8.0.4-rc
Contoh 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;
Ini memeriksa 3.009.685 baris dan mengambil ~ 1,95 detik pada 5.7.21 dan sedikit lebih lama pada 8.0.4-rc
Contoh 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
)
);
Ini memeriksa 6.137.810 baris dan memakan waktu ~ 2.2 detik pada 5.7.21 dan sedikit lebih lama pada 8.0.4-rc
Contoh 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;
Ini memeriksa 6.017.808 baris dan memakan waktu ~ 4.2 detik pada 8.0.4-rc
Contoh 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;
Ini memeriksa 6.017.908 baris dan mengambil ~ 17,5 detik pada 8.0.4-rc
Contoh 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;
Yang ini memakan waktu lama jadi saya harus membunuhnya.