Короткий ответ
Нужно либо использовать модификатор FINAL в запросе (SELECT ... FROM table FINAL), который принудительно схлопывает дубликаты по ключу сортировки на момент чтения, либо явно дедуплицировать через ROW_NUMBER()-подобную логику (argMax/LIMIT 1 BY в ClickHouse) без FINAL, если производительность важнее.
Как это работает подробнее
Как это работает: ReplacingMergeTree асинхронно «склеивает» строки с одинаковым ключом сортировки в фоновых слияниях партиций, оставляя версию с наибольшим значением указанного столбца версии; до завершения слияния в таблице физически могут существовать несколько версий одной логической записи, и обычный SELECT без FINAL увидит их все.
Пример кода
ClickHouse: дедупликация по версии
SELECT id, argMax(status, version) AS current_status, argMax(amount, version) AS current_amount
FROM orders_replacing
GROUP BY id;Что использовать на практике
- На практике:
FINALдаёт корректный результат, но может быть существенно медленнее на больших таблицах, так как требует дополнительной работы по слиянию «на лету» во время запроса. Альтернатива — агрегировать сLIMIT 1 BY id ORDER BY version DESCили использоватьargMax(column, version)вGROUP BY id, что часто быстрее, но требует более сложного запроса. - Проверка: Проверка: сравнить количество строк результата с ожидаемым количеством уникальных
id, а также сравнить агрегат с результатомSELECT ... FROM orders_replacing FINALна небольшой выборке.
Подводные камни
- Частые ошибки: считают метрики (например,
COUNT(*),SUM) прямо по таблицеReplacingMergeTreeбезFINALи без ручной дедупликации, получая задвоенные/утроенные значения, которые на первый взгляд выглядят правдоподобно, но фактически неверны.