Nhìn sơ qua thì window function và aggregate function có vẻ giống nhau khi phân tích và xử lý dữ liệu. Cả hai đều thực hiện các phép tính như tổng, trung bình, xếp hạng v.v. Nhưng hãy cùng xem chúng khác nhau ở điểm nào nhé.
Aggregate function (GROUP BY)
Aggregate function hoạt động như sau:
- Nó nhóm các dòng theo cột được chỉ định.
- Sau khi nhóm, mỗi nhóm sẽ thành một dòng kết quả duy nhất.
- Ví dụ: bạn muốn biết tổng doanh thu theo từng vùng.
SELECT region, SUM(sales) AS total_sales
FROM sales_data
GROUP BY region;
Đặc điểm: GROUP BY "nén" dữ liệu. Nếu bạn dùng group, tất cả các dòng trong cùng một nhóm sẽ biến mất — chỉ còn lại kết quả tổng hợp.
Window function (PARTITION BY)
Window function thì ngược lại:
- Giữ nguyên cấu trúc dữ liệu gốc (không nén hay làm mất dòng nào hết!).
- Có thể thực hiện phép tính trong từng "window" — nhóm dòng được chia logic.
Ví dụ: bạn muốn biết tỷ lệ doanh số của từng thành phố trong tổng doanh số của vùng, nhưng vẫn giữ nguyên tất cả dữ liệu.
SELECT
region,
city,
sales,
SUM(sales) OVER (PARTITION BY region) AS total_sales_by_region
FROM sales_data;
Đặc điểm: dùng window function không làm mất dòng nào, chỉ thêm giá trị tính toán mới vào từng dòng thôi.
Ví dụ: SUM() với GROUP BY vs SUM() với PARTITION BY
Để hiểu rõ hơn, cùng xem SUM() hoạt động thế nào ở cả hai trường hợp. Giả sử bạn có bảng sales_data như sau:
| region | city | sales |
|---|---|---|
| North | CityA | 100 |
| North | CityB | 150 |
| South | CityC | 200 |
| South | CityD | 250 |
Tính tổng bằng GROUP BY
Chúng ta muốn biết tổng doanh số theo từng vùng:
SELECT region, SUM(sales) AS total_sales
FROM sales_data
GROUP BY region;
Kết quả sẽ như sau:
| region | total_sales |
|---|---|
| North | 250 |
| South | 450 |
Chuyện gì đã xảy ra: các dòng được nhóm theo region, và mỗi nhóm bị "nén" thành một dòng với tổng doanh số.
Tính tổng bằng PARTITION BY
Bây giờ làm tương tự với window function:
SELECT
region,
city,
sales,
SUM(sales) OVER (PARTITION BY region) AS total_sales_by_region
FROM sales_data;
Kết quả:
| region | city | sales | total_sales_by_region |
|---|---|---|---|
| North | CityA | 100 | 250 |
| North | CityB | 150 | 250 |
| South | CityC | 200 | 450 |
| South | CityD | 250 | 450 |
Chuyện gì đã xảy ra: PARTITION BY không "nén" dòng nào. Thay vào đó nó tính tổng trong từng window (mỗi vùng là một window riêng).
Khi nào dùng GROUP BY và khi nào dùng PARTITION BY?
GROUP BY: hợp cho báo cáo tổng kết
GROUP BY hữu ích khi bạn muốn giảm lượng dữ liệu và lấy kết quả cuối cùng ở cấp nhóm. Ví dụ:
- Tổng doanh số theo tháng.
- Đếm số đơn hàng theo loại sản phẩm.
Ví dụ:
SELECT category, COUNT(*) AS total_orders
FROM orders
GROUP BY category;
PARTITION BY: chuẩn cho phân tích chi tiết
PARTITION BY hợp khi bạn cần giữ nguyên tất cả dòng dữ liệu và tính thêm gì đó cho từng dòng. Ví dụ:
- Tính tỷ lệ doanh số của từng sản phẩm trong loại.
- Đánh số dòng trong từng nhóm.
Ví dụ tính tỷ lệ doanh số:
SELECT
category,
product,
sales,
ROUND(
(sales * 100.0) / SUM(sales) OVER (PARTITION BY category),
2
) AS sales_percentage
FROM sales_data;
Ví dụ: dùng nhiều window function cùng lúc
Một ưu điểm của window function là bạn có thể dùng nhiều phép tính cùng lúc. Ví dụ:
SELECT
region,
city,
sales,
SUM(sales) OVER (PARTITION BY region) AS total_sales,
RANK() OVER (PARTITION BY region ORDER BY sales DESC) AS sales_rank
FROM sales_data;
Kết quả:
| region | city | sales | total_sales | sales_rank |
|---|---|---|---|---|
| North | CityB | 150 | 250 | 1 |
| North | CityA | 100 | 250 | 2 |
| South | CityD | 250 | 450 | 1 |
| South | CityC | 200 | 450 | 2 |
Ưu điểm của window function so với GROUP BY
Giữ nguyên dữ liệu gốc: GROUP BY "nén" dòng, còn window function thì giữ nguyên cấu trúc bảng.
Nhiều phép tính trong một query: Bạn có thể dùng nhiều window function với các tham số PARTITION BY và ORDER BY khác nhau mà vẫn giữ dữ liệu.
Phân tích linh hoạt: Window function cho phép bạn tuỳ chỉnh phép tính theo ý muốn: tổng tích luỹ, xếp hạng, tính tỷ lệ và nhiều thứ khác.
Ví dụ về sự linh hoạt
Thử kết hợp nhiều function:
SELECT
region,
city,
sales,
SUM(sales) OVER (PARTITION BY region) AS total_sales,
AVG(sales) OVER (PARTITION BY region) AS avg_sales,
RANK() OVER (PARTITION BY region ORDER BY sales DESC) AS rank
FROM sales_data;
Kết quả:
| region | city | sales | total_sales | avg_sales | rank |
|---|---|---|---|---|---|
| North | CityB | 150 | 250 | 125.0 | 1 |
| North | CityA | 100 | 250 | 125.0 | 2 |
| South | CityD | 250 | 450 | 225.0 | 1 |
| South | CityC | 200 | 450 | 225.0 | 2 |
Giới hạn và lỗi thường gặp
Một lỗi phổ biến là cố dùng PARTITION BY khi cần "nén" dữ liệu. Ví dụ, thay vì:
SELECT region, SUM(sales) AS total_sales
FROM sales_data
GROUP BY region;
Có bạn lại viết như này:
SELECT
region,
SUM(sales) OVER (PARTITION BY region) AS total_sales
FROM sales_data;
Nhưng cái này sẽ trả về tất cả các dòng, không giảm lượng dữ liệu (không phải lúc nào cũng đúng ý bạn muốn).
Bây giờ bạn đã biết rõ khi nào dùng GROUP BY, khi nào dùng window function. Nó giống như chọn giữa búa và tua vít: cả hai đều làm việc với đinh... nhưng theo cách khác nhau.
GO TO FULL VERSION