Trước hết phải xác định thế nào là trùng: trùng toàn bộ các cột, hay cùng khoá nghiệp vụ (order_id) nhưng khác phiên bản. Hai trường hợp xử lý khác nhau.
Trùng toàn bộ cột: SELECT DISTINCT là đủ.
Cùng khoá, nhiều phiên bản: đánh số từng dòng trong nhóm cùng khoá bằng ROW_NUMBER(), giữ dòng số 1.
SELECT *
FROM (
SELECT s.*,
ROW_NUMBER() OVER (
PARTITION BY order_id
ORDER BY updated_at DESC, ingested_at DESC
) AS rn
FROM stg_orders s
WHERE load_date = :run_date
) t
WHERE rn = 1;BigQuery, Snowflake và Databricks có QUALIFY rn = 1, viết gọn hơn không cần subquery.
Các điểm hay bị hỏi tiếp:
- Sắp xếp phải xác định: nếu hai dòng có cùng updated_at, thêm cột phụ (ingested_at, offset Kafka) để lần chạy nào cũng chọn cùng một dòng.
- Dùng ROW_NUMBER, không dùng RANK: RANK cho hai dòng hoà cùng hạng 1 và vẫn giữ lại trùng.
- Tối ưu: chỉ dedup phần dữ liệu mới (load_date = :run_date), không quét lại cả bảng.
Lưu ý: dedup trong batch không thay thế được việc nạp idempotent. Nguồn gửi lại sự kiện đã nạp từ hôm qua thì dedup trong batch hôm nay không thấy; phải nạp vào bảng chính bằng MERGE theo khoá (hoặc chỉ chèn khoá chưa có).