Rõ ràng có rất nhiều cách khác nhau để có được kết quả giống nhau, câu hỏi của bạn dường như là cách hiệu quả để có được kết quả cuối cùng trong mỗi nhóm trong MySQL. Nếu bạn đang làm việc với lượng dữ liệu khổng lồ và giả sử rằng bạn đang sử dụng InnoDB với ngay cả các phiên bản mới nhất của MySQL (như 5.7,21 và 8,0,4-rc) thì có thể không có cách nào hiệu quả để làm việc này.
Đôi khi chúng ta cần làm điều này với các bảng có hơn 60 triệu hàng.
Đối với những ví dụ này, tôi sẽ sử dụng dữ liệu chỉ với khoảng 1,5 triệu hàng trong đó các truy vấn sẽ cần tìm kết quả cho tất cả các nhóm trong dữ liệu. Trong các trường hợp thực tế của chúng tôi, chúng tôi thường sẽ cần phải trả lại dữ liệu từ khoảng 2.000 nhóm (theo giả thuyết sẽ không yêu cầu kiểm tra rất nhiều dữ liệu).
Tôi sẽ sử dụng các bảng sau:
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));
Bảng nhiệt độ được điền với khoảng 1,5 triệu bản ghi ngẫu nhiên và với 100 nhóm khác nhau. Nhóm được chọn được điền với 100 nhóm đó (trong trường hợp của chúng tôi, tỷ lệ này thường sẽ dưới 20% cho tất cả các nhóm).
Vì dữ liệu này là ngẫu nhiên, điều đó có nghĩa là nhiều hàng có thể có cùng các bản ghi được ghi lại. Điều chúng tôi muốn là có được một danh sách tất cả các nhóm được chọn theo thứ tự của nhómID với bản ghi cuối cùng cho mỗi nhóm và nếu cùng một nhóm có nhiều hơn một hàng khớp như vậy thì id khớp cuối cùng của các hàng đó.
Nếu giả thuyết MySQL có hàm () cuối cùng trả về các giá trị từ hàng cuối cùng trong mệnh đề ORDER BY đặc biệt thì chúng ta có thể thực hiện đơn giản:
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;
trong đó chỉ cần kiểm tra một vài hàng trong trường hợp này vì nó không sử dụng bất kỳ hàm GROUP BY bình thường nào. Điều này sẽ thực hiện trong 0 giây và do đó có hiệu quả cao. Lưu ý rằng thông thường trong MySQL, chúng ta sẽ thấy mệnh đề ORDER BY theo mệnh đề GROUP BY tuy nhiên mệnh đề ORDER BY này được sử dụng để xác định ORDER cho hàm () cuối cùng, nếu nó nằm sau GROUP BY thì nó sẽ ra lệnh NHÓM. Nếu không có mệnh đề GROUP BY thì các giá trị cuối cùng sẽ giống nhau trong tất cả các hàng được trả về.
Tuy nhiên, MySQL không có điều này vì vậy chúng ta hãy xem xét các ý tưởng khác nhau về những gì nó có và chứng minh rằng không có cách nào trong số này là hiệu quả.
ví dụ 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
);
Điều này đã kiểm tra 3.009.254 hàng và mất ~ 0.859 giây vào ngày 5.7,21 và lâu hơn một chút trên 8.0.4-RC
Ví dụ 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;
Điều này đã kiểm tra 1,505.331 hàng và mất ~ 1,25 giây vào ngày 5.7,21 và lâu hơn một chút trên 8.0.4-rc
Ví dụ 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;
Điều này đã kiểm tra 3.009.685 hàng và mất ~ 1,95 giây vào ngày 5.7,21 và lâu hơn một chút trên 8.0.4-RC
Ví dụ 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
)
);
Điều này đã kiểm tra 6.137.810 hàng và mất ~ 2,2 giây vào ngày 5.7,21 và lâu hơn một chút trên 8.0.4-RC
Ví dụ 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;
Điều này đã kiểm tra 6.017.808 hàng và mất ~ 4.2 giây trên 8.0.4-rc
Ví dụ 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;
Điều này đã kiểm tra 6.017.908 hàng và mất ~ 17,5 giây trên 8.0.4-RC
Ví dụ 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;
Cái này đã mất mãi mãi nên tôi phải giết nó.