Bài tập: đếm theo trạng thái
Bạn sẽ học được
- Ghép
GROUP BY, hàm tổng hợp vàORDER BYthành một câu báo cáo hoàn chỉnh. - Đọc đề để xác định đúng cột, đúng tên cột và đúng thứ tự.
- Hiểu cách bài chấm so sánh kết quả gom nhóm.
- Chẩn đoán ba lỗi phổ biến nhất khi làm bài đếm theo nhóm.
- Tự kiểm tra kết quả bằng phép đối chiếu tổng.
Khái niệm cốt lõi
Đề bài Đếm đơn theo trạng thái:
Đếm số đơn hàng theo từng
status, đặt tên cột đếm làtotal; sắp xếp theostatus.
Checklist bốn thành phần:
| Câu hỏi | Trả lời | Mệnh đề |
|---|---|---|
| Bảng nào? | orders | FROM orders |
| Cột nào? | status, và COUNT(*) AS total | SELECT status, COUNT(*) AS total |
| Nhóm theo gì? | status | GROUP BY status |
| Thứ tự? | status tăng dần | ORDER BY status |
Không có điều kiện lọc, nên không có WHERE và không có HAVING.
Đáp án:
SELECT status, COUNT(*) AS total
FROM orders
GROUP BY status
ORDER BY status;Kết quả:
| status | total |
|---|---|
| cancelled | 1 |
| completed | 13 |
| pending | 2 |
Ba dòng, tổng 16 — đúng bằng tổng số đơn. Phép đối chiếu này là bước tự kiểm tra quan trọng nhất: tổng các nhóm phải bằng tổng bảng khi không có WHERE lọc mất dòng. Nếu bạn ra tổng khác 16, lỗi nằm ở phép đếm chứ không phải ở cách trình bày.
Đề nói "đặt tên cột đếm là total" — đây không phải chi tiết trang trí. Bài chấm so sánh theo tên cột, nên COUNT(*) AS so_don hay COUNT(*) trần đều bị coi là sai dù con số đúng. Hãy đọc đề theo kiểu gạch chân từng yêu cầu: cột nào, tên gì, sắp xếp theo gì.
Thứ tự cột trong SELECT cũng do đề quyết định: status trước, total sau. Một số bài chấm so khớp theo vị trí cột; đảo SELECT total, status có thể bị báo sai dù dữ liệu đúng.
Vì sao ORDER BY status cho thứ tự cancelled, completed, pending? Đó là thứ tự alphabet của chuỗi theo collation mặc định, không phải thứ tự nghiệp vụ nào. Nếu đề muốn "hoàn tất trước, chờ sau, huỷ cuối", sẽ phải dùng CASE hoặc một cột ưu tiên — chương này chưa học, và đề hiện tại không yêu cầu.
GROUP BY status gom ba nhóm vì bảng có ba giá trị status khác nhau. Nếu ngày mai có thêm trạng thái refunded, truy vấn tự động trả bốn nhóm — đây là ưu điểm của việc gom theo dữ liệu thay vì liệt kê cứng từng trạng thái bằng ba câu SELECT riêng.
Ví dụ chạy được
Trước khi viết đáp án, kiểm tra phân bố thô:
SELECT status FROM orders ORDER BY status;Mười sáu dòng, nhóm lại thành 1 cancelled, 13 completed, 2 pending. Việc nhìn dữ liệu thô giúp bạn chắc rằng không có giá trị lạ (ví dụ Completed viết hoa, hay khoảng trắng thừa) tạo nhóm thứ tư không mong muốn.
Sau đó chạy bản không sắp xếp:
SELECT status, COUNT(*) AS total
FROM orders
GROUP BY status;Ba dòng với con số đúng nhưng thứ tự không đảm bảo. PostgreSQL thường trả theo thứ tự nhóm nội bộ (hash aggregate có thể cho thứ tự bất kỳ), nên đừng dựa vào đó. Thêm ORDER BY status để kết quả xác định — đây chính là yêu cầu cuối của đề.
Ba lần thử sai thường gặp:
Lần 1 — quên GROUP BY:
SELECT status, COUNT(*) AS total FROM orders;Lỗi column "orders.status" must appear in the GROUP BY clause. Đây là lỗi cú pháp, dễ sửa: thêm GROUP BY status.
Lần 2 — đếm sai đơn vị:
SELECT o.status, COUNT(*) AS total
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
GROUP BY o.status
ORDER BY o.status;Câu này không báo lỗi nhưng cho completed 26, pending 2, cancelled 1 — vì COUNT(*) đếm dòng hàng, không đếm đơn. Đề chỉ hỏi bảng orders; việc tự ý nối thêm bảng là sai phạm vi. Nếu buộc phải nối, dùng COUNT(DISTINCT o.id).
Lần 3 — sai tên cột:
SELECT status, COUNT(*) AS so_don
FROM orders
GROUP BY status
ORDER BY status;Con số đúng, tên cột sai. Bài chấm báo sai cột. Cách sửa: đổi bí danh thành total.
Một lần thử khác đáng lưu ý — liệt kê cứng bằng ba câu UNION:
SELECT 'completed' AS status, COUNT(*) AS total FROM orders WHERE status = 'completed'
UNION ALL
SELECT 'pending', COUNT(*) FROM orders WHERE status = 'pending'
UNION ALL
SELECT 'cancelled', COUNT(*) FROM orders WHERE status = 'cancelled'
ORDER BY status;Kết quả đúng trên dữ liệu hiện tại, nhưng sai về bản chất: khi bộ dữ liệu ẩn có thêm trạng thái mới, câu này bỏ sót. GROUP BY tự thích nghi với mọi giá trị có trong dữ liệu — đó là lý do nó là công cụ đúng cho câu hỏi "theo từng".
Bộ dữ liệu ẩn của bài này có thêm đơn hàng so với bộ hiển thị, nên các con số sẽ khác. Đừng hardcode 13, 2, 1; hãy viết quy tắc để SQL tự đếm. Bài chấm chạy đúng câu lệnh của bạn trên cả hai bộ dữ liệu và so sánh kết quả — một câu lệnh đúng quy tắc sẽ đúng ở cả hai nơi.
Thực hành
- Nộp đáp án cho bài Đếm đơn theo trạng thái (
count-orders-by-status). - Viết biến thể đếm số đơn theo từng khách, đặt tên cột
total, sắp theototal DESCrồicustomer_id. Khách có nhiều đơn nhất là Bình Trần với 3. - Viết biến thể đếm theo
statusnhưng chỉ tính đơn cóorder_datetrong năm 2025. Vì mọi đơn đều thuộc 2025, kết quả phải giống đáp án — dùng phép so sánh này để kiểm tra điều kiện ngày viết đúng. - Thêm cột
SUM(quantity * unit_price)vào đáp án bằng cách nốiorder_items. Nhớ dùngCOUNT(DISTINCT o.id)cho cộttotal, nếu không số đơn sẽ bị thổi phồng.
Lỗi thường gặp
| Triệu chứng | Nguyên nhân | Cách sửa |
|---|---|---|
must appear in the GROUP BY clause | Thiếu GROUP BY | Thêm GROUP BY status |
| Số đơn lớn hơn thực tế | JOIN rồi COUNT(*) | Chỉ dùng bảng orders, hoặc COUNT(DISTINCT o.id) |
| Chấm báo sai cột | Bí danh không phải total | COUNT(*) AS total |
| Chấm báo sai thứ tự | Thiếu ORDER BY status | Thêm mệnh đề sắp xếp |
| Bộ hiển thị đúng, bộ ẩn sai | Hardcode giá trị hoặc liệt kê cứng trạng thái | Viết quy tắc bằng GROUP BY |
Lỗi cuối cùng là lỗi khó thấy nhất vì màn hình không báo gì. Cách tự kiểm tra: hỏi "nếu dữ liệu có thêm một trạng thái mới, câu lệnh của mình có tự trả thêm dòng không?". Nếu câu trả lời là không, bạn đã viết sai quy tắc.
Tóm tắt
- Báo cáo theo nhóm =
SELECT nhom, COUNT(*) AS total+GROUP BY nhom+ORDER BY. - Đọc đề để lấy đúng tên cột và thứ tự cột — bài chấm kiểm tra cả hai.
- Tổng các nhóm phải bằng tổng bảng khi không có
WHERE. - Không nối thêm bảng khi đề chỉ hỏi một bảng; nếu nối, dùng
COUNT(DISTINCT id). GROUP BYtự thích nghi với dữ liệu mới; liệt kê cứng thì không.
Bạn đã hoàn thành chương hàm tổng hợp và GROUP BY. Chương tiếp theo chuyển sang JOIN — nơi dữ liệu từ nhiều bảng được kết hợp để trả lời những câu hỏi không bảng đơn nào trả lời được.
Dùng ← → để chuyển bài