Câu 1Khi bạn thêm ORDER BY vào trong OVER, như SUM(amount) OVER (PARTITION BY emp_id ORDER BY sale_date), giá trị nào hiện lên trên mỗi dòng?
Window Functions ② — ORDER BY và Frame (ROWS / RANGE)
Hình dung tổng lũy kế với OVER (... ORDER BY ...), tổng di động với ROWS BETWEEN 2 PRECEDING, và sự khác biệt giữa ROWS và RANGE khi có dòng trùng giá trị — tất cả khám phá với dữ liệu sales và sơ đồ.
Window với ORDER BY — tạo tổng lũy kế
Thêm ORDER BY vào trong OVER và mục tiêu tổng hợp chuyển thành "từ đầu window đến dòng hiện tại" — giá trị tăng dần theo từng dòng, tạo nên tổng lũy kế (tổng từ đầu đến dòng hiện tại).
Dạng là SUM(amount) OVER (PARTITION BY emp_id ORDER BY sale_date).
PARTITION BY emp_id chia window theo từng nhân viên, ORDER BY sale_date sắp xếp giao dịch của mỗi nhân viên theo ngày, sau đó tổng lũy kế được tính theo từng nhân viên.
Sơ đồ bên dưới theo dấu cách tổng lũy kế tăng dần qua 5 giao dịch của emp_id=29 theo thứ tự ngày.
-- Không có ORDER BY: mỗi dòng nhận tổng không đổi của nhân viên
SELECT emp_id, sale_date, amount,
SUM(amount) OVER (PARTITION BY emp_id) AS emp_total
FROM sales
ORDER BY emp_id, sale_date;
-- Với ORDER BY: tổng lũy kế theo nhân viên từ đầu đến dòng hiện tại
SELECT emp_id, sale_date, amount,
SUM(amount) OVER (PARTITION BY emp_id ORDER BY sale_date) AS running_total
FROM sales
ORDER BY emp_id, sale_date;
Frame — các dòng thực sự được tổng hợp trong window
Khi bạn thêm ORDER BY vào window function, phạm vi các dòng mà mỗi dòng thực sự tổng hợp được gọi là frame.
Nếu bạn không viết frame một cách tường minh, mặc định là RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW (từ đầu window đến dòng hiện tại).
Đó chính là lý do thêm ORDER BY ở bài tập trước cho bạn tổng lũy kế — frame mặc định này đang làm việc.
-- A: bỏ frame (mặc định = RANGE UNBOUNDED PRECEDING ... CURRENT ROW)
SELECT emp_id, sale_date, amount,
SUM(amount) OVER (PARTITION BY emp_id ORDER BY sale_date) AS run_default
FROM sales WHERE emp_id = 14
ORDER BY sale_date;
-- B: frame mặc định viết tường minh (cùng kết quả với A)
SELECT emp_id, sale_date, amount,
SUM(amount) OVER (
PARTITION BY emp_id ORDER BY sale_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS run_explicit
FROM sales WHERE emp_id = 14
ORDER BY sale_date;
Frame được xây từ 5 từ khóa sau, dùng để nói "từ đâu" và "đến đâu" so với dòng hiện tại.
| Từ khóa | Ý nghĩa |
|---|---|
UNBOUNDED PRECEDING | Lùi tới tận đầu window |
n PRECEDING | n dòng trước dòng hiện tại |
CURRENT ROW | Chính dòng hiện tại |
n FOLLOWING | n dòng sau dòng hiện tại |
UNBOUNDED FOLLOWING | Tiến tới tận cuối window |
Mặc định (với ORDER BY) | RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW |
ROWS BETWEEN n PRECEDING — tổng di động và trung bình di động
Đặt frame thành ROWS BETWEEN n PRECEDING AND CURRENT ROW và phép tổng hợp thu hẹp thành "n dòng gần nhất + dòng hiện tại".
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW bao trùm 3 dòng gần nhất (bao gồm dòng hiện tại), nên SUM trở thành tổng di động và AVG trở thành trung bình di động.
Đây là dạng dùng phổ biến để làm mịn xu hướng gần đây.
Gần đầu window thì frame có ít dòng để lấy (dòng 1 có 1 dòng, dòng 2 có 2 dòng, dòng 3 trở đi có 3 dòng).
Sơ đồ bên dưới đi qua 5 giao dịch của emp_id=14 theo từng dòng, hiển thị nội dung của frame tại mỗi "dòng hiện tại".
-- Trung bình di động của 3 giao dịch gần nhất (2 dòng trước + hiện tại)
SELECT emp_id, sale_date, amount,
AVG(amount) OVER (
PARTITION BY emp_id ORDER BY sale_date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS moving_avg3
FROM sales WHERE emp_id = 2
ORDER BY sale_date;
-- Tổng di động của 2 giao dịch gần nhất (1 dòng trước + hiện tại) dùng cùng dạng
SELECT emp_id, sale_date, amount,
SUM(amount) OVER (
PARTITION BY emp_id ORDER BY sale_date
ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
) AS moving_sum2
FROM sales WHERE emp_id = 2
ORDER BY sale_date;
ROWS vs. RANGE — cách xử lý các dòng trùng giá trị
Frame có hai chế độ đếm: ROWS và RANGE.
ROWS đếm dòng vật lý ("2 dòng trước" nghĩa là đúng 2 dòng). Ngay cả khi có giá trị trùng, mỗi dòng vẫn được đếm riêng, nên tổng lũy kế tăng theo từng dòng.
RANGE gom các dòng có cùng giá trị ORDER BY (trùng) vào một nhóm. Các dòng trùng nhau dùng chung frame và kết thúc với cùng tổng lũy kế.
Hai sơ đồ bên dưới cho thấy ROWS và RANGE xử lý ra sao với 3 dòng có amount = 100000.
-- ORDER BY amount có 3 dòng trùng tại 100000, nên ROWS và RANGE khác nhau
SELECT sale_id, amount,
SUM(amount) OVER (ORDER BY amount ROWS UNBOUNDED PRECEDING) AS rows_run,
SUM(amount) OVER (ORDER BY amount RANGE UNBOUNDED PRECEDING) AS range_run
FROM sales
WHERE amount = 100000
ORDER BY sale_id;
-- ROWS đếm từng dòng một; RANGE gói 3 dòng trùng 100000 vào một frame
Kiểm tra kiến thức
Hãy trả lời từng câu hỏi một.
Câu 2Khi window function có ORDER BY và bạn không viết frame tường minh, frame mặc định là gì?
Câu 3Sự khác biệt giữa frame ROWS và RANGE là gì?