MySQL "Nhóm theo" và "Đặt hàng theo"


96

Tôi muốn có thể chọn một loạt các hàng từ một bảng e-mail và nhóm chúng theo người gửi từ. Truy vấn của tôi trông như thế này:

SELECT 
    `timestamp`, `fromEmail`, `subject`
FROM `incomingEmails` 
GROUP BY LOWER(`fromEmail`) 
ORDER BY `timestamp` DESC

Truy vấn gần như hoạt động như tôi muốn - nó chọn các bản ghi được nhóm theo e-mail. Vấn đề là chủ đề và dấu thời gian không tương ứng với bản ghi gần đây nhất cho một địa chỉ e-mail cụ thể.

Ví dụ: nó có thể trả về:

fromEmail: john@example.com, subject: hello
fromEmail: mark@example.com, subject: welcome

Khi các bản ghi trong cơ sở dữ liệu là:

fromEmail: john@example.com, subject: hello
fromEmail: john@example.com, subject: programming question
fromEmail: mark@example.com, subject: welcome

Nếu chủ đề "câu hỏi lập trình" là chủ đề mới nhất, làm cách nào để MySQL chọn bản ghi đó khi nhóm các e-mail?

Câu trả lời:


140

Một giải pháp đơn giản là bọc truy vấn thành một lựa chọn con với câu lệnh ORDER trước và áp dụng GROUP BY sau :

SELECT * FROM ( 
    SELECT `timestamp`, `fromEmail`, `subject`
    FROM `incomingEmails` 
    ORDER BY `timestamp` DESC
) AS tmp_table GROUP BY LOWER(`fromEmail`)

Điều này tương tự như sử dụng phép nối nhưng trông đẹp hơn nhiều.

Việc sử dụng các cột không tổng hợp trong mệnh đề SELECT với mệnh đề GROUP BY là không chuẩn. MySQL thường sẽ trả về các giá trị của hàng đầu tiên mà nó tìm thấy và loại bỏ phần còn lại. Bất kỳ mệnh đề ORDER BY nào sẽ chỉ áp dụng cho giá trị cột được trả về, không áp dụng cho giá trị cột bị loại bỏ.

CẬP NHẬT QUAN TRỌNG Lựa chọn các cột không tổng hợp được sử dụng để làm việc trong thực tế nhưng không nên dựa vào. Theo tài liệu MySQL ", điều này hữu ích chủ yếu khi tất cả các giá trị trong mỗi cột không được tổng hợp không có tên trong GROUP BY là giống nhau cho mỗi nhóm. Máy chủ có thể tự do chọn bất kỳ giá trị nào từ mỗi nhóm, vì vậy trừ khi chúng giống nhau, các giá trị được chọn là không xác định . "

Kể từ ngày 5.7.5 ONLY_FULL_GROUP_BY được bật theo mặc định nên các cột không tổng hợp gây ra lỗi truy vấn (ER_WRONG_FIELD_WITH_GROUP)

Như @mikep đã chỉ ra bên dưới, giải pháp là sử dụng ANY_VALUE () từ 5.7 trở lên

Xem http://www.cafewebmaster.com/mysql-order-sort-group https://dev.mysql.com/doc/refman/5.6/en/group-by-handling.html https://dev.mysql .com / doc / RefMan / 5,7 / en / group-by-handling.html https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_any-value


7
Tôi đã đưa ra cùng một giải pháp cách đây vài năm, và đó là một giải pháp tuyệt vời. kudo cho b7kich. Tuy nhiên, hai vấn đề ở đây ... GROUP BY không phân biệt chữ hoa chữ thường nên LOWER () là không cần thiết và thứ hai, $ userID dường như là một biến trực tiếp từ PHP, mã của bạn có thể dễ bị chèn sql nếu $ userID là do người dùng cung cấp và không bị ép buộc là một số nguyên.
khóa dán

CẬP NHẬT QUAN TRỌNG cũng áp dụng cho MariaDB: mariadb.com/kb/en/mariadb/…
Arthur Shipkowski

1
As of 5.7.5 ONLY_FULL_GROUP_BY is enabled by default, i.e. it's impossible to use non-aggregate columns.Chế độ SQL có thể được thay đổi trong thời gian chạy mà không có đặc quyền của quản trị viên, vì vậy rất dễ vô hiệu hóa ONLY_FULL_GROUP_BY. Ví dụ: SET SESSION sql_mode = '';. Demo: db-fiddle.com/f/esww483qFQXbXzJmkHZ8VT/3
mikep

1
Hoặc một giải pháp thay thế khác cho bỏ qua được bật ONLY_FULL_GROUP_BY là sử dụng ANY_VALUE (). Xem thêm dev.mysql.com/doc/refman/8.0/en/…
mikep

42

Đây là một cách tiếp cận:

SELECT cur.textID, cur.fromEmail, cur.subject, 
     cur.timestamp, cur.read
FROM incomingEmails cur
LEFT JOIN incomingEmails next
    on cur.fromEmail = next.fromEmail
    and cur.timestamp < next.timestamp
WHERE next.timestamp is null
and cur.toUserID = '$userID' 
ORDER BY LOWER(cur.fromEmail)

Về cơ bản, bạn tự tham gia bảng, tìm kiếm các hàng sau đó. Trong mệnh đề where bạn nói rằng không thể có các hàng sau đó. Điều này chỉ cung cấp cho bạn hàng mới nhất.

Nếu có thể có nhiều email có cùng dấu thời gian, truy vấn này sẽ cần được tinh chỉnh. Nếu có cột ID gia tăng trong bảng email, hãy thay đổi THAM GIA như:

