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;🧠 Quiz
Câu 1: Sự khác biệt chính giữa WHERE và HAVING là gì?
- [ ] A) WHERE dùng cho số, HAVING dùng cho chuỗi
- [ ] B) Không khác nhau, dùng thay thế được
- [x] C) WHERE lọc dòng trước khi GROUP BY, HAVING lọc nhóm sau khi GROUP BY
- [ ] D) HAVING nhanh hơn WHERE
💡 Giải thích: WHERE thực thi trước GROUP BY — lọc từng dòng riêng lẻ. HAVING thực thi sau GROUP BY — lọc trên kết quả gom nhóm (aggregate). Ví dụ:
HAVING COUNT(*) > 5lọc nhóm có hơn 5 dòng.
Câu 2: Cột nào được phép xuất hiện trong SELECT khi dùng GROUP BY?
- [ ] A) Mọi cột trong bảng
- [x] B) Chỉ cột trong GROUP BY hoặc cột bên trong hàm aggregate (COUNT, SUM, AVG...)
- [ ] C) Chỉ cột trong GROUP BY
- [ ] D) Chỉ cột có kiểu số
💡 Giải thích: Đây là "quy tắc SELECT list": mỗi cột trong SELECT phải nằm trong GROUP BY hoặc được bọc trong aggregate function. Lý do: khi gom nhóm, database không biết chọn giá trị nào cho cột không được nhóm.
Câu 3: COUNT(column) khác COUNT(*) thế nào?
- [ ] A) Hoàn toàn giống nhau
- [ ] B)
COUNT(*)chậm hơn - [x] C)
COUNT(column)bỏ qua NULL,COUNT(*)đếm tất cả dòng kể cả NULL - [ ] D)
COUNT(column)đếm distinct values
💡 Giải thích:
COUNT(*)đếm mọi dòng, không quan tâm giá trị.COUNT(column)chỉ đếm dòng mà column đó NOT NULL. Nếu muốn đếm giá trị distinct: dùngCOUNT(DISTINCT column).