ON CONFLICT упал на пакете: один ключ пришёл дважды
Построчная загрузка справочника работала, а после перехода на один INSERT SELECT появилась ошибка о повторном изменении строки. В пакете две записи клиента с одинаковым внешним идентификатором. Обе подходят под ON CONFLICT DO UPDATE, поэтому выглядит естественно ожидать, что последняя просто победит.
Одна команда INSERT с DO UPDATE не может затронуть одну существующую строку дважды. Последовательность отдельных команд и один массовый INSERT имеют разную семантику. Исправление заключается в выборе единственной версии каждого ключа до UPSERT, а не в перестановке строк внутри VALUES.
Как изменить решение
WITH latest AS (
SELECT DISTINCT ON (external_id)
external_id, name, source_version
FROM customer_stage
ORDER BY external_id, source_version DESC, import_id DESC
)
INSERT INTO customers AS c (external_id, name, source_version)
SELECT external_id, name, source_version FROM latest
ON CONFLICT (external_id) DO UPDATE
SET name = EXCLUDED.name,
source_version = EXCLUDED.source_version
WHERE EXCLUDED.source_version > c.source_version;Внутри пакета остаётся одна версия клиента. Условие в DO UPDATE дополнительно защищает текущую таблицу от более старого пакета, который пришёл позже. Дедупликация пакета и защита от устаревшей записи решают разные части задачи.
Версия источника надёжнее времени загрузки: повтор вчерашнего файла сегодня не должен сделать старые сведения новыми. При равной версии договоритесь, обязано ли содержимое совпадать. Если оно различается, полезнее отправить конфликт на разбор, чем молча выбрать произвольную строку.
Что проверить на своих данных
Подготовьте три проверки: два ключа в пакете, две версии одного ключа и повтор уже применённого пакета. Для последнего сравните содержимое customers до и после. Отдельный тест должен отправить меньшую source_version и убедиться, что актуальное имя не изменилось.
Если RETURNING ничего не вернул для ключа, это ещё не означает отсутствия клиента: условие WHERE в DO UPDATE могло запретить обновление. Статистику импорта стоит разделить на вставленные, изменённые и проигнорированные версии. Иначе повторная доставка будет выглядеть как потеря строк, хотя таблица уже содержит нужное состояние.
Практика дальше
Версии источника становятся особенно важны, когда загрузка регулярно повторяется. Курс Data Engineering Fast-Track связывает Kafka, Spark, lakehouse и Airflow в один проект. В практике есть схемы и контракты, зависимости загрузок и сценарий backfill — следующий уровень после исправления одного UPSERT.
Посмотреть программу и практику курса
Источник: ON CONFLICT упал на пакете — официальная документация.
Другие задачи по теме: PostgreSQL: запись, блокировки и планы запросов.
Продолжить на практике
- Data Engineering Fast‑Track: Kafka → Airflow → Spark ↗
Практический fast-track по data engineering: за 4–6 недель собираем рабочий конвейер Kafka → Spark → Lakehouse под управлением Airflow. Разберём batch и streaming, окна и watermark, схемы и контракты. Мини‑проекты, Docker Compose, шаблоны DAG и пайплайнов. Курс подойдёт инженерам, аналитикам и тем, кто хочет освоить современные пайплайны данных с уверенным Python.
