GROUP BY statement sẽ nhóm những rows có data giống nhau tại một hoặc nhiều column thành một summary rows.
GROUP BY thường được kết hợp sử dụng với aggregate functions (SUM, COUNT, AVG...) để đưa ra các kết quả có tính chất tổng hợp.
SELECT column_name(s)
FROM table_name
WHERE condition
GROUP BY column_name(s)
HAVING condition
ORDER BY column_name(s);
Với những bài toán cần data tổng hợp, chức năng báo cáo như: "tính doanh thu theo thời gian, tính trung bình điểm của học viên..." thì GROUP BY là một solution được nghĩ đến đầu tiên.
Để giải thích cách hoạt động của GROUP BY, chúng ta cùng làm 1 ví dụ.
Hãy sử dụng table Employees với structure/data cho trước và trả lời câu hỏi bên dưới:
SQL Statement trong bài viết này sẽ sử dụng SQL SERVER 2019 để demo source
EmployeeID (PK) | FullName | Level | Status | |
---|---|---|---|---|
1 | Ken Sánchez | ken@test.com | Fresher | Working |
2 | Brian Welcker | brian@test.com | Junior | Working |
3 | Stephen Jiang | stephen@test.com | Senior | Working |
4 | Linda Mitchell | linda@test.com | Senior | Out |
5 | Michael Blythe | michael@test.com | Junior | Unpaid Leave |
6 | Syed Abbas | syed@test.com | Fresher | Working |
7 | Lynn Tsoflias | lynn@test.com | Fresher | Working |
8 | David Bradley | david@test.com | Junior | Working |
9 | Mary Gibson | mary@test.com | Senior | Unpaid Leave |
10 | Clever | clever@test.com | Senior | Working |
* Phân tích: Với yêu cầu [Q.1], rõ ràng mục tiêu của chúng ta sẽ phải đi tìm: Có bao nhiêu nhân viên Fresher? Có bao nhiêu nhân viên Junior...?
Đối chiếu khái niệm của GROUP BY và mục đích nhóm data như Figure 01. Ta sẽ cần nhóm các nhân viên theo colum "Level" với query như sau:
SELECT Level, Count(1) AS TotalEmployee
FROM Employees
GROUP BY Level
Chúng ta sẽ được kết quả như bên dưới:
Level | TotalEmployee |
---|---|
Fresher | 3 |
Junior | 3 |
Senior | 4 |
Với yêu cầu của [Q.1] rất nhiều bạn đưa ra sự lựa chọn là sẽ GROUP BY theo EmployeeID. Điều này hoàn toàn sai.
Chúng ta phải dựa vào yêu cầu để xác định rõ ràng 2 yếu tố:
1. ĐỐI TƯỢNG cần nhóm: Employee
2. TIÊU CHÍ muốn nhóm: Level
Mục đích chúng ta sử dụng GROUP BY là sẽ nhóm đối tượng theo một tiêu chí chung nào đó. Đồng nghĩa rằng nếu ta có X số lượng row(s) thì sau khi GROUP BY số lượng row(s) sẽ phải <= X.
Tuy nhiên với việc sử dụng GROUP BY với >= 1 column PK/Unique thì sau khi GROUP BY số lượng row(s) sẽ không thay đổi, vì chúng ta đang tìm điểm chung của tệp data bằng 1 TIÊU CHÍ luôn khác nhau.
Đôi khi làm việc với GROUP BY chúng ta sẽ phải filter data theo 1 hoặc nhiều tiêu chí nào đó. Lúc này một số bạn sẽ bối rối giữa việc lựa chọn giữa WHERE và HAVING. Hãy cùng nhau so sánh:
# | WHERE | HAVING |
---|---|---|
Định nghĩa | Dùng để filter row(s) data trước khi GROUP BY (Pre-filter). | Dùng để filter row(s) data sau khi GROUP BY (Post-filter). |
Vị trí | Trước GROUP BY | Sau GROUP BY |
Hạn chế | Không thể sử dụng với aggregate functions. Refer [Q.3] | Chỉ filter được với các column trong GROUP BY clause hoặc trong aggregate functions. Refer [Q.2] | Performance * Điều kiện có thể filter được cả ở WHERE và HAVING. |
Nhanh hơn Refer [Q.4] | Chậm hơn |
Để làm rõ hơn về những so sánh trên, chúng ta cùng nhau trả lời những câu hỏi tiếp theo
Với yêu cầu nhóm những nhân viên đang làm việc, thì chúng ta sẽ chỉ thực hiện thao tác với dữ liệu Status = 'Working'
# | WHERE | HAVING | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
SQL |
SELECT Level, Count(1) AS TotalEmployee |
SELECT Level, Count(1) AS TotalEmployee |
||||||||
Kết quả |
|
ERROR: Column 'Employees.Status' is invalid in the HAVING clause because it is not contained in either an aggregate function or the GROUP BY clause. |
Với yêu cầu số lượng nhân viên >= 4, thì chúng ta sẽ chỉ thực hiện thao tác với dữ liệu Count(1) >= 4
# | WHERE | HAVING | ||||
---|---|---|---|---|---|---|
SQL |
SELECT Level, Count(1) AS TotalEmployee |
SELECT Level, Count(1) AS TotalEmployee |
||||
Kết quả | ERROR: An aggregate may not appear in the WHERE clause unless it is in a subquery contained in a HAVING clause or a select list, and the column being aggregated is an outer reference. |
|
# | WHERE | HAVING | ||||
---|---|---|---|---|---|---|
SQL |
SELECT Level, Count(1) AS TotalEmployee |
SELECT Level, Count(1) AS TotalEmployee |
||||
Kết quả |
|
Chúng ta có thể thấy WHERE và HAVING đều trả về 1 kết quả. Tuy nhiên cách thức filter sẽ khác nhau.
Với WHERE (pre-filter) sẽ loại bỏ data trước khi GROUP BY, chúng ta chỉ còn 3 rows sau filter để GROUP BY và COUNT.
Ngược lại với HAVING (post-filter), chúng ta sẽ phải GROUP BY chia nhóm và COUNT với 10 rows sau đó mới loại bỏ data không phù hợp.
Với yêu cầu này chúng ta cần filter 2 tiêu chí Status = 'Working' và COUNT(1) > 2
SELECT Level, Count(1) AS TotalEmployee
FROM Employees
WHERE Status = 'Working'
GROUP BY Level
HAVING COUNT(1) > 2
Level | TotalEmployee |
---|---|
Fresher | 3 |
Trên đây là những hiểu lầm/thiếu sót phổ biến ở các bạn Beginer. Hi vọng rằng sau bài viết này chúng ta sẽ thực sự hiểu và sẽ có những sự lựa chọn đúng đắn khi dùng GROUP BY.