Tính tổng với SUM
Bạn sẽ học được
- Cộng giá trị một cột bằng
SUM. - Cộng biểu thức — như
quantity * unit_price— để tính doanh thu. - Biết
SUMbỏ quaNULLvà trảNULLkhi không có dòng nào. - Kết hợp
SUMvớiWHEREđể tính tổng theo điều kiện. - Phân biệt
SUMvớiCOUNTvà chọn đúng hàm cho câu hỏi.
Khái niệm cốt lõi
SUM(cot) cộng mọi giá trị không NULL trong cột và trả về một số:
SELECT SUM(price) AS tong_gia_tri_san_pham
FROM products;Chín sản phẩm có giá: 890.000 + 1.250.000 + 1.690.000 + 320.000 + 250.000 + 180.000 + 450.000 + 290.000 + 390.000. Kết quả: 5.710.000. Con số này là "tổng giá niêm yết", không phải doanh thu — nó không quan tâm sản phẩm đã bán hay chưa.
Hai đặc điểm quan trọng nhất của SUM:
- Bỏ qua
NULL. Nếu một dòng cópricelàNULL, nó không được cộng và cũng không làm hỏng kết quả. Đây là hành vi khác với phép cộng thông thường trong nhiều ngôn ngữ lập trình, nơi1 + NULLchoNULL. - Không có dòng nào thì trả
NULL, không phải 0.SELECT SUM(price) FROM products WHERE price > 5000000;trả một dòng với giá trịNULL. Khi hiển thị cho người dùng, hãy bọcCOALESCE(SUM(...), 0).
SUM nhận cả biểu thức, không chỉ tên cột. Phép tính phổ biến nhất trong dữ liệu bán hàng là doanh thu:
SELECT SUM(quantity * unit_price) AS doanh_thu
FROM order_items;Mỗi dòng trong order_items là một sản phẩm trong một đơn: số lượng nhân đơn giá. Cộng 29 dòng lại cho tổng doanh thu 31.230.000. Đây là con số quan trọng nhất của cả chương: nó sẽ xuất hiện lại khi tính doanh thu theo trạng thái, theo khách, theo tháng.
Lưu ý vì sao phải nhân trước rồi cộng: SUM(quantity) * SUM(unit_price) là một phép tính hoàn toàn khác và vô nghĩa. Phép nhân và phép cộng không giao hoán theo cách đó. Hãy đọc công thức bằng lời: "với mỗi dòng, tính thành tiền; sau đó cộng các thành tiền". Đó chính là SUM(quantity * unit_price).
SUM kết hợp WHERE cho tổng theo điều kiện:
SELECT SUM(quantity * unit_price) AS doanh_thu_hoan_tat
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
WHERE o.status = 'completed';Kết quả 29.050.000. Chênh lệch so với tổng 31.230.000 là 2.180.000, gồm 1.220.000 của hai đơn pending và 960.000 của một đơn cancelled. Kiểm tra chéo này rất hữu ích: ba phần phải cộng lại đúng bằng tổng.
Bí danh AS nên đặt ở mọi cột tổng hợp. Không có bí danh, tên cột là sum, khó đọc và dễ trùng. Trong bài luyện, đề có thể yêu cầu tên cột cụ thể như total hay doanh_thu; đọc đề trước khi viết.
Kiểu dữ liệu: unit_price là numeric(12,2), nên SUM trả numeric chính xác — không có sai số dấu phẩy động như khi cộng số thực (float). Nếu cột là integer, SUM trả bigint. Đây là lý do nên dùng numeric cho tiền: cộng một triệu dòng vẫn chính xác đến từng đồng.
Một bẫy khác: SUM sau LEFT JOIN có thể nhân đôi giá trị nếu bảng phải có nhiều dòng khớp. Ví dụ orders JOIN order_items — một đơn có ba món tạo ba dòng. Nếu bạn SUM(o.id) trong truy vấn đó, mỗi id bị cộng ba lần. Nguyên tắc: chỉ SUM cột thuộc bảng mà mỗi dòng kết quả đại diện đúng một lần, hoặc chấp nhận trùng và tính theo hạt (grain) bạn muốn.
Ví dụ chạy được
Tổng giá trị hàng tồn kho — số lượng nhân đơn giá, cộng toàn bộ:
SELECT SUM(price * stock) AS gia_tri_ton_kho
FROM products;Kết quả: 72.910.000. Sản phẩm đóng góp nhiều nhất là Tai nghe (22.250.000), sau đó Bàn phím (12.500.000) và Áo thun (10.000.000). Bình giữ nhiệt có stock = 0 nên đóng góp 0 dù giá khác 0 — đây là ví dụ cho thấy SUM cộng cả số 0, và kết quả phụ thuộc vào từng dòng chứ không chỉ danh sách sản phẩm.
Tổng số lượng sản phẩm đã bán:
SELECT SUM(quantity) AS tong_so_luong
FROM order_items;Kết quả: 54. Sách SQL bán nhiều nhất với 11 cuốn, Áo thun 9, Tai nghe 7. Con số này khác với COUNT(*) = 29 — 29 là số dòng đặt hàng, 54 là số sản phẩm. Hai câu hỏi khác nhau, hai hàm khác nhau.
Doanh thu theo trạng thái đơn:
SELECT o.status, SUM(oi.quantity * oi.unit_price) AS doanh_thu
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
GROUP BY o.status
ORDER BY doanh_thu DESC;Ba dòng: completed 29.050.000; pending 1.220.000; cancelled 960.000. Nếu bạn chỉ muốn xem doanh thu thật, lọc WHERE o.status = 'completed' trước khi gom nhóm. Lưu ý ORDER BY doanh_thu dùng được bí danh vì ORDER BY chạy sau SELECT.
Doanh thu của một khách cụ thể, nối hai bảng:
SELECT SUM(oi.quantity * oi.unit_price) AS doanh_thu
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
WHERE o.customer_id = 2;Kết quả: 6.750.000. Bình Trần có ba đơn: 1.750.000 + 2.950.000 + 2.050.000. Đây là mẫu truy vấn bạn sẽ dùng lại khi xếp hạng khách hàng ở chương sau.
So sánh SUM và COUNT trên cùng dữ liệu:
SELECT COUNT(*) AS so_dong,
SUM(quantity) AS tong_so_luong,
SUM(quantity * unit_price) AS doanh_thu
FROM order_items;Một dòng: 29 | 54 | 31230000. Ba cột trả lời ba câu hỏi: có bao nhiêu dòng, có bao nhiêu sản phẩm, và tổng tiền là bao nhiêu. Chọn đúng hàm là một nửa của việc phân tích đúng.
Cuối cùng, COALESCE cho báo cáo:
SELECT COALESCE(SUM(quantity * unit_price), 0) AS doanh_thu
FROM order_items
WHERE quantity > 10;Kết quả: 0, không phải ô trống. Không đơn nào có hơn 10 sản phẩm cùng loại. Nếu hiển thị trên dashboard, số 0 dễ đọc hơn NULL — nhưng chỉ dùng COALESCE khi 0 là câu trả lời đúng về mặt nghiệp vụ, đừng che mất dấu hiệu thiếu dữ liệu.
Thực hành
- Tính tổng giá trị đơn hàng 13 (một đơn của Minh Dương). Đáp án phải là 4.030.000 — kiểm tra bằng cách cộng tay ba dòng của đơn.
- Tính doanh thu của khách 6 (Giang Đặng) chỉ với đơn
completed. Hai đơn của khách đều completed, tổng 5.490.000. - Tính doanh thu theo từng
categorybằng cách nốiorder_itemsvớiproducts. Nhóm Gia dụng nhiều nhất; ghi lại thứ hạng các nhóm. - Chạy
SUMvới điều kiện không khớp dòng nào, có và không cóCOALESCE. So sánh kết quả và quyết định khi nào dùng cách nào.
Lỗi thường gặp
| Triệu chứng | Nguyên nhân | Cách sửa |
|---|---|---|
Kết quả NULL dù có dữ liệu | Điều kiện WHERE không khớp dòng nào | Kiểm tra điều kiện; bọc COALESCE khi hiển thị |
| Doanh thu lớn bất thường | SUM sau JOIN một-nhiều bị nhân bản | Tính theo đúng hạt hoặc dùng truy vấn con |
| Số lẻ khó hiểu | Cột giá là float | Dùng numeric cho tiền |
SUM(quantity) * SUM(price) ra số vô lý | Nhân hai tổng thay vì tổng của tích | SUM(quantity * price) |
| Tổng không khớp báo cáo khác | Một bên lọc status, bên kia không | Thống nhất định nghĩa trước khi so sánh |
Lỗi "doanh thu lớn bất thường" đáng nhớ nhất: nó không báo lỗi, chỉ cho số sai. Cách phát hiện là so tổng doanh thu tính từ order_items (31.230.000) với tổng tính từ một đường khác, ví dụ cộng tay theo từng đơn. Nếu hai đường lệch nhau đúng bội số, bạn đã JOIN trùng.
Tóm tắt
SUM(cot)cộng giá trị, bỏ quaNULL; không có dòng nào thì trảNULL.- Doanh thu =
SUM(quantity * unit_price), không phải tích của haiSUM. WHEREđặt trước khi cộng để lọc phạm vi;COALESCEđể hiển thị 0.SUMvàCOUNTtrả lời hai câu hỏi khác nhau — chọn theo câu hỏi.- Cảnh giác với
SUMsauJOINmột-nhiều: giá trị có thể bị đếm trùng.
Use ← → to move between lessons