Условие
Файл клиентской информации содержит дубли по ID_клиента (одна сущность, разные снимки). Строки не являются полными дублями — могут отличаться по фамилии, месту работы и пр.
Написать запрос, оставляющий по каждому ID_клиента ровно одну строку. Актуальной считаем последнюю; если определить «последнюю» сложно — берём любую.
clients(ID_клиента, Фамилия, Имя, Отчество, Дата_рождения, Место_работы, Должность, ...)Решение
Подход
Если есть колонка времени (updated_at, dt_load, etl_ts) — берём последнюю по ней. Если нет — нужен любой стабильный tie-breaker (любой rowid / случайный).
Вариант 1 — есть updated_at
SELECT *
FROM (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY ID_клиента ORDER BY updated_at DESC) AS rn
FROM clients
) t
WHERE rn = 1;Вариант 2 — есть только rowid (PostgreSQL: ctid; Oracle: ROWID)
DELETE FROM clients
WHERE ctid NOT IN (
SELECT MIN(ctid)
FROM clients
GROUP BY ID_клиента
);или select-вариант:
SELECT DISTINCT ON (ID_клиента) *
FROM clients; -- PostgreSQL: возьмёт «первую» в естественном порядкеВариант 3 — нет ничего, просто «любая»
SELECT *
FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY ID_клиента ORDER BY (SELECT 1)) AS rn
FROM clients
) t
WHERE rn = 1;ORDER BY (SELECT 1) или ORDER BY 1 — недетерминированный, но рабочий.
Если в проде нужно сделать таблицу уникальной
-- Создать новую с UNIQUE-ключом, перелить туда дедуп
CREATE TABLE clients_dedup AS
SELECT DISTINCT ON (ID_клиента) *
FROM clients;
ALTER TABLE clients_dedup ADD CONSTRAINT pk_clients PRIMARY KEY (ID_клиента);Подводные камни
SELECT DISTINCT *не сработает — строки не полные дубли, отличаются хотя бы одним полем.GROUP BY ID + MAX(other_field)— ошибка. Это «склеит» поля от разных снимков (новая фамилия + старая должность) → франкенштейн.- Tie-breaker не определён — повторный запуск может дать разные результаты, что критично для воспроизводимости отчётов. Лучше всегда иметь ETL-метку времени.
- Большие таблицы.
ROW_NUMBERсортирует все строки — на миллиардах нужен индекс по(ID_клиента, updated_at DESC). - Удалять (
DELETE) или выгружать (SELECT) дедуп? Удаление в исходной таблице — необратимо. Лучше делать новую витрину.
Эталонный ответ
ROW_NUMBER() OVER (PARTITION BY ID_клиента ORDER BY updated_at DESC), фильтр rn=1. Если нет временной метки — DISTINCT ON (ID_клиента) (PostgreSQL) или любой фиктивный ORDER BY. Не использовать MAX/MIN по другим полям отдельно — это смешает данные из разных снимков.