Giao diện
GROUP BY & HAVING: Gom nhóm dữ liệu
GROUP BY cho phép bạn gom các dòng dữ liệu giống nhau lại thành một nhóm để tính toán các con số thống kê (Aggregation) như: Tổng (SUM), Trung bình (AVG), Đếm (COUNT), Cao nhất (MAX), Thấp nhất (MIN).
Concept: Quy trình "Split-Apply-Combine"
- Split: Chia dữ liệu thành các nhóm dựa trên key.
- Apply: Áp dụng hàm aggregate (SUM, COUNT) cho từng nhóm.
- Combine: Trả về kết quả là mỗi nhóm 1 dòng.
Phân biệt WHERE và HAVING
Đây là câu hỏi phỏng vấn kinh điển.
| Đặc điểm | WHERE | HAVING |
|---|---|---|
| Thời điểm chạy | Chạy TRƯỚC khi gom nhóm (GROUP BY). | Chạy SAU khi đã gom nhóm. |
| Dữ liệu lọc | Lọc từng dòng (Row) riêng lẻ. | Lọc kết quả của các nhóm (Aggregate). |
| Sử dụng Aggregate? | ❌ KHÔNG thể dùng SUM(), COUNT()... | ✅ BẮT BUỘC dùng để lọc theo Aggregate. |
Ví dụ thực tế (Real-world Example)
Yêu cầu: Tìm những khách hàng "VIP" - đã chi tiêu tổng cộng trên 10 triệu đồng.
sql
SELECT
user_id,
SUM(total_amount) as total_spent
FROM orders
WHERE status = 'completed' -- 1. Lọc đơn hàng thành công TRƯỚC
GROUP BY user_id -- 2. Gom nhóm theo User
HAVING SUM(total_amount) > 10000000; -- 3. Lọc ra User chi nhiều tiền SAU khi tính tổng💡 HPN Pro Tip: Performance Thinking
Luôn cố gắng lọc dữ liệu bằng WHERE nhiều nhất có thể để giảm tải cho bước GROUP BY. Ví dụ: Thay vì Group By cả triệu dòng rồi mới HAVING, hãy WHERE theo năm (year = 2024) để chỉ Group By vài nghìn dòng.
⚠️ Common Mistake: "The SELECT List Rule"
Khi dùng GROUP BY, bạn chỉ được phép SELECT:
- Các cột nằm trong
GROUP BY. - Các hàm Aggregate (
SUM,COUNT,MAX...).
Sai:
sql
SELECT user_id, full_name, SUM(total_amount) -- full_name ở đâu ra??
FROM orders
GROUP BY user_id;Nếu một user_id có nhiều full_name (ít khả năng, nhưng về lý thuyết database không biết lấy dòng nào), câu lệnh này sẽ lỗi trên Postgres/SQL Server (MySQL ở chế độ lỏng lẻo có thể chạy nhưng trả về kết quả ngẫu nhiên - rất nguy hiểm).
Đúng: Thêm full_name vào GROUP BY hoặc dùng hàm Aggregate giả (MAX(full_name)).
sql
SELECT user_id, full_name, SUM(total_amount)
FROM orders
GROUP BY user_id, full_name;