GROUP BY trong SQL là gì?
GROUP BY trong SQL dùng để gom các dòng có cùng giá trị ở một hoặc nhiều cột, sau đó tính aggregate cho từng nhóm. Nếu aggregate functions trả lời tổng thể, GROUP BY trả lời theo từng nhóm như doanh thu theo cửa hàng, số đơn theo khách hàng hoặc giá trung bình theo danh mục.
Trong BikeStores, GROUP BY là kỹ năng cốt lõi để tạo báo cáo bán hàng.
Cú pháp GROUP BY cơ bản
Ví dụ đếm số sản phẩm theo năm model:
SELECT
model_year,
COUNT(*) AS total_products
FROM production.products
GROUP BY model_year
ORDER BY model_year;Mọi cột xuất hiện trong SELECT mà không nằm trong aggregate thường phải có trong GROUP BY. Vì vậy model_year phải được đưa vào GROUP BY.
GROUP BY nhiều bảng với JOIN
Tính số sản phẩm theo danh mục:
SELECT
c.category_name,
COUNT(*) AS total_products,
AVG(p.list_price) AS avg_price
FROM production.products AS p
JOIN production.categories AS c
ON p.category_id = c.category_id
GROUP BY c.category_name
ORDER BY total_products DESC;Ở đây ta JOIN để lấy tên danh mục, sau đó group theo category_name. Nếu group theo category_id, kết quả vẫn đúng nhưng người đọc khó hiểu hơn nếu không chọn tên danh mục.
Tính doanh thu theo cửa hàng
Doanh thu nằm ở chi tiết đơn hàng, còn cửa hàng nằm ở bảng đơn hàng. Vì vậy cần JOIN orders, order_items và stores.
SELECT
st.store_name,
SUM(oi.quantity * oi.list_price * (1 - oi.discount)) AS revenue
FROM sales.orders AS o
JOIN sales.order_items AS oi
ON o.order_id = oi.order_id
JOIN sales.stores AS st
ON o.store_id = st.store_id
GROUP BY st.store_name
ORDER BY revenue DESC;Đây là mẫu báo cáo rất thực tế: JOIN để gom đủ dữ liệu, GROUP BY để chia nhóm, SUM để tính số liệu.
HAVING khác WHERE thế nào?
WHERE lọc dòng trước khi group. HAVING lọc nhóm sau khi group.
Ví dụ chỉ lấy cửa hàng có doanh thu trên 1 triệu:
SELECT
st.store_name,
SUM(oi.quantity * oi.list_price * (1 - oi.discount)) AS revenue
FROM sales.orders AS o
JOIN sales.order_items AS oi
ON o.order_id = oi.order_id
JOIN sales.stores AS st
ON o.store_id = st.store_id
GROUP BY st.store_name
HAVING SUM(oi.quantity * oi.list_price * (1 - oi.discount)) > 1000000
ORDER BY revenue DESC;Không thể dùng aggregate như SUM(...) > 1000000 trong WHERE cùng cấp vì lúc WHERE chạy, nhóm chưa được tạo.
Lỗi kinh điển: COUNT(*) với LEFT JOIN
Khi dùng LEFT JOIN, COUNT(*) có thể làm bạn hiểu sai. Ví dụ muốn đếm số đơn của mỗi khách hàng:
SELECT
c.customer_id,
c.first_name,
c.last_name,
COUNT(*) AS wrong_order_count,
COUNT(o.order_id) AS correct_order_count
FROM sales.customers AS c
LEFT JOIN sales.orders AS o
ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.first_name, c.last_name;Nếu khách hàng chưa có đơn, LEFT JOIN vẫn tạo một dòng với cột đơn hàng là NULL. COUNT(*) đếm dòng đó thành 1, còn COUNT(o.order_id) trả về 0. Đây là edge case cực kỳ quan trọng.
Những lỗi thường gặp với GROUP BY và HAVING
- Chọn cột không aggregate nhưng quên đưa vào
GROUP BY. - Dùng
WHEREđể lọc aggregate, ví dụWHERE COUNT(*) > 5, là sai. - Group theo tên nhưng tên không duy nhất. Với dữ liệu thật, nên group theo ID và chọn thêm tên nếu cần.
- Tính doanh thu sau JOIN nhưng không hiểu quan hệ một-nhiều, dẫn đến nhân dòng.
- Dùng
COUNT(*)sauLEFT JOINkhi cần đếm bản ghi bên phải.
Bài tập thực hành
Hãy viết các báo cáo sau:
- Số sản phẩm theo từng thương hiệu.
- Giá trung bình theo từng danh mục.
- Doanh thu theo từng cửa hàng.
- Khách hàng có từ 2 đơn hàng trở lên.
Gợi ý cho câu cuối:
SELECT
c.customer_id,
c.first_name,
c.last_name,
COUNT(o.order_id) AS total_orders
FROM sales.customers AS c
JOIN sales.orders AS o
ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.first_name, c.last_name
HAVING COUNT(o.order_id) >= 2;Sau khi chạy, hãy đổi JOIN thành LEFT JOIN và quan sát kết quả có thêm khách hàng nào không.
Câu hỏi thường gặp về GROUP BY trong SQL
GROUP BY có bắt buộc đi với aggregate không?
Thường là có ý nghĩa nhất khi đi với aggregate. Bạn vẫn có thể dùng GROUP BY để loại trùng, nhưng DISTINCT thường rõ hơn cho mục đích đó.
HAVING có thay thế WHERE không?
Không. WHERE lọc dòng trước khi group, HAVING lọc nhóm sau khi group. Hai phần này phục vụ hai thời điểm khác nhau.
Có nên group theo tên hay ID?
Trong báo cáo nghiêm túc, nên group theo khóa định danh như customer_id, store_id, category_id, rồi chọn thêm tên để hiển thị. Tên có thể trùng hoặc thay đổi.
Tóm tắt
Bạn đã học GROUP BY, HAVING, báo cáo doanh thu theo nhóm và lỗi COUNT(*) với LEFT JOIN. Ở bài tiếp theo, chúng ta sẽ học subquery để dùng kết quả của một query bên trong query khác.
Bài viết liên quan

Next.js là gì? Tại sao nên dùng Next.js để làm web?
Giới thiệu Next.js — framework React phổ biến nhất. Tìm hiểu ưu điểm, tính năng nổi bật và khi nào nên dùng.

Con bug đầu tiên trong cuộc đời lập trình viên
Câu chuyện hài hước về lần đầu gặp bug và mất 3 tiếng để tìm ra nguyên nhân chỉ là... thiếu dấu chấm phẩy.

Hướng dẫn cài đặt Python chi tiết trên Windows, macOS, Linux
Hướng dẫn từng bước cài đặt Python trên mọi hệ điều hành. Kèm cách kiểm tra và chạy chương trình đầu tiên.