LEFT JOIN incomingEmails next
    on cur.fromEmail = next.fromEmail
    and cur.id < next.id

Nói rằng textIDđã mơ hồ = /
John Kurlak

1
Sau đó, xóa ambuigity và đặt tiền tố bằng tên bảng, như cur.textID. Đã thay đổi trong câu trả lời.
Andomar

Đây là giải pháp duy nhất có thể làm với DQL Doctrine.
VisioN

Điều này không hiệu quả khi bạn đang cố gắng tự nối nhiều cột quá tốt. IE khi bạn đang cố gắng tìm email mới nhất và tên người dùng mới nhất và bạn yêu cầu nhiều liên kết tự trái để thực hiện thao tác này trong một truy vấn duy nhất.
Loveen Dyall

Khi làm việc với quá khứ và tương lai timestamps / ngày, để hạn chế resultset đến ngày phi tương lai, bạn cần phải thêm điều kiện khác với LEFT JOINtiêu chíAND next.timestamp <= UNIX_TIMESTAMP()
fyrye

32

Như đã chỉ ra trong một câu trả lời, câu trả lời hiện tại là sai, vì GROUP BY tự ý chọn bản ghi từ cửa sổ.

Nếu một người đang sử dụng MySQL 5.6 hoặc MySQL 5.7 với ONLY_FULL_GROUP_BY, truy vấn chính xác (xác định) là:

SELECT incomingEmails.*
  FROM (
    SELECT fromEmail, MAX(timestamp) `timestamp`
    FROM incomingEmails
    GROUP BY fromEmail
  ) filtered_incomingEmails
  JOIN incomingEmails USING (fromEmail, timestamp)
GROUP BY fromEmail, timestamp

Để truy vấn chạy hiệu quả, cần phải lập chỉ mục thích hợp.

Lưu ý rằng vì mục đích đơn giản hóa, tôi đã xóa dấu LOWER(), mà trong hầu hết các trường hợp, sẽ không được sử dụng.


2
Đây phải là câu trả lời chính xác. Tôi vừa phát hiện ra một lỗi trên trang web của mình liên quan đến điều này. Các order bytrong subselect trong câu trả lời khác, không có tác dụng gì cả.
Jette

1
OMG, vui lòng biến đây thành câu trả lời được chấp nhận. Người được chấp nhận đã lãng phí 5 giờ thời gian của tôi :(
Richard Kersey

29

Thực hiện GROUP BY sau ORDER BY bằng cách kết hợp truy vấn của bạn với GROUP BY như sau:

SELECT t.* FROM (SELECT * FROM table ORDER BY time DESC) t GROUP BY t.from

1
Vậy GROUP BY` tự động chọn cái mới nhất time, hay cái mới nhất time, hay ngẫu nhiên?
xrDDDD

1
Nó chọn thời gian mới nhất vì chúng tôi đang đặt hàng time DESCvà sau đó nhóm của chúng tôi lấy thời gian đầu tiên (mới nhất).
11101101b

Bây giờ, giá mà tôi có thể thực hiện JOINS trên các lựa chọn phụ trong VIEWS, trong mysql 5.1. Có thể tính năng đó xuất hiện trong một bản phát hành mới hơn.
IcarusNM

21

Theo tiêu chuẩn SQL, bạn không thể sử dụng các cột không tổng hợp trong danh sách chọn. MySQL cho phép sử dụng như vậy (không sử dụng chế độ ONLY_FULL_GROUP_BY) nhưng không thể đoán trước được kết quả.

ONLY_FULL_GROUP_BY

Trước tiên, bạn nên chọn từ Email, MIN (đọc), và sau đó, với truy vấn thứ hai (hoặc truy vấn con) - Chủ đề.


MIN (read) sẽ trả về giá trị nhỏ nhất của "read". Có thể anh ấy đang tìm cờ "đã đọc" của email mới nhất để thay thế.
Andomar

2

Tôi đã đấu tranh với cả hai phương pháp này cho các truy vấn phức tạp hơn so với các truy vấn được hiển thị, bởi vì phương pháp truy vấn con rất kém hiệu quả cho dù tôi đặt chỉ mục nào và vì tôi không thể có được tự tham gia bên ngoài thông qua Hibernate

Cách tốt nhất (và dễ nhất) để làm điều này là nhóm theo thứ gì đó được xây dựng để chứa nối các trường bạn yêu cầu và sau đó kéo chúng ra bằng cách sử dụng các biểu thức trong mệnh đề SELECT. Nếu bạn cần thực hiện MAX (), hãy đảm bảo rằng trường bạn muốn thêm MAX () luôn ở cuối quan trọng nhất của thực thể được nối.

Chìa khóa để hiểu điều này là truy vấn chỉ có thể có ý nghĩa nếu các trường khác này là bất biến đối với bất kỳ thực thể nào thỏa mãn Max (), vì vậy về mặt sắp xếp các phần khác của nối có thể bị bỏ qua. Nó giải thích cách thực hiện điều này ở cuối liên kết này. http://dev.mysql.com/doc/refman/5.0/en/group-by-hiised-columns.html

Nếu bạn có thể nhận được sự kiện am insert / update (như một trình kích hoạt) để tính toán trước việc nối các trường, bạn có thể lập chỉ mục nó và truy vấn sẽ nhanh như thể nhóm của chỉ vượt qua trường bạn thực sự muốn MAX ( ). Bạn thậm chí có thể sử dụng nó để nhận tối đa nhiều trường. Tôi sử dụng nó để thực hiện các truy vấn đối với các cây đa chiều được biểu thị dưới dạng các tập hợp lồng nhau.

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.