Giao diện
Window Functions: Sức mạnh của cửa sổ
Nếu GROUP BY gom nhiều dòng thành 1 dòng, làm mất chi tiết, thì Window Functions cho phép tính toán trên một tập hợp dòng (cửa sổ) mà vẫn giữ nguyên các dòng chi tiết.
Concept: "Cửa sổ nhìn ra thế giới"
Với mỗi dòng hiện tại, "cửa sổ" là tập hợp các dòng liên quan đến nó (ví dụ: cùng phòng ban, cùng ngày).
Cú pháp cơ bản:
sql
FUNCTION_NAME() OVER (
PARTITION BY column1 -- Chia nhỏ cửa sổ theo nhóm (giống Group By)
ORDER BY column2 -- Sắp xếp thứ tự trong cửa sổ
)Các hàm Ranking (Xếp hạng)
Giả sử bảng employees (id, name, department, salary). Yêu cầu: Tìm top 3 nhân viên lương cao nhất trong MỖI phòng ban.
sql
SELECT * FROM (
SELECT
name, department, salary,
DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) as ranking
FROM employees
) as ranked
WHERE ranking <= 3;Phân biệt ROW_NUMBER, RANK, DENSE_RANK
Giả sử lương là: [1000, 1000, 900, 800]
| Hàm | Kết quả | Giải thích |
|---|---|---|
| ROW_NUMBER() | 1, 2, 3, 4 | Số thứ tự đơn thuần. Bất kể lương bằng nhau, số vẫn tăng. |
| RANK() | 1, 1, 3, 4 | Hạng giống nhau nếu giá trị bằng nhau. Hạng tiếp theo sẽ bị nhảy cóc (mất số 2). |
| DENSE_RANK() | 1, 1, 2, 3 | Hạng giống nhau, hạng tiếp theo KHÔNG nhảy cóc (Số 2 vẫn xuất hiện). Thường dùng nhất để tìm Top N. |
LEAD & LAG (Du hành thời gian)
Truy cập dòng trước (LAG) hoặc dòng sau (LEAD) mà không cần Self-Join.
Ví dụ: So sánh doanh thu ngày hôm nay so với ngày hôm qua.
sql
SELECT
date,
revenue,
LAG(revenue) OVER (ORDER BY date) as previous_day_revenue,
(revenue - LAG(revenue) OVER (ORDER BY date)) as diff
FROM daily_sales;💡 HPN Pro Tip: Pagination bằng Window Function
Thay vì chạy 2 query: 1 query lấy data, 1 query COUNT(*) để tính tổng số trang (Rất chậm), bạn có thể dùng COUNT(*) OVER() để lấy tổng số dòng ngay trong query lấy data.
sql
SELECT
id, name,
COUNT(*) OVER() as total_records -- Tổng tất cả dòng (Không bị ảnh hưởng bởi LIMIT)
FROM users
LIMIT 10 OFFSET 0;Lưu ý: Cách này tiện nhưng cũng có thể nặng nếu bảng quá lớn.
⚠️ Common Mistake
Quên ORDER BY trong mệnh đề OVER. Các hàm như RANK, LEAD, LAG vô nghĩa nếu không có thứ tự xác định. PARTITION BY là tùy chọn, nhưng ORDER BY thường là bắt buộc để có kết quả đúng ý nghĩa logic.
🧠 Quiz
Câu 1: Window Function khác Aggregate Function ở điểm nào?
- [ ] A) Window Function chỉ dùng được với số
- [ ] B) Window Function nhanh hơn
- [x] C) Window Function không gom nhóm — giữ nguyên số dòng, aggregate gom lại thành 1 dòng per group
- [ ] D) Window Function thay thế hoàn toàn Aggregate
💡 Giải thích: Aggregate + GROUP BY gom nhiều dòng thành 1 (mất chi tiết). Window Function tính toán "trên một cửa sổ" dòng nhưng giữ nguyên mọi dòng gốc — bạn vừa thấy chi tiết, vừa thấy tổng hợp.
Câu 2: ROW_NUMBER vs RANK — khác nhau khi nào?
- [ ] A) Luôn cho kết quả giống nhau
- [ ] B) ROW_NUMBER dùng cho số, RANK dùng cho chuỗi
- [x] C) Khi có giá trị bằng nhau: ROW_NUMBER vẫn đánh số liên tục, RANK đánh cùng hạng và bỏ số tiếp theo
- [ ] D) RANK nhanh hơn ROW_NUMBER
💡 Giải thích: Với điểm [90, 90, 80]: ROW_NUMBER cho 1, 2, 3 (arbitrary cho 2 điểm 90). RANK cho 1, 1, 3 (cùng hạng 1, bỏ hạng 2). DENSE_RANK cho 1, 1, 2 (cùng hạng 1, không bỏ số).
Câu 3: Tại sao ORDER BY trong OVER() quan trọng?
- [ ] A) Bắt buộc về cú pháp, thiếu sẽ lỗi
- [x] B) Xác định thứ tự dòng trong window — thiếu thì RANK, LEAD, LAG cho kết quả vô nghĩa
- [ ] C) Quyết định cột nào được trả về
- [ ] D) Chỉ cần cho PARTITION BY
💡 Giải thích: Không có ORDER BY trong OVER(), hàm RANK xếp hạng theo thứ tự nào? LEAD lấy dòng "tiếp theo" là dòng nào? Kết quả trở nên không xác định và vô nghĩa.