Trong PostgreSQL, DISTINCT ON là một kỹ thuật mạnh mẽ giúp loại bỏ dữ liệu trùng lặp dựa trên một hoặc nhiều cột. Một trong những ứng dụng phổ biến nhất là truy xuất bản ghi mới nhất theo nhóm (ví dụ: lấy giao dịch gần nhất của từng khách hàng, hoặc trạng thái cập nhật cuối cùng của từng thiết bị).
Câu lệnh DISTINCT ON trong PostgreSQL giúp giải quyết những bài toán này một cách hiệu quả và ngắn gọn, vượt trội hơn nhiều so với việc sử dụng ROW_NUMBER() hoặc GROUP BY kết hợp JOIN.
1. Cú pháp DISTINCT ON
SELECT DISTINCT ON (column_to_group_by) *
FROM table_name
ORDER BY column_to_group_by, sort_column DESC;
- column_to_group_by: Cột dùng để nhóm dữ liệu.
- sort_column: Cột dùng để xác định bản ghi nào sẽ được chọn trong mỗi nhóm (bản ghi mới nhất, lớn nhất, hoặc bất kỳ tiêu chí nào).
- Thứ tự sắp xếp trong ORDER BY ảnh hưởng trực tiếp đến kết quả chọn lọc.
2. Ví dụ thực tế
Bảng orders
order_id | customer_id | order_date | total_amount |
---|---|---|---|
1 | 101 | 2023-01-05 | 200 |
2 | 101 | 2023-03-20 | 300 |
3 | 102 | 2023-02-14 | 150 |
4 | 102 | 2023-02-18 | 180 |
Yêu cầu: Truy xuất đơn hàng mới nhất của mỗi khách hàng.
Câu lệnh:
SELECT DISTINCT ON (customer_id) *
FROM orders
ORDER BY customer_id, order_date DESC;
Kết quả:
order_id | customer_id | order_date | total_amount |
---|---|---|---|
2 | 101 | 2023-03-20 | 300 |
4 | 102 | 2023-02-18 | 180 |
Một số hệ quản trị cơ sở dữ liệu không hỗ trợ DISTINCT ON, do đó buộc phải dùng ROW_NUMBER():
SELECT *
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC) AS rn
FROM orders
) t
WHERE rn = 1;
Cách này dài hơn và ít trực quan hơn so với DISTINCT ON.
4. Các ứng dụng thực tế khác
4.1. Lấy lần đăng nhập gần nhất của mỗi người dùng
SELECT DISTINCT ON (user_id) *
FROM login_history
ORDER BY user_id, login_time DESC;
4.2. Trạng thái đơn hàng mới nhất của mỗi đơn
SELECT DISTINCT ON (order_id) *
FROM order_status_history
ORDER BY order_id, updated_at DESC;
4.3. Bản ghi có giá trị cao nhất theo từng nhóm
SELECT DISTINCT ON (category_id) *
FROM products
ORDER BY category_id, price DESC;
4.4. Lấy giao dịch cuối cùng của mỗi khách hàng để: Hiển thị số dư gần nhất, phát hiện bất thường giao dịch, phân tích hành vi giao dịch theo thời gian.
SELECT DISTINCT ON (account_id) *
FROM transactions
ORDER BY account_id, transaction_date DESC;
4.5. Lấy lịch sử nạp tiền mới nhất, hoặc gói cước đang sử dụng hiện tại cho mỗi thuê bao (isdn)
SELECT DISTINCT ON (isdn) *
FROM topup_history
ORDER BY isdn, topup_time DESC;
5. Lưu ý quan trọng
- DISTINCT ON chỉ hoạt động trên PostgreSQL – không có trên MySQL, Oracle hoặc SQL Server.
- Phải dùng ORDER BY cùng với DISTINCT ON, và cột trong DISTINCT ON phải đứng đầu trong ORDER BY.
- Không nên lạm dụng nếu bạn cần xử lý logic phức tạp hơn nhiều (lúc này hãy dùng ROW_NUMBER() hoặc LATERAL JOIN).