Nhớ lại nhé, các hàm tổng hợp là những hàm làm việc với nhiều dòng dữ liệu cùng lúc và trả về một kết quả duy nhất. Trong PostgreSQL, bạn sẽ thường xuyên dùng các hàm tổng hợp sau:
SUM()— tính tổng dữ liệu.AVG()— tính giá trị trung bình.MIN()— tìm giá trị nhỏ nhất.MAX()— tìm giá trị lớn nhất.COUNT()— đếm số dòng.
Nhìn qua thì đơn giản thôi: truyền vào hàm một cột hoặc biểu thức, và nhận kết quả. Nhưng chuyện gì xảy ra nếu trong cột đó có NULL?
Hành vi của NULL trong các hàm tổng hợp: tóm tắt nhanh
Đây mới là phần thú vị nè:
SUM()vàAVG()sẽ bỏ quaNULL. Nếu có dòng nào có giá trịNULL, nó sẽ không được tính vào kết quả. Hợp lý mà, tổng làm sao tăng nếu ai đó "không đến dự tiệc"? Hoặc làm sao tính trung bình nếu thiếu một giá trị?MIN()vàMAX()cũng bỏ quaNULL. Chúng chỉ tìm giá trị nhỏ nhất/lớn nhất trong các dòng không phảiNULL. Vậy nên, khi bạn tìm nhân viên trẻ nhất mà quên điền ngày sinh,NULLsẽ không được chọn đâu.COUNT(*)đếm tất cả các dòng, kể cả dòng cóNULL. NhưngCOUNT(column)chỉ đếm những dòng mà cột đó có giá trị, tức là bỏ quaNULL.
Cùng xem ví dụ cho dễ hiểu nhé.
Ví dụ sử dụng hàm tổng hợp với NULL
Đây là bảng students_scores, chứa điểm kiểm tra của sinh viên:
| student_id | name | score |
|---|---|---|
| 1 | Alysa | 85 |
| 2 | Bob | NULL |
| 3 | Charly | 92 |
| 4 | Dana | NULL |
| 5 | Elena | 74 |
Giờ mình thử vài truy vấn và phân tích kết quả nhé:
- Tổng tất cả điểm:
SUM()
SELECT SUM(score) AS total_score
FROM students_scores;
Kết quả:
| total_score |
|---|
| 251 |
Nhìn nè, các giá trị NULL bị bỏ qua khi tính tổng. Alysa (85), Charly (92) và Elena (74) cộng lại thành 251. Bob và Dana bị loại khỏi cuộc chơi.
- Điểm trung bình:
AVG()
SELECT AVG(score) AS average_score
FROM students_scores;
Kết quả:
| average_score |
|---|
| 83.67 |
Lại nữa, NULL bị bỏ qua, và điểm trung bình chỉ tính cho ai có điểm: (85 + 92 + 74) / 3 = 83.67.
- Điểm nhỏ nhất và lớn nhất:
MIN()vàMAX()
SELECT
MIN(score) AS min_score,
MAX(score) AS max_score
FROM students_scores;
Kết quả:
| min_score | max_score |
|---|---|
| 74 | 92 |
Chỗ này cũng dễ hiểu: NULL lại bị bỏ qua, điểm nhỏ nhất là 74, lớn nhất là 92.
- Đếm dòng:
COUNT(*)vsCOUNT(column)
SELECT
COUNT(*) AS total_rows,
COUNT(score) AS non_null_scores
FROM students_scores;
Kết quả:
| total_rows | non_null_scores |
|---|---|
| 5 | 3 |
COUNT(*)đếm tất cả các dòng, kể cả dòngscorelàNULL.COUNT(score)chỉ đếm dòng có giá trị trong cộtscore.
Case thực tế
Cùng xem vài ví dụ thực tế nhé.
Ví dụ 1: Đếm nhân viên có và không có lương
Giả sử bạn có bảng employees với lương của nhân viên.
| id | name | salary |
|---|---|---|
| 1 | Alex Lin | 50000 |
| 2 | Maria Chi | NULL |
| 3 | Anna Song | 60000 |
| 4 | Otto Art | NULL |
| 5 | Liam Park | 55000 |
Bạn muốn biết có bao nhiêu nhân viên đã khai báo lương, và bao nhiêu người chưa khai báo.
SELECT
COUNT(*) AS total_employees,
COUNT(salary) AS employees_with_salary,
COUNT(*) - COUNT(salary) AS employees_without_salary
FROM employees;
Ở đây:
COUNT(*)trả về tổng số nhân viên.COUNT(salary)đếm số nhân viên đã khai báo lương.- Để tính số nhân viên chưa có lương, chỉ cần lấy tổng trừ đi số đã khai báo.
Kết quả
| total_employees | employees_with_salary | employees_without_salary |
|---|---|---|
| 5 | 3 | 2 |
Ví dụ 2: Tính giá trung bình sản phẩm khi có giá bị thiếu
Bạn là chủ shop phép thuật, bảng products có cột price, nhưng một số sản phẩm chưa có giá.
| id | name | price |
|---|---|---|
| 1 | Magic Wand | 150 |
| 2 | Enchanted Cloak | NULL |
| 3 | Potion Bottle | 75 |
| 4 | Spell Book | 200 |
| 5 | Crystal Ball | NULL |
Bạn cần biết giá trung bình chỉ cho sản phẩm đã có giá.
SELECT AVG(price) AS average_price
FROM products;
Kết quả:
| average_price |
|---|
| 141.6667 |
Nếu bạn muốn đặt giá mặc định cho sản phẩm chưa có giá (ví dụ là 0), có thể dùng hàm COALESCE() (sẽ học ở bài sau).
Ví dụ 3: Tìm tuổi nhỏ nhất và lớn nhất của sinh viên
Bảng students lưu tuổi học sinh, nhưng một số bạn chưa rõ tuổi (NULL).
| id | name | age |
|---|---|---|
| 1 | Alex Lin | 20 |
| 2 | Maria Chi | NULL |
| 3 | Anna Song | 19 |
| 4 | Otto Art | 22 |
| 5 | Liam Park | NULL |
Bạn muốn biết ai trẻ nhất và ai lớn tuổi nhất.
SELECT
MIN(age) AS youngest_student,
MAX(age) AS eldest_student
FROM students;
Kết quả:
| youngest_student | eldest_student |
|---|---|
| 19 | 22 |
Truy vấn này trả về tuổi nhỏ nhất và lớn nhất chỉ cho sinh viên đã có tuổi. NULL lại bị bỏ qua.
Lưu ý và bẫy thường gặp
Khi làm việc với NULL trong hàm tổng hợp, nhớ mấy điểm sau nha:
- Trong
SUM()vàAVG(),NULLbị bỏ qua. Có thể tận dụng để không tính giá trị "rỗng". - Nếu muốn đếm cả dòng có
NULLtrong cột, dùngCOUNT(*). - Dùng
MIN()hoặcMAX(),NULLkhông ảnh hưởng kết quả. Nhưng nếu cả cột đều làNULL, kết quả cũng làNULLluôn.
Mẹo khi làm việc với NULL
- Xem kỹ yêu cầu bài toán. Hiểu rõ có cần tính
NULLkhông. Đôi khi, như vớiAVG(), bỏ quaNULLlà đúng ý. Nhưng khi đếm tổng số dòng, nhớ tính cả dòng cóNULLnữa. - Dùng
COALESCE()khi cần. Nếu muốn thayNULLbằng giá trị mặc định khi tính toán, hàmCOALESCE()là bạn thân (nhưng cái này để bài sau nói kỹ hơn). - Đừng nhầm
COUNT(*)vớiCOUNT(column). Đây là lỗi newbie hay gặp. Cái đầu đếm tất cả dòng, cái sau chỉ đếm dòng có giá trị khác NULL.
Giờ thì bạn đã biết NULL "im lặng" ảnh hưởng hàm tổng hợp thế nào rồi đó. Biết mấy chiêu này sẽ giúp bạn tránh bị bất ngờ và tận dụng NULL cho lợi ích của mình. Bài sau tụi mình sẽ học công cụ mạnh mẽ COALESCE() để xử lý NULL còn bá đạo hơn nữa nha.
GO TO FULL VERSION