Trước khi đi vào tìm hiểu về window function, chúng ta hãy ôn tập lại về các hàm aggregate: SUM, COUNT và AVG

Tôi có 2 bảng dữ liệu products và product_groups có quan hệ 1-nhiều như sau:

1 product group gồm nhiều product
1 product group gồm nhiều product

Ta lấy ra nhóm sản phẩm, tên và giá mỗi sản phẩm:

select product_groups.group_name, products.product_name, products.price
from products, product_groups
where products.group_id = product_groups.group_id
Thông tin sản phẩm và giá của từng nhóm
Thông tin sản phẩm và giá của từng nhóm

 Với mỗi nhóm sản phẩm, ta tính được giá trung bình của các sản phẩm trong nhóm bằng cách dùng hàm AVG:

select product_groups.group_name, AVG(products.price) AS average_price
from products, product_groups
where products.group_id = product_groups.group_id
group by product_groups.group_name
Giá trung bình của từng nhóm sản phẩm
Giá trung bình của từng nhóm sản phẩm

Bây giờ, yêu cầu đặt ra là: Hãy tính mức chênh lệch giá của từng sản phẩm so với mức giá trung bình của nhóm. Ví dụ, giá của Microsoft Lumia là 200, Microsoft Lumia thuộc nhóm sản phẩm Smartphone có mức giá trung bình là 500 --> chênh lệch giá là 200 - 500 = -300

Ta có thể dùng Sub-Query trong mệnh đề WHERE như sau:

select product_groups.group_id, products.product_name, products.price, 
group_avg_price.average_price,
(products.price - group_avg_price.average_price) AS price_diff
from products, product_groups, 
(
	select product_groups.group_id AS group_id, 
    AVG(products.price) AS average_price
	from products, product_groups
	where products.group_id = product_groups.group_id
	group by product_groups.group_id
) group_avg_price
where products.group_id = product_groups.group_id
and product_groups.group_id = group_avg_price.group_id

 

Kết quả thu được bằng cách dùng Sub-Query
Kết quả thu được bằng cách dùng Sub-Query

Cách làm trên cho kết quả đúng, tuy nhiên câu lệnh SQL trông hơi dài và rối. Ta có thể dùng một cách khác ngắn gọn hơn:

select product_groups.group_id, products.product_name, products.price, 
AVG(price) OVER (PARTITION BY product_groups.group_id) AS average_price, 
( products.price - AVG(price) OVER (PARTITION BY product_groups.group_id) ) AS price_diff
from products, product_groups
where products.group_id = product_groups.group_id
Code ngắn hơn, kết quả vẫn chính xác
Code ngắn hơn, kết quả vẫn chính xác

Ta vẫn dùng hàm AVG() để tính trung bình, tuy nhiên khác biệt ở đây chính là mệnh đề OVER (PARTITION BY product_groups.group_id) ở đằng sau.

Vậy thì, mệnh đề này có tác dụng gì ?

PARTITION BY product_groups.group_id sẽ chia dữ liệu thành các khối riêng biệt dựa trên giá trị của cột product_groups.group_id, mỗi một khối dữ liệu được gọi là 1 window

3 khối dữ liệu được chia theo group_id
3 khối dữ liệu được chia theo group_id

Sau khi đã chia dữ liệu thành 3 khối, hàm AVG() sẽ được chạy cho từng khối, tức là nó sẽ tính toán giá trị trung bình cho từng khối một. Đến đây, các bạn có thể thấy nó khá giống với việc chúng ta dùng hàm AVG() kết hợp với GROUP BY như ở phần đầu bài viết. Điểm khác biệt quan trọng nhất là: GROUP BY sẽ "gom" các bản ghi lại, trong khi với PARTITION BY các bản ghi sẽ không bị "gom" lại và mỗi bản ghi trong một khối (window) sẽ nhận được giá trị trung bình tương ứng của khối đó. 

Thêm một ví dụ khác, lần này ta dùng SUM() để tính tổng giá của từng nhóm sản phẩm:

select product_groups.group_id, products.product_name, products.price, 
AVG(price) OVER (PARTITION BY product_groups.group_id) AS average_price, 
SUM(price) OVER (PARTITION BY product_groups.group_id) AS total_price,
( products.price - AVG(price) OVER (PARTITION BY product_groups.group_id) ) AS price_diff
from products, product_groups
where products.group_id = product_groups.group_id
SUM() kết hợp với OVER (PARTITION BY)
SUM() kết hợp với OVER (PARTITION BY)

Trong các bài viết sau, chúng ta sẽ tiếp tục tìm hiểu cách sử dụng window function để giải quyết các yêu cầu đọc và tính toán dữ liệu