Window function tính toán trên một "cửa sổ" các dòng liên quan nhưng không gộp dòng như GROUP BY — mỗi dòng vẫn giữ nguyên và được gắn thêm một giá trị tính ra (số thứ tự, xếp hạng, tổng lũy kế...).
Mệnh đề OVER: PARTITION BY chia thành các cửa sổ con (như GROUP BY nhưng không gộp), ORDER BY quy định thứ tự trong cửa sổ.
Nhóm xếp hạng (khác nhau ở cách xử lý đồng hạng):
- ROW_NUMBER(): số thứ tự duy nhất (1,2,3).
- RANK(): đồng hạng cùng số, nhảy số tiếp (1,1,3).
- DENSE_RANK(): đồng hạng cùng số, không nhảy (1,1,2).
Nhóm khác:
- LAG/LEAD: lấy giá trị dòng trước/sau — LAG(salary) OVER (ORDER BY hire_date), tính tăng trưởng tháng-qua-tháng.
- Tổng lũy kế: SUM(amount) OVER (PARTITION BY user_id ORDER BY created_at).
- Khung (frame): ROWS BETWEEN 2 PRECEDING AND CURRENT ROW (trung bình trượt 3 dòng).
Pattern phỏng vấn kinh điển — lấy dòng mới nhất mỗi nhóm (khử trùng lặp):
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
FROM orders
) t WHERE rn = 1;A window function computes over a "window" of related rows but does not collapse rows like GROUP BY — each row is kept and gets an extra computed value (row number, rank, running total...).
The OVER clause: PARTITION BY splits into sub-windows (like GROUP BY but without collapsing), ORDER BY sets the order within the window.
Ranking family (differ in how ties are handled):
- ROW_NUMBER(): a unique sequential number (1,2,3).
- RANK(): ties share a number, then the next is skipped (1,1,3).
- DENSE_RANK(): ties share a number, no skip (1,1,2).
Other useful ones:
- LAG/LEAD: fetch the previous/next row's value — LAG(salary) OVER (ORDER BY hire_date), for month-over-month growth.
- Running total: SUM(amount) OVER (PARTITION BY user_id ORDER BY created_at).
- Frame: ROWS BETWEEN 2 PRECEDING AND CURRENT ROW (3-row moving average).
Classic interview pattern — get the latest row per group (deduplication):
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
FROM orders
) t WHERE rn = 1;