Skip to content

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"

  1. Split: Chia dữ liệu thành các nhóm dựa trên key.
  2. Apply: Áp dụng hàm aggregate (SUM, COUNT) cho từng nhóm.
  3. 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ểmWHEREHAVING
Thời điểm chạyChạy TRƯỚC khi gom nhóm (GROUP BY).Chạy SAU khi đã gom nhóm.
Dữ liệu lọcLọ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:

  1. Các cột nằm trong GROUP BY.
  2. 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;