Задачи на дедупликацию SQL на собеседовании
Карьерник — квиз-тренажёр в Telegram с 1500+ задач и вопросов для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.
Зачем эти задачи
Дедупликация — одна из самых частых задач на собеседовании middle-аналитика. Вас попросят найти дубли по email, по (user_id, date), удалить лишнее, оставив самую свежую запись, слить «частичные дубликаты» с разными NULL. Знание ROW_NUMBER, GROUP BY + HAVING, DISTINCT ON и ON CONFLICT — обязательно.
На практике дубликаты встречаются везде: один пользователь зарегистрировался дважды, ETL отработал повторно и задвоил транзакции, приёмка файла от партнёра пришла с повторами. Если не умеете это чистить — ваши агрегаты врут в 2 раза.
Ниже — 12 задач с разборами, которые покрывают все типовые кейсы. Решайте по порядку: от «найти дубли по одной колонке» до «merge дубликатов с сохранением свежих значений».
Задача 1. Найти дубликаты по email
SELECT email, COUNT(*) AS cnt
FROM users
GROUP BY email
HAVING COUNT(*) > 1;Задача 2. Оставить одну запись на email (последнюю)
SELECT * FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY email ORDER BY created_at DESC) AS rn
FROM users
) t
WHERE rn = 1;Задача 3. Удалить дубликаты из orders по (user_id, product_id, date)
DELETE FROM orders
WHERE order_id IN (
SELECT order_id FROM (
SELECT order_id,
ROW_NUMBER() OVER (
PARTITION BY user_id, product_id, DATE(created_at)
ORDER BY created_at
) AS rn
FROM orders
) t
WHERE rn > 1
);Задача 4. Полные дубли (все колонки одинаковые)
SELECT * FROM (
SELECT *,
COUNT(*) OVER (PARTITION BY col1, col2, col3) AS cnt
FROM TABLE
) t
WHERE cnt > 1;Задача 5. Процент дублей
SELECT
COUNT(*) AS total,
COUNT(DISTINCT email) AS unique_emails,
ROUND(100.0 * (COUNT(*) - COUNT(DISTINCT email)) / COUNT(*), 2) AS dup_pct
FROM users;Задача 6. Дубли с учётом case-insensitive
SELECT LOWER(email), COUNT(*)
FROM users
GROUP BY LOWER(email)
HAVING COUNT(*) > 1;Задача 7. Последний заказ каждого пользователя (dedupe)
SELECT DISTINCT ON (user_id) *
FROM orders
ORDER BY user_id, created_at DESC;
-- Postgres
-- universal
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
FROM orders
) t WHERE rn = 1;Задача 8. Merge дубликатов — взять самое свежее значение
WITH ranked AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY email ORDER BY updated_at DESC) AS rn
FROM users
)
SELECT email,
MAX(CASE WHEN rn = 1 THEN name END) AS latest_name,
MAX(CASE WHEN rn = 1 THEN phone END) AS latest_phone
FROM ranked
GROUP BY email;Задача 9. Дубли по «нормализованному» ключу
Учесть пробелы и регистр:
SELECT LOWER(TRIM(email)), COUNT(*)
FROM users
GROUP BY LOWER(TRIM(email))
HAVING COUNT(*) > 1;Задача 10. CTAS без дубликатов
CREATE TABLE users_clean AS
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rn
FROM users
) t
WHERE rn = 1;Задача 11. Подсчитать, сколько уникальных пользователей
SELECT COUNT(DISTINCT user_id) FROM events;Задача 12. INSERT с проверкой дубликата
-- Postgres
INSERT INTO users (email, name)
VALUES ('alice@x.com', 'Alice')
ON CONFLICT (email) DO NOTHING;
-- или UPDATE при конфликте
ON CONFLICT (email) DO UPDATE SET name = EXCLUDED.name;Частые ошибки
- DELETE без LIMIT на большой таблице → долго и блокирует.
- ROW_NUMBER без ORDER BY → недетерминистический dedupe.
- SELECT DISTINCT не уберёт «почти дубликаты» (с пробелами, регистром).
- Забыть про FK при DELETE — каскад может удалить больше.
Связанные темы
- Как найти дубликаты в SQL
- Как удалить дубликаты в SQL
- DISTINCT vs GROUP BY
- ROW_NUMBER vs RANK vs DENSE_RANK
FAQ
ROW_NUMBER или DISTINCT?
DISTINCT — если все колонки одинаковые. ROW_NUMBER — когда нужно выбрать одну из «почти дубликатов».
Performance?
Индекс на PARTITION BY колонку помогает. CREATE INDEX ON users(email).
Тренируйте SQL — откройте тренажёр с 1500+ задач для собесов.