Trước khi bắt đầu, tưởng tượng bạn đang làm việc với một bảng có hàng ngàn dòng bán hàng. Nhiệm vụ của bạn: xác định ai là người bán số một trong từng danh mục, ai là số hai, v.v. Hoặc, ví dụ, bạn cần đánh số tất cả các dòng trong kết quả truy vấn để theo dõi thứ tự. Tất cả điều này dễ dàng làm được với hàm cửa sổ.
Hàm cửa sổ — là các hàm SQL hoạt động với một tập con các dòng (gọi là "cửa sổ") từ tập dữ liệu. Khác với hàm tổng hợp, vốn gộp các dòng thành một (ví dụ SUM() hoặc AVG()), hàm cửa sổ giữ nguyên các dòng, chỉ thêm giá trị tính toán vào từng dòng.
Khác biệt với hàm tổng hợp
Hàm tổng hợp "nén" dữ liệu bằng cách nhóm các dòng lại:
SELECT department, COUNT(*)
FROM employees
GROUP BY department;
Kết quả: chỉ còn vài dòng, mỗi dòng cho một phòng ban.
So sánh với hàm cửa sổ — ở đây các dòng vẫn giữ nguyên, nhưng thêm một trường mới, ví dụ ROW_NUMBER():
SELECT employee_name, department,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rank_within_department
FROM employees;
Bạn sẽ nhận được tất cả các dòng như cũ, nhưng có thêm cột rank_within_department, trong đó mỗi nhân viên được gán số thứ tự trong phòng ban của mình.
Các hàm cửa sổ cơ bản
Cú pháp OVER()
Phần quan trọng nhất của mỗi hàm cửa sổ — là từ khóa thần thánh OVER(). Nó xác định "cửa sổ" dữ liệu mà hàm sẽ làm việc. Bên trong OVER() bạn có thể chỉ định phân nhóm (PARTITION BY) và/hoặc thứ tự sắp xếp (ORDER BY).
Cú pháp tổng quát:
<hàm_cửa_sổ>() OVER (
[PARTITION BY <nhóm>]
[ORDER BY <thứ_tự>]
)
Các thành phần:
PARTITION BY: Chia các dòng thành nhóm. Ví dụ, "chia dữ liệu theo phòng ban".ORDER BY: Chỉ định thứ tự xử lý các dòng. Ví dụ, "sắp xếp nhân viên theo lương giảm dần".
Hàm ROW_NUMBER()
Hàm ROW_NUMBER() đánh số các dòng, bắt đầu từ 1, trong "cửa sổ" đã chỉ định. Đôi khi nó hữu ích để tạo số thứ tự dòng trong bảng tạm hoặc xác định vị trí của bản ghi.
Ví dụ. Bảng sales (bán hàng):
| id | product_category | seller_name | revenue |
|---|---|---|---|
| 1 | Electronics | Alice | 1000 |
| 2 | Electronics | Bob | 850 |
| 3 | Furniture | Alice | 1200 |
| 4 | Furniture | Charlie | 1100 |
| 5 | Electronics | Dana | 750 |
Truy vấn:
SELECT seller_name, product_category, revenue,
ROW_NUMBER() OVER (PARTITION BY product_category ORDER BY revenue DESC) AS row_number
FROM sales;
Kết quả:
| seller_name | product_category | revenue | row_number |
|---|---|---|---|
| Alice | Electronics | 1000 | 1 |
| Bob | Electronics | 850 | 2 |
| Dana | Electronics | 750 | 3 |
| Alice | Furniture | 1200 | 1 |
| Charlie | Furniture | 1100 | 2 |
Cách nó hoạt động:
- Dữ liệu được chia thành nhóm theo
product_category. - Mỗi nhóm được sắp xếp theo
revenue(giảm dần). - Các dòng trong mỗi nhóm được gán số thứ tự.
Hàm RANK()
Hàm RANK() dùng để xếp hạng các dòng. Khác với ROW_NUMBER(), nó xử lý các giá trị trùng nhau và sẽ bỏ qua số thứ tự (hạng) nếu có giá trị trùng.
Ví dụ:
SELECT seller_name, product_category, revenue,
RANK() OVER (PARTITION BY product_category ORDER BY revenue DESC) AS rank
FROM sales;
Kết quả:
| seller_name | product_category | revenue | rank |
|---|---|---|---|
| Alice | Electronics | 1000 | 1 |
| Bob | Electronics | 850 | 2 |
| Dana | Electronics | 750 | 3 |
| Alice | Furniture | 1200 | 1 |
| Charlie | Furniture | 1100 | 2 |
Hàm DENSE_RANK()
DENSE_RANK() giống RANK(), chỉ khác một điểm: nó không bỏ qua số hạng nếu có giá trị trùng.
Ví dụ. Thêm một giao dịch có cùng doanh thu:
| id | product_category | seller_name | revenue |
|---|---|---|---|
| 6 | Electronics | Alice | 1000 |
| 7 | Electronics | Dana | 750 |
Truy vấn:
SELECT seller_name, product_category, revenue,
DENSE_RANK() OVER (PARTITION BY product_category ORDER BY revenue DESC) AS dense_rank
FROM sales;
Kết quả:
| seller_name | product_category | revenue | dense_rank |
|---|---|---|---|
| Alice | Electronics | 1000 | 1 |
| Alice | Electronics | 1000 | 1 |
| Bob | Electronics | 850 | 2 |
| Dana | Electronics | 750 | 3 |
Ví dụ sử dụng: đánh số dòng
Bài toán: đánh số tất cả các đơn hàng trong bảng orders, sắp xếp theo ngày.
SELECT order_id, customer_name, order_date,
ROW_NUMBER() OVER (ORDER BY order_date) AS order_number
FROM orders;
Kết quả: bạn sẽ có danh sách đơn hàng được đánh số theo thứ tự thực hiện.
Ví dụ sử dụng: top-3 người bán trong mỗi danh mục
Bài toán: xác định ba người bán tốt nhất trong mỗi danh mục sản phẩm.
WITH ranked_sales AS (
SELECT seller_name, product_category, revenue,
RANK() OVER (PARTITION BY product_category ORDER BY revenue DESC) AS rank
FROM sales
)
SELECT seller_name, product_category, revenue
FROM ranked_sales
WHERE rank <= 3;
Ví dụ sử dụng: phát hiện các giá trị trùng nhau
Bài toán: xác định xem có người bán nào có doanh thu giống nhau trong từng danh mục không.
SELECT seller_name, product_category, revenue,
DENSE_RANK() OVER (PARTITION BY product_category ORDER BY revenue DESC) AS dense_rank
FROM sales;
Bây giờ bạn có thể thấy các hạng, nơi các giá trị "dính" lại với nhau.
GO TO FULL VERSION