Lập trình · 25/09/2026

SQL WHERE và HAVING: Lọc đúng trước và sau khi gom nhóm

Một báo cáo cần tìm khách hàng có tổng giá trị đơn đã thanh toán từ 100 trở lên. Nếu lọc từng đơn có giá trị từ 100 trước khi cộng, bạn đang trả lời một câu hỏi khác. WHERE và HAVING giúp đặt điều kiện ở hai cấp: dòng đầu vào và nhóm kết quả.

SQL WHERE và HAVING: Lọc đúng trước và sau khi gom nhóm

Một báo cáo cần tìm khách hàng có tổng giá trị đơn đã thanh toán từ 100 trở lên. Nếu lọc từng đơn có giá trị từ 100 trước khi cộng, bạn đang trả lời một câu hỏi khác. WHERE và HAVING giúp đặt điều kiện ở hai cấp: dòng đầu vào và nhóm kết quả.

1. Viết yêu cầu trước khi viết SQL

Yêu cầu minh họa gồm ba phần: chỉ tính đơn paid, cộng amount theo customer_id, rồi giữ khách hàng có tổng ít nhất 100. Giả sử amount không NULL, mọi đơn dùng cùng đơn vị tiền và mỗi dòng là một đơn độc lập. Dữ liệu không đại diện cho một hệ thống thanh toán thực tế.

Hướng dẫn hàm tổng hợp của PostgreSQL phân biệt WHERE lọc dòng trước khi gom nhóm và tính tổng hợp, còn HAVING lọc các nhóm sau bước đó. Đây là mô hình logic để hiểu truy vấn, không phải cam kết về thứ tự thực thi vật lý của bộ tối ưu.

2. Ví dụ chạy độc lập trên PostgreSQL

WITH orders(customer_id, status, amount) AS (
    VALUES
        (1, 'paid', 60),
        (1, 'paid', 50),
        (1, 'pending', 500),
        (2, 'paid', 90),
        (3, 'paid', 150)
)
SELECT customer_id, SUM(amount) AS paid_total
FROM orders
WHERE status = 'paid'
GROUP BY customer_id
HAVING SUM(amount) >= 100
ORDER BY customer_id;

Kết quả mong đợi là khách hàng 1 với tổng 110 và khách hàng 3 với tổng 150. Đơn pending 500 không được cộng; khách hàng 2 bị loại vì tổng 90. Truy vấn dùng dữ liệu VALUES và không tạo hoặc sửa bảng.

3. Một điều kiện tưởng tương đương nhưng làm đổi kết quả

Nếu thay phần lọc bằng WHERE status = 'paid' AND amount >= 100, hai đơn 60 và 50 của khách hàng 1 bị loại trước khi cộng. Khách hàng này biến mất dù tổng hợp lệ là 110.

Hai câu hỏi khác nhau là “có các đơn riêng lẻ đủ lớn” và “tổng các đơn đủ lớn”. Khi review SQL, đọc lại điều kiện bằng ngôn ngữ nghiệp vụ sẽ dễ phát hiện lỗi hơn việc chỉ nhìn truy vấn có chạy thành công hay không.

4. Chọn khóa nhóm ổn định

Ví dụ dùng customer_id, không gom theo tên hiển thị. Hai khách hàng cùng tên không nên tự trở thành một nhóm. Nếu dữ liệu đến từ nhiều tenant và mã khách hàng chỉ duy nhất trong từng tenant, cần xác định phạm vi và khóa nhóm tương ứng.

Trước khi bổ sung JOIN để lấy tên khách hàng, kiểm tra quan hệ có làm tăng số dòng đầu vào hay không. JOIN mỗi đơn với nhiều dòng chi tiết rồi cộng lại amount của đơn có thể nhân tổng lên. HAVING không tự sửa được lỗi đó; cần xác định đúng cấp dữ liệu trước khi tổng hợp.

5. Khoảng thời gian và đơn vị là một phần của yêu cầu

Trong dự án thật, báo cáo thường chỉ tính một kỳ cụ thể. Điều kiện thời gian của từng đơn thuộc bộ lọc đầu vào khi yêu cầu là cộng các đơn trong kỳ. Xác định rõ múi giờ, mốc bắt đầu và mốc kết thúc, thay vì thêm bộ lọc ngày theo cảm tính sau khi báo cáo đã chạy.

Không cộng lẫn các loại tiền hoặc đơn vị khác nhau chỉ vì cùng nằm trong cột amount. Chính sách quy đổi, xử lý hoàn tiền và trạng thái hợp lệ phải được thống nhất với người sở hữu nghiệp vụ. Truy vấn mẫu không đưa ra những chính sách đó thay cho dự án.

6. Bộ kiểm thử nhỏ trước khi tối ưu

  • Hai đơn nhỏ cộng lại vừa đủ ngưỡng.
  • Một đơn lớn nhưng sai trạng thái.
  • Tổng thấp hơn, bằng và cao hơn ngưỡng.
  • Khách hàng không có đơn phù hợp.
  • Dữ liệu có số âm hoặc NULL nếu mô hình cho phép, với kỳ vọng được xác định rõ.
  • Trường hợp JOIN có nhiều dòng liên quan để phát hiện đếm hoặc cộng lặp.

Đối chiếu tổng bằng một tập dữ liệu nhỏ có thể tính thủ công, rồi mới đánh giá kế hoạch thực thi và chỉ mục. Không tối ưu một truy vấn trước khi xác nhận nó đang trả lời đúng câu hỏi.

Quy tắc dễ nhớ: WHERE quyết định dòng nào được đưa vào phép tính; HAVING quyết định nhóm nào được giữ lại. Kết hợp quy tắc này với khóa nhóm và cấp dữ liệu đúng sẽ giúp báo cáo đáng tin cậy hơn.

Thảo luận

Bình luận 0

Đăng nhập để bình luận

Bạn cần có tài khoản để tham gia thảo luận và trả lời độc giả khác.

Đăng nhậpĐăng ký

Chưa có bình luận. Hãy là người đầu tiên chia sẻ ý kiến.