Có một thứ tụi mình chưa bàn tới, đó là làm sao để lọc các nhóm sau khi đã dùng aggregate? Đôi khi mình đâu cần tất cả các khoa — chỉ cần những khoa có hơn trăm sinh viên thôi. Hoặc mình muốn xem những phòng ban nào có lương trung bình trên 50,000. Hôm nay tụi mình sẽ làm quen với việc lọc dữ liệu đã tổng hợp bằng HAVING.
Tại sao lại cần HAVING, trong khi đã có WHERE? Sao không đặt WHERE sau GROUP BY luôn nhỉ :)
Không đơn giản vậy đâu! Đầu tiên, thứ tự các toán tử trong SQL là cố định và WHERE sẽ chạy trước GROUP BY.
Vậy có thể đẩy nó xuống sau GROUP BY không?
Cũng không luôn! Thường thì mình cần lọc các dòng trong bảng trước khi nhóm. Sau đó mới nhóm dữ liệu đã lọc. Rồi cuối cùng loại bỏ những nhóm không cần thiết sau khi đã nhóm.
Vậy thì sao không copy WHERE, đặt tên là HAVING rồi để nó sau GROUP BY?
Chuẩn luôn! Làm vậy đi! :)
Khác biệt giữa HAVING và WHERE
WHERE lọc dòng trước khi nhóm.
Ví dụ như bạn chọn bánh theo vị: dâu và socola thì giữ lại, còn lại bỏ qua. Đó là việc của WHERE.
HAVING lọc sau khi dữ liệu đã được nhóm và các hàm tổng hợp đã làm xong việc của mình.
Ví dụ, bạn đã nhóm bánh theo bàn, đếm số lượng bánh trên mỗi bàn và giờ chỉ muốn giữ lại những bàn có hơn ba cái bánh.
Vậy nên, HAVING dùng để lọc dữ liệu ở cấp nhóm.
Cú pháp của HAVING
Cú pháp gần giống WHERE, nhưng nó hoạt động hơi khác chút:
SELECT cột, hàm_tổng_hợp
FROM bảng
GROUP BY cột
HAVING điều_kiện;
Các bước thực hiện:
- Đầu tiên các dòng được lọc qua
WHERE. - Sau đó dữ liệu được nhóm bằng
GROUP BY. - Các hàm tổng hợp được áp dụng lên kết quả nhóm.
- Cuối cùng, kết quả sẽ được lọc tiếp bằng
HAVING.
Ví dụ sử dụng HAVING
Ví dụ 1: Lọc các khoa có nhiều sinh viên
Bạn muốn biết khoa nào trong trường đại học có hơn 100 sinh viên. Giả sử có bảng students:
| id | name | faculty |
|---|---|---|
| 1 | Alice | Engineering |
| 2 | Bob | Engineering |
| 3 | Charlie | Arts |
| 4 | Daisy | Business |
| 5 | ... | ... |
Câu truy vấn:
SELECT faculty, COUNT(*) AS student_count
FROM students
GROUP BY faculty
HAVING COUNT(*) > 100;
Chuyện gì đang xảy ra ở đây:
- Đầu tiên mình nhóm sinh viên theo cột
facultybằngGROUP BY. - Sau đó hàm tổng hợp
COUNT(*)đếm số sinh viên ở mỗi khoa. - Cuối cùng,
HAVINGloại bỏ các khoa có 100 sinh viên trở xuống.
Kết quả:
| faculty | student_count |
|---|---|
| Engineering | 150 |
| Arts | 120 |
Ví dụ 2: Phòng ban có lương trung bình cao
Bạn muốn tìm những phòng ban mà lương trung bình của nhân viên lớn hơn 50,000. Giả sử có bảng employees:
| id | name | department | salary |
|---|---|---|---|
| 1 | Alice | IT | 60000 |
| 2 | Bob | HR | 45000 |
| 3 | Charlie | IT | 70000 |
| 4 | Daisy | HR | 52000 |
| 5 | ... | ... | ... |
Câu truy vấn:
SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department
HAVING AVG(salary) > 50000;
Kết quả:
| department | avg_salary |
|---|---|
| IT | 65000 |
Lưu ý: HAVING làm việc với kết quả đã được tính sau GROUP BY.
Thứ tự thực hiện WHERE, GROUP BY và HAVING
Lọc bằng WHERE và HAVING diễn ra ở các bước khác nhau. Để hiểu rõ hơn, cùng xem quy trình từng bước của truy vấn:
WHERE: lọc dòng.Ở bước này, tất cả các dòng của bảng được xử lý. Nếu dòng không thỏa điều kiện
WHERE, nó sẽ không được xử lý tiếp.GROUP BY: nhóm dòng.Sau khi lọc, các dòng được gom thành nhóm dựa trên các cột chỉ định trong
GROUP BY.Hàm tổng hợp:
Các hàm tổng hợp như
COUNT(),AVG(),SUM()... được áp dụng lên dữ liệu đã nhóm.HAVING: lọc nhóm.Bước này chỉ xử lý kết quả của các aggregate. Điều kiện
HAVINGchỉ áp dụng cho nhóm.
Đặc điểm của HAVING
Đặc điểm 1: Làm việc với aggregate
Điểm khác biệt lớn nhất giữa HAVING và WHERE là HAVING làm việc với các hàm tổng hợp. Ví dụ:
SELECT department, AVG(salary)
FROM employees
GROUP BY department
HAVING AVG(salary) > 50000;
Trong truy vấn này AVG(salary) không thể dùng trong WHERE, vì WHERE xử lý dòng trước khi nhóm. Truy vấn kiểu như:
SELECT department, AVG(salary)
FROM employees
WHERE AVG(salary) > 50000
GROUP BY department;
sẽ báo lỗi: aggregate functions are not allowed in WHERE.
Đặc điểm 2: Lọc mà không cần GROUP BY
Bạn có thể dùng HAVING ngay cả khi không có GROUP BY. Khi đó truy vấn sẽ coi như có một nhóm duy nhất — tất cả các bản ghi:
SELECT AVG(salary) AS avg_salary
FROM employees
HAVING AVG(salary) > 50000;
Ví dụ thực tế
Giả sử có một cửa hàng và bảng bán hàng sales:
| id | product_id | sales_amount |
|---|---|---|
| 1 | 101 | 200.00 |
| 2 | 102 | 300.00 |
| 3 | 101 | 400.00 |
| 4 | 103 | 150.00 |
Câu truy vấn: tìm sản phẩm có tổng doanh số lớn hơn 500.
SELECT product_id, SUM(sales_amount) AS total_sales
FROM sales
GROUP BY product_id
HAVING SUM(sales_amount) > 500;
Kết quả:
| product_id | total_sales |
|---|---|
| 101 | 600.00 |
Lỗi thường gặp
Dùng aggregate trong WHERE:
Ví dụ:
SELECT department, AVG(salary)
FROM employees
WHERE AVG(salary) > 50000
GROUP BY department;
Lỗi: không được dùng hàm tổng hợp trong WHERE.
Lỗi với NULL:
Nếu dữ liệu có NULL, việc lọc có thể ra kết quả bất ngờ. Ví dụ:
SELECT department, SUM(salary)
FROM employees
GROUP BY department
HAVING SUM(salary) > 0;
Nếu cột salary chỉ toàn NULL, kết quả có thể là 0 hoặc rỗng.
Chúc mừng nhé. Đến đây là bạn đã tự tin lọc dữ liệu tổng hợp rồi! Đừng quên, HAVING là chìa khóa để phân tích ở cấp nhóm, nơi mà WHERE thông thường không đủ dùng nữa đâu.
GO TO FULL VERSION