Tìm nạp hàng mới nhất được nhóm theo một cột trong MySQL


7

Vấn đề của tôi có vẻ như cần có một giải pháp đơn giản hơn nhiều so với những gì tôi nghĩ ra. Bắt đầu với tập dữ liệu này:

log_table

+--------+-----------+------------------+---------+
| log_id | entity_id |       date       | comment |
+--------+-----------+------------------+---------+
|      1 | A         | 2012-10-23 07:50 | foo     |
|      2 | B         | 2012-10-23 07:59 | bar     |
|      3 | B         | 2012-10-23 08:11 | baz     |
|      4 | A         | 2012-10-23 08:23 | bat     |
+--------+-----------+------------------+---------+

Giả sử tôi muốn nhận ngày nhập nhật ký mới nhất cho mỗi thực thể để kết quả trông như sau:

Results:
+-----------+------------------+--------------+
| entity_id |  last_log_date   | last_comment |
+-----------+------------------+--------------+
| B         | 2012-10-23 08:11 | baz          |
| A         | 2012-10-23 08:23 | bat          |
+-----------+------------------+--------------+

Tôi hiện đang sử dụng MySQL trông giống như:

SELECT
  `entity_id`,
  `date` AS last_log_date,
  `comment` AS last_comment
FROM (
  SELECT *
  FROM `log_table`
  ORDER BY `date` DESC, log_id ASC
) AS `ordered_log`
GROUP BY `entity_id`

Đây hoạt động tốt nhưng nó không có vẻ rất hiệu quả đối với tôi, đó phải là một cách tốt hơn để làm điều này, chắc chắn?

Câu trả lời:


4

Tạo một truy vấn con thu thập các khóa từ log_tablengày tối đa trên mỗi thực thể. Sau đó, thực hiện THAM GIA THAM GIA của Truy vấn đó trở lại log_table.

SELECT
    B.entity_id,B.last_log_date,B.last_comment
FROM
(
    SELECT entity_id,MAX(last_log_date) last_log_date
    FROM log_table GROUP BY entity_id
) A INNER JOIN B USING (entity_id,last_log_date);

Hãy thử một lần !!!

Bạn thực sự có thể tăng tốc độ này nếu bạn có một chỉ số ghép như thế này

ALTER TABLE log_table ADD INDEX entity_date_ndx (entity_id,last_log_date);

Các chỉ mục trên mỗi cột riêng biệt có thể mang lại sự hợp nhất chỉ mục. Chỉ số ghép này sẽ bỏ qua điều đó.

Hãy thử LEFT JOINthay vìINNER JOIN

SELECT
    B.entity_id,B.last_log_date,B.last_comment
FROM
(
    SELECT entity_id,MAX(last_log_date) last_log_date
    FROM log_table GROUP BY entity_id
) A LEFT JOIN B USING (entity_id,last_log_date);

Ok truy vấn này có vẻ hiệu quả hơn. Trên một bảng có 87.283 hồ sơ entity_id và 1.309.252 riêng biệt, truy vấn của tôi mất quá nhiều thời gian (hơn 50 giây cho đến khi tôi giết truy vấn) nhưng truy vấn của bạn chỉ mất hơn 11 giây. Có cách nào để tăng tốc độ này hơn nữa không? Tôi đã có chỉ mục trên entity_idvà các datecột rồi. Lý tưởng nhất là truy vấn sẽ chạy nhiều hơn hoặc ít hơn ngay lập tức nếu có thể ...
Asgrim

@Asgrim Bạn có hai chỉ mục riêng biệt trên entity_iddatecột hoặc một chỉ mục trên cả hai cột không?
Matts

@matts Theo câu trả lời đã được chỉnh sửa của @ RolandoMySQLDBA, tôi đã thêm một chỉ mục ghép vào các cột và điều này không tạo ra sự khác biệt nào. Chỉ chạy phần truy vấn con (tức là SELECT entity_id,MAX(last_log_date) last_log_date FROM log_table GROUP BY entity_idphần) và đó là những gì đang dành thời gian để chạy (vẫn mất 11 giây). Dường như với tôi rằng điều này sẽ không nhanh hơn?
Asgrim

1
Vui lòng thay đổi INNER THAM GIA thành TRÁI PHIẾU để thấy thứ tự truy vấn con được bảo toàn và nhanh hơn.
RolandoMySQLDBA

@RolandoMySQLDBA Điều đó cải thiện nó một chút, nhưng chỉ chạy truy vấn con (tức là SELECT entity_id, MAX(`date`) last_log_date FROM log_table GROUP BY entity_id) mất 8 giây. Điều tôi đã hỏi trong bình luận cuối cùng của mình là liệu có cách nào để tăng đáng kể hiệu năng của truy vấn đó không - điều đó rất có thể sẽ giải quyết tất cả các vấn đề ...
Asgrim

3

Các công trình phụ; Đây là cách bạn sẽ làm điều đó mà không cần truy vấn con:

SELECT
  `entity_id`,
  SUBSTRING_INDEX(GROUP_CONCAT(`date` ORDER BY `date` DESC), ',', 1) AS last_log_date,
  SUBSTRING_INDEX(GROUP_CONCAT(`comment` ORDER BY `date` DESC), ',', 1) AS last_comment
FROM `log_table`
GROUP BY `entity_id`

Truy vấn ở trên sử dụng GROUP_CONCATđể tạo ra một chuỗi dài các giá trị cho mỗi nhóm, sau đó được phân tích cú pháp để trích xuất mã thông báo đầu tiên thông qua SUBSTRING_INDEX.

Bạn có thể có một cách giải quyết tuyệt vời nếu chỉ có các Hàm cửa sổ hỗ trợ của MySQL (còn gọi là Hàm phân tích). Nó không, và chúng ta bị bỏ lại xung quanh GROUP_CONCAT.


Cảm ơn - một câu trả lời khác cũng có hiệu quả, nhưng cũng chậm như những câu khác .. có vẻ hơi hack, nhưng suy nghĩ tốt :)
Asgrim

Bạn có phím nào trên bàn đó? một KEY (entity_id, date) sẽ làm tốt cho truy vấn của tôi.
Shlomi Noach

Như tôi đã mô tả trong các nhận xét trong câu trả lời của @ RolandoMySQLDBA, việc thêm khóa ghép không tạo ra sự khác biệt nào. Vấn đề cuối cùng là lượng dữ liệu khổng lồ trong trường văn bản trong cột nhận xét có nghĩa là có quá nhiều tìm kiếm đĩa đang diễn ra. Tôi thích truy vấn con hơn là sử dụng hack GROUP_CONCAT, chỉ vậy thôi.
Asgrim
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.