GROUP BY gộp các hàng thành từng nhóm
Các hàm tổng hợp như COUNT, SUM và AVG thu gọn nhiều hàng thành một con số. GROUP BY cho phép bạn làm điều đó theo từng loại: một con số cho mỗi khách hàng, mỗi tháng, mỗi trạng thái. Mỗi giá trị riêng biệt (hoặc tổ hợp giá trị) trở thành một hàng trong kết quả.
Ba khách hàng, ba hàng kết quả. Sáu hàng ban đầu đã biến mất: chúng được gộp thành các nhóm theo khách hàng, với COUNT(*) và SUM(amount) được tính trong từng nhóm.
Cách hình dung: GROUP BY customer nghĩa là "coi mọi hàng có cùng khách hàng là một nhóm". Sau đó các hàm tổng hợp chạy riêng trên từng nhóm.
Được đặt gì trong danh sách SELECT
Đây là chỗ nhiều người vấp. Khi dùng GROUP BY, mọi cột trong danh sách SELECT phải nằm trong mệnh đề GROUP BY hoặc nằm bên trong một hàm tổng hợp. Nếu không, giá trị sẽ mơ hồ: nó nên lấy từ hàng nào trong nhóm?
Nếu bạn viết SELECT region, rep, SUM(amount) với GROUP BY region, SQLite vẫn vui vẻ chạy (nó dễ dãi ở chỗ các cơ sở dữ liệu khác sẽ từ chối), nhưng rep sẽ được chọn tùy ý từ nhóm. Bạn nhận được một tên nhân viên cho mỗi khu vực mà không biết chắc là ai. Đừng dựa vào điều đó: hãy gộp nhóm theo mọi cột không tổng hợp mà bạn hiển thị.
HAVING lọc các nhóm sau khi tổng hợp
WHERE lọc các hàng trước khi gộp nhóm. HAVING lọc các nhóm sau khi gộp nhóm. Đó là toàn bộ sự khác biệt, và cũng là lý do bạn không thể đặt COUNT(*) > 1 trong mệnh đề WHERE: lúc WHERE chạy, số đếm đó chưa tồn tại.
Cleo chỉ đặt một đơn hàng, nên nhóm của cô bị loại. Ada và Boris được giữ lại. Điều kiện được áp dụng trên giá trị tổng hợp của từng nhóm, không phải trên từng hàng.
Bạn có thể dùng trực tiếp bí danh cột từ danh sách SELECT trong HAVING, SQLite cho phép điều đó:
Cách này thường dễ đọc hơn việc lặp lại SUM(amount) trong mệnh đề HAVING.
WHERE và HAVING: dùng cả hai cùng lúc
Hai mệnh đề này không loại trừ nhau. WHERE thu hẹp những hàng tham gia gộp nhóm; HAVING thu hẹp những nhóm xuất hiện trong kết quả. Hầu hết truy vấn thực tế dùng cả hai.
Đọc từ trên xuống theo thứ tự thực thi:
WHERE status = 'paid': loại bỏ hoàn toàn các hàng đã hoàn tiền.GROUP BY customer: gộp phần còn lại theo khách hàng.SUM(amount)chạy trên từng nhóm.HAVING SUM(amount) > 75: chỉ giữ các nhóm thỏa điều kiện.
Boris (80 + 20 = 100) và Cleo (200) được giữ lại. Đơn hàng đã thanh toán duy nhất của Ada là 50, không đạt ngưỡng.
Nhiều điều kiện và nhiều cột gộp nhóm
HAVING nhận các toán tử logic giống WHERE (AND, OR, NOT), và bạn có thể gộp nhóm theo nhiều cột để có các nhóm con:
Mỗi cặp (region, quarter) là một nhóm riêng. Mệnh đề HAVING yêu cầu tổng lớn hơn 100 và có ít nhất hai giao dịch. Chỉ ('North', 'Q1') và ('South', 'Q2') đạt yêu cầu.
Một mẫu thực tế: tìm giá trị trùng lặp
Truy vấn GROUP BY ... HAVING COUNT(*) > 1 là cách chuẩn để tìm các giá trị trùng lặp trong một cột:
Hai giá trị trùng lặp hiện ra. Từ đây bạn thường sẽ quyết định gộp tài khoản, thêm ràng buộc UNIQUE hay dọn dẹp dữ liệu, nhưng truy vấn để phát hiện thì lần nào cũng có cùng dạng.
HAVING không có GROUP BY
Cách này hiếm gặp nhưng hợp lệ. Không có GROUP BY, toàn bộ tập kết quả được coi là một nhóm duy nhất, và HAVING lọc nó như một khối: bạn nhận được tất cả giá trị tổng hợp hoặc không nhận được gì:
Hàng kết quả duy nhất xuất hiện vì tổng là 160. Đổi ngưỡng thành > 200 và truy vấn sẽ không trả về hàng nào. Trên thực tế, bạn gần như luôn dùng HAVING cùng GROUP BY, nhưng nên biết rằng ngôn ngữ không bắt buộc điều đó.
Tóm tắt nhanh
GROUP BYgộp các hàng thành từng nhóm theo khóa; các hàm tổng hợp chạy bên trong mỗi nhóm.- Mọi cột không tổng hợp trong
SELECTnên xuất hiện trongGROUP BY. WHERElọc hàng trước khi gộp nhóm;HAVINGlọc nhóm sau đó.- Các hàm tổng hợp như
COUNT(*)vàSUM(...)thuộc vềHAVING, không bao giờ thuộc vềWHERE. HAVINGnhận điều kiện kết hợp và có thể dùng bí danh từSELECT.
Tiếp theo: khóa ngoại
Tổng hợp trên một bảng đã hữu ích, nhưng hầu hết cấu trúc dữ liệu thực tế trải dữ liệu ra nhiều bảng: đơn hàng ở đây, khách hàng ở kia, sản phẩm ở một nơi khác. Khóa ngoại là cách bạn nối các bảng đó với nhau để các mối quan hệ luôn nhất quán. Đó là nội dung của chương tiếp theo.
Câu hỏi thường gặp
WHERE và HAVING trong SQLite khác nhau thế nào?
WHERE lọc từng hàng trước khi gộp nhóm. HAVING lọc các nhóm sau khi tổng hợp. Vì vậy WHERE amount > 100 chỉ giữ các hàng lớn hơn 100, còn HAVING SUM(amount) > 100 chỉ giữ các nhóm có tổng lớn hơn 100. Các hàm tổng hợp như COUNT hay SUM không được dùng trong WHERE; đó là việc của HAVING.
Có thể dùng HAVING mà không có GROUP BY trong SQLite không?
Có. Không có GROUP BY, SQLite coi toàn bộ tập kết quả là một nhóm duy nhất, và HAVING lọc nhóm đó như một khối. Truy vấn sẽ trả về một hàng hoặc không hàng nào. Trên thực tế cách này hiếm gặp: thường khi có HAVING thì cũng có GROUP BY đi kèm.
Làm sao lọc các nhóm theo COUNT trong SQLite?
Đặt hàm tổng hợp trong HAVING, không phải WHERE. Ví dụ, SELECT customer_id, COUNT(*) FROM orders GROUP BY customer_id HAVING COUNT(*) > 1 trả về những khách hàng có nhiều hơn một đơn hàng. Trong SQLite, bạn cũng có thể dùng bí danh cột từ danh sách SELECT bên trong HAVING.