sqlpostgresqlinsertupsert

INSERT ... ON CONFLICT DO NOTHING in PostgreSQL: Idempotent Inserts Without Errors

How ON CONFLICT DO NOTHING makes INSERT idempotent, how a conflict target differs from a constraint name, why RETURNING stays silent on skipped rows, and how to seed data in bulk.

11 Min. LesezeitReferencesql · postgresql · insert · upsert · idempotency
Dieser Artikel ist derzeit auf Russisch — die englische Übersetzung ist in Arbeit.

Обычный INSERT в PostgreSQL падает с ошибкой, если вы пытаетесь вставить строку, которая нарушает уникальность.

Например, в таблице users есть уникальный email:

CREATE TABLE users (
  id      bigint PRIMARY KEY,
  email   text NOT NULL UNIQUE,
  name    text,
  country text
);

Если строка с таким email уже есть, обычный INSERT упадёт:

INSERT INTO users (email, name, country)
VALUES ('ada@example.com', 'Ada', 'GB');

Ошибка будет примерно такая:

duplicate key value violates unique constraint

Иногда это нормальное поведение. Но часто нам нужно другое:

если строка уже есть — просто пропусти её и не ломай весь процесс.

Для этого в PostgreSQL есть конструкция:

ON CONFLICT DO NOTHING

Она превращает вставку в безопасную повторяемую операцию.

Что делает ON CONFLICT DO NOTHING

ON CONFLICT DO NOTHING говорит PostgreSQL:

попробуй вставить строку; если она конфликтует с уникальным ограничением — ничего не делай.

Пример:

INSERT INTO users (id, email, name, country)
VALUES (1, 'ada@example.com', 'Ada', 'GB')
ON CONFLICT (id) DO NOTHING;

Если пользователя с id = 1 ещё нет, строка вставится.

Если пользователь с id = 1 уже есть, PostgreSQL не упадёт с ошибкой. Он просто пропустит вставку.

Существующая строка при этом не изменится.

То есть DO NOTHING — это не обновление. Это именно:

если можно вставить — вставь
если нельзя из-за конфликта — пропусти

Зачем это нужно

ON CONFLICT DO NOTHING часто используют там, где операция может выполниться несколько раз.

Например:

  • сидинг справочников;
  • миграции;
  • повторяемые SQL-скрипты;
  • фоновые задачи;
  • импорт данных;
  • обработка событий;
  • вставка записей из очереди;
  • создание дефолтных настроек;
  • защита от повторного запуска одного и того же процесса.

Главная идея — идемпотентность.

Идемпотентная операция — это операция, которую можно выполнить несколько раз, а итоговое состояние останется таким же, как после первого успешного запуска.

Например:

INSERT INTO users (id, email, name, country)
VALUES (1, 'ada@example.com', 'Ada', 'GB')
ON CONFLICT (id) DO NOTHING;

Если выполнить этот запрос 10 раз, после первого запуска таблица уже будет содержать нужную строку. Остальные запуски ничего не изменят и не упадут с ошибкой.

Это очень удобно в деплое: скрипт можно перезапустить после сбоя, и он не начнёт ломаться на уже созданных строках.

Обычный INSERT против ON CONFLICT DO NOTHING

Обычный INSERT:

INSERT INTO users (id, email, name)
VALUES (1, 'ada@example.com', 'Ada');

Если id = 1 уже существует, команда упадёт.

А если это часть транзакции, ошибка может испортить всю транзакцию, и дальше придётся делать ROLLBACK.

С ON CONFLICT DO NOTHING:

INSERT INTO users (id, email, name)
VALUES (1, 'ada@example.com', 'Ada')
ON CONFLICT (id) DO NOTHING;

Если конфликт есть, команда не падает.

Она просто вставит 0 строк.

Важно:

  • транзакция остаётся живой;
  • существующая строка не обновляется;
  • конфликтующая строка пропускается;
  • остальные строки в массовой вставке могут вставиться.

Пример с уникальным email

Допустим, в таблице есть ограничение:

CREATE TABLE users (
  id    bigserial PRIMARY KEY,
  email text NOT NULL UNIQUE,
  name  text
);

Теперь можно сделать безопасную вставку по email:

INSERT INTO users (email, name)
VALUES ('ada@example.com', 'Ada')
ON CONFLICT (email) DO NOTHING;

Если email свободен — строка вставится.

Если email уже занят — PostgreSQL ничего не сделает.

Такой запрос можно использовать для создания пользователя, если он ещё не существует.

Но важно понимать: если пользователь уже есть, его имя не обновится.

Например, если в таблице уже лежит:

email           | name
----------------+------
ada@example.com | Ada Old

а вы выполните:

INSERT INTO users (email, name)
VALUES ('ada@example.com', 'Ada New')
ON CONFLICT (email) DO NOTHING;

результат останется прежним:

email           | name
----------------+------
ada@example.com | Ada Old

DO NOTHING не меняет существующую строку.

Если нужно обновить существующую строку, это уже другая конструкция:

ON CONFLICT ... DO UPDATE

Conflict target: какой конфликт игнорируем

В запросе:

ON CONFLICT (email) DO NOTHING

часть:

(email)

называется conflict target.

Она говорит PostgreSQL:

если конфликт произошёл по уникальности email, пропусти строку.

Пример:

INSERT INTO users (email, name)
VALUES ('ada@example.com', 'Ada')
ON CONFLICT (email) DO NOTHING;

Чтобы это работало, на email должен быть уникальный индекс или уникальное ограничение.

Например:

CREATE UNIQUE INDEX users_email_idx
ON users (email);

или:

CREATE TABLE users (
  id    bigserial PRIMARY KEY,
  email text NOT NULL UNIQUE,
  name  text
);

Если уникальности на email нет, PostgreSQL не примет такой ON CONFLICT.

Он не может игнорировать конфликт, которого с точки зрения базы не существует.

Конфликт по PRIMARY KEY

Самый частый вариант — конфликт по первичному ключу.

INSERT INTO employees (id, name, dept)
VALUES (1, 'Grace', 'eng')
ON CONFLICT (id) DO NOTHING;

Если сотрудник с id = 1 уже есть, строка будет пропущена.

Это удобно для сидеров и справочников, где id заранее фиксированы.

Например:

INSERT INTO roles (id, code, title)
VALUES
  (1, 'admin', 'Administrator'),
  (2, 'manager', 'Manager'),
  (3, 'student', 'Student')
ON CONFLICT (id) DO NOTHING;

Такой сидер можно запускать повторно.

ON CONSTRAINT: конфликт по имени ограничения

Иногда удобнее указать не список колонок, а имя ограничения.

Например, таблица:

CREATE TABLE users (
  id    bigserial PRIMARY KEY,
  email text NOT NULL,
  name  text,
  CONSTRAINT users_email_key UNIQUE (email)
);

Можно написать:

INSERT INTO users (email, name)
VALUES ('ada@example.com', 'Ada')
ON CONFLICT ON CONSTRAINT users_email_key DO NOTHING;

Это полезно, если:

  • ограничение уже имеет понятное имя;
  • уникальность сложная;
  • уникальный индекс построен по нескольким колонкам;
  • вы хотите явно привязаться к конкретному constraint.

Например:

ON CONFLICT ON CONSTRAINT users_email_key DO NOTHING

читается как:

если конфликт случился по ограничению users_email_key, ничего не делай.

Конфликт по нескольким колонкам

Уникальность может быть составной.

Например, один пользователь не может иметь две настройки с одинаковым ключом:

CREATE TABLE user_settings (
  user_id bigint NOT NULL,
  key     text NOT NULL,
  value   text,
  UNIQUE (user_id, key)
);

Тогда безопасная вставка выглядит так:

INSERT INTO user_settings (user_id, key, value)
VALUES (1, 'language', 'ru')
ON CONFLICT (user_id, key) DO NOTHING;

Если у пользователя 1 уже есть настройка language, вставка будет пропущена.

Если такого сочетания ещё нет, строка вставится.

Важно: конфликт здесь не по user_id отдельно и не по key отдельно, а именно по паре:

(user_id, key)

DO NOTHING без conflict target

PostgreSQL позволяет написать так:

INSERT INTO users (id, email, name)
VALUES (1, 'ada@example.com', 'Ada')
ON CONFLICT DO NOTHING;

Здесь мы не указали, по какому именно ограничению игнорировать конфликт.

В таком виде PostgreSQL проигнорирует конфликт по любому подходящему уникальному ограничению или первичному ключу.

На первый взгляд удобно, но часто опасно.

Допустим, в таблице есть:

PRIMARY KEY (id)
UNIQUE (email)

Вы вставляете:

INSERT INTO users (id, email, name)
VALUES (2, 'ada@example.com', 'Ada Clone')
ON CONFLICT DO NOTHING;

Если id = 2 новый, но email = 'ada@example.com' уже занят, строка тоже будет молча пропущена.

Вы можете подумать, что всё хорошо, хотя на самом деле сработал конфликт по email, а не по id.

Поэтому правило:

Если вам важна причина пропуска, указывайте conflict target явно.

Лучше:

ON CONFLICT (id) DO NOTHING

или:

ON CONFLICT (email) DO NOTHING

чем слишком широкое:

ON CONFLICT DO NOTHING

RETURNING показывает только вставленные строки

RETURNING в INSERT возвращает строки, которые реально были вставлены.

Пример:

INSERT INTO orders (id, user_id, amount, status)
VALUES (5001, 1, 99.90, 'paid')
ON CONFLICT (id) DO NOTHING
RETURNING id, status;

Если заказа 5001 ещё не было, результат будет:

id   | status
-----+-------
5001 | paid

Если заказ 5001 уже существует, вставка будет пропущена, а RETURNING вернёт 0 строк.

То есть:

строка вставилась      -> RETURNING вернул строку
строка была пропущена  -> RETURNING ничего не вернул

Это удобно в приложении.

Можно понять, была ли реально вставка, без отдельного запроса.

DO NOTHING не возвращает существующую строку

Важный момент: если строка уже была, DO NOTHING RETURNING не вернёт существующую строку.

Например:

INSERT INTO users (email, name)
VALUES ('ada@example.com', 'Ada')
ON CONFLICT (email) DO NOTHING
RETURNING id, email, name;

Если пользователь уже есть, результат будет пустым.

Не будет так:

id | email           | name
---+-----------------+-----
1  | ada@example.com | Ada

Почему?

Потому что RETURNING возвращает только строки, затронутые самим INSERT.

А при DO NOTHING конфликтующая строка не вставляется и не обновляется.

Если нужно получить строку независимо от того, была она вставлена или уже существовала, есть два варианта:

  1. сделать отдельный SELECT;
  2. использовать ON CONFLICT ... DO UPDATE, если это подходит по смыслу.

Массовая вставка с DO NOTHING

ON CONFLICT DO NOTHING особенно полезен для массовых вставок.

Например, сидинг сотрудников:

INSERT INTO employees (id, name, manager_id, dept, salary)
VALUES
  (1, 'Grace', NULL, 'eng', 180000),
  (2, 'Linus', 1,    'eng', 150000),
  (3, 'Margaret', 1, 'eng', 150000)
ON CONFLICT (id) DO NOTHING;

Если часть строк уже есть, они будут пропущены.

Если часть строк новая, они вставятся.

Например:

id = 1 уже есть    -> пропустить
id = 2 уже есть    -> пропустить
id = 3 отсутствует -> вставить

Команда не падает целиком из-за одного дубля.

Это удобно для справочников, ролей, тарифов, дефолтных настроек и тестовых данных.

Дубликаты внутри одной пачки

Допустим, в одном INSERT случайно пришли две строки с одинаковым ключом:

INSERT INTO roles (id, code)
VALUES
  (1, 'admin'),
  (1, 'administrator')
ON CONFLICT (id) DO NOTHING;

Одна строка может вставиться, а другая будет пропущена из-за конфликта.

Но лучше не полагаться на такие дубликаты как на нормальную бизнес-логику.

Если порядок и выбор «какая строка победит» важны, лучше заранее очистить входные данные.

Например, через DISTINCT ON, ROW_NUMBER или отдельный staging-этап.

DO NOTHING — это защита от дублей, а не инструмент выбора правильной версии данных.

INSERT ... SELECT с DO NOTHING

Частый сценарий — перелить данные из staging-таблицы в основную.

Например, есть временная таблица staging_users:

email           | name | country
----------------+------+--------
ada@example.com | Ada  | GB
bob@example.com | Bob  | US

Основная таблица users имеет уникальный email.

Можно вставить только новых пользователей:

INSERT INTO users (email, name, country)
SELECT
  email,
  name,
  country
FROM staging_users
ON CONFLICT (email) DO NOTHING;

Если пользователь с таким email уже есть, строка пропускается.

Если email новый, строка вставляется.

Это часто лучше, чем писать:

WHERE NOT EXISTS (...)

потому что ON CONFLICT безопаснее при параллельных вставках.

Почему ON CONFLICT лучше, чем WHERE NOT EXISTS

Иногда пытаются сделать так:

INSERT INTO users (email, name)
SELECT 'ada@example.com', 'Ada'
WHERE NOT EXISTS (
  SELECT 1
  FROM users
  WHERE email = 'ada@example.com'
);

На первый взгляд логично:

если пользователя нет — вставь.

Но при параллельной работе возможна гонка.

Два процесса одновременно проверили:

пользователя нет

и оба попытались вставить.

Один вставит, второй упадёт на уникальном ограничении.

ON CONFLICT DO NOTHING решает это аккуратнее:

INSERT INTO users (email, name)
VALUES ('ada@example.com', 'Ada')
ON CONFLICT (email) DO NOTHING;

Уникальный индекс становится источником правды.

Если другой процесс успел вставить такую строку первым, текущий запрос просто пропустит вставку.

Главная мысль:

Не проверка перед вставкой защищает от дублей, а уникальное ограничение плюс ON CONFLICT.

Что происходит с триггерами

При ON CONFLICT DO NOTHING существующая строка не обновляется.

Поэтому триггеры на UPDATE не вызываются.

Например, не будет логики вида:

BEFORE UPDATE
AFTER UPDATE

Потому что обновления нет.

Но важно понимать: это не значит, что вообще никакие триггеры никогда не сработают. Логика INSERT и проверка конфликтов имеют свои нюансы, особенно если есть BEFORE INSERT-триггеры.

Практическое правило проще:

DO NOTHING не меняет существующую строку и не запускает update-логику.

Если вам нужно обновлять updated_at, имя, статус или другие поля при конфликте, нужен не DO NOTHING, а DO UPDATE.

DO NOTHING не проверяет бизнес-равенство

Допустим, у вас есть пользователь:

email           | name
----------------+------
ada@example.com | Ada Old

Вы выполняете:

INSERT INTO users (email, name)
VALUES ('ada@example.com', 'Ada New')
ON CONFLICT (email) DO NOTHING;

PostgreSQL не будет проверять, совпадает ли name.

Он просто увидит конфликт по email и пропустит строку.

В результате Ada Old останется.

Это правильно для DO NOTHING.

Если бизнес-логика говорит:

если email уже есть, обнови name

тогда нужен upsert:

INSERT INTO users (email, name)
VALUES ('ada@example.com', 'Ada New')
ON CONFLICT (email) DO UPDATE
SET name = EXCLUDED.name;

DO NOTHING подходит только тогда, когда существующая строка должна остаться как есть.

DO NOTHING и NOT NULL / CHECK / FOREIGN KEY

ON CONFLICT DO NOTHING помогает только с конфликтами уникальности.

Он не глушит любые ошибки.

Например, если колонка email обязательная:

email text NOT NULL

то такой запрос всё равно упадёт:

INSERT INTO users (email, name)
VALUES (NULL, 'Ada')
ON CONFLICT (email) DO NOTHING;

Потому что это нарушение NOT NULL, а не уникальный конфликт.

То же самое с CHECK:

salary numeric CHECK (salary > 0)

Если вставить отрицательную зарплату, DO NOTHING не спасёт.

И с внешними ключами тоже:

user_id REFERENCES users(id)

Если вы вставляете заказ с несуществующим user_id, это не конфликт уникальности. Это ошибка внешнего ключа.

Главное правило:

ON CONFLICT DO NOTHING игнорирует конфликты уникальности, но не превращает любой плохой INSERT в успешный.

Частичная уникальность

В PostgreSQL бывают частичные уникальные индексы.

Например, только один активный промокод с конкретным кодом:

CREATE UNIQUE INDEX promo_codes_active_code_idx
ON promo_codes (code)
WHERE active;

Для таких случаев conflict target может включать условие индекса.

Например:

INSERT INTO promo_codes (code, discount, active)
VALUES ('SUMMER', 20, true)
ON CONFLICT (code) WHERE active DO NOTHING;

Так PostgreSQL понимает, что речь идёт о частичном уникальном индексе.

Это уже более продвинутый сценарий, но он важен: ON CONFLICT должен совпадать с реальным уникальным индексом, включая его условие, если индекс частичный.

DO NOTHING и конкурентные вставки

ON CONFLICT DO NOTHING хорошо работает при параллельных вставках.

Например, два воркера одновременно пытаются вставить одно и то же событие:

INSERT INTO processed_events (event_id, processed_at)
VALUES ('evt_123', now())
ON CONFLICT (event_id) DO NOTHING;

Если event_id уникальный, один воркер вставит строку, второй пропустит.

Оба запроса завершатся без ошибки.

Это полезно для идемпотентной обработки событий:

  • webhook может прийти дважды;
  • очередь может переотдать сообщение;
  • фоновой процесс может перезапуститься;
  • задача может быть выполнена повторно.

Уникальное ограничение защищает таблицу от дублей, а DO NOTHING защищает приложение от падения на ожидаемом повторе.

Пример: таблица обработанных событий

Допустим, есть таблица:

CREATE TABLE processed_events (
  event_id     text PRIMARY KEY,
  processed_at timestamptz NOT NULL DEFAULT now()
);

Перед обработкой события можно попытаться вставить его id:

INSERT INTO processed_events (event_id)
VALUES ('evt_123')
ON CONFLICT (event_id) DO NOTHING
RETURNING event_id;

Если RETURNING вернул строку — событие новое, можно обрабатывать.

Если RETURNING пустой — событие уже было, его можно пропустить.

Пример логики:

RETURNING вернул event_id -> обрабатываем событие
RETURNING пустой          -> событие уже обработано

Это простой паттерн для идемпотентных воркеров.

Как понять, сколько строк вставилось

На стороне приложения драйвер обычно показывает количество затронутых строк.

Для ON CONFLICT DO NOTHING:

строка вставилась     -> rowcount = 1
строка пропущена      -> rowcount = 0

Для массовой вставки:

вставилось 7 строк из 10 -> rowcount = 7

Также можно использовать RETURNING, чтобы получить именно вставленные строки:

INSERT INTO users (email, name)
VALUES
  ('ada@example.com', 'Ada'),
  ('bob@example.com', 'Bob')
ON CONFLICT (email) DO NOTHING
RETURNING id, email;

В результате будут только новые пользователи.

Практические шаблоны

Вставить пользователя, если email свободен

INSERT INTO users (email, name)
VALUES ('ada@example.com', 'Ada')
ON CONFLICT (email) DO NOTHING;

Вставить строку по primary key

INSERT INTO employees (id, name, dept)
VALUES (1, 'Grace', 'eng')
ON CONFLICT (id) DO NOTHING;

Вставить настройку пользователя

INSERT INTO user_settings (user_id, key, value)
VALUES (1, 'language', 'ru')
ON CONFLICT (user_id, key) DO NOTHING;

Использовать имя ограничения

INSERT INTO users (email, name)
VALUES ('ada@example.com', 'Ada')
ON CONFLICT ON CONSTRAINT users_email_key DO NOTHING;

Массовый сидинг ролей

INSERT INTO roles (id, code, title)
VALUES
  (1, 'admin', 'Administrator'),
  (2, 'manager', 'Manager'),
  (3, 'student', 'Student')
ON CONFLICT (id) DO NOTHING;

Перелить данные из staging

INSERT INTO users (email, name, country)
SELECT
  email,
  name,
  country
FROM staging_users
ON CONFLICT (email) DO NOTHING;

Узнать, вставилась строка или уже была

INSERT INTO processed_events (event_id)
VALUES ('evt_123')
ON CONFLICT (event_id) DO NOTHING
RETURNING event_id;

Игнорировать любой уникальный конфликт

INSERT INTO users (id, email, name)
VALUES (1, 'ada@example.com', 'Ada')
ON CONFLICT DO NOTHING;

Этот вариант лучше использовать осторожно, потому что он гасит конфликт по любому уникальному ограничению.

MySQL: INSERT IGNORE

В MySQL ближайший похожий инструмент:

INSERT IGNORE INTO users (email, name)
VALUES ('ada@example.com', 'Ada');

Но это не полный аналог PostgreSQL ON CONFLICT DO NOTHING.

INSERT IGNORE в MySQL может подавлять не только дубликаты, но и некоторые другие ошибки или предупреждения, например проблемы с данными в зависимости от режима SQL.

Поэтому он менее точный.

В PostgreSQL ON CONFLICT DO NOTHING явно говорит:

игнорируй именно конфликт уникальности.

Для обновления при конфликте в MySQL часто используют:

INSERT ... ON DUPLICATE KEY UPDATE

Но это уже аналог upsert, а не DO NOTHING.

ClickHouse

В ClickHouse другая модель.

Обычно ClickHouse не проверяет уникальность при каждой вставке так, как PostgreSQL.

Дедупликация зависит от движка таблицы и настроек.

Например, ReplacingMergeTree может схлопывать версии строк при фоновых merge-процессах, но это происходит не как строгий уникальный constraint в момент INSERT.

То есть PostgreSQL-паттерн:

ON CONFLICT DO NOTHING

в ClickHouse напрямую не переносится.

Если нужна идемпотентность, её обычно проектируют через:

  • ключи вставки;
  • движок таблицы;
  • дедупликацию блоков;
  • материализованные представления;
  • отдельную логику загрузки;
  • периодические merge-процессы.

Поэтому при переносе из PostgreSQL в ClickHouse важно не просто заменить синтаксис, а пересмотреть модель записи.

Частые ошибки

Используют DO NOTHING и ждут обновления

Запрос:

INSERT INTO users (email, name)
VALUES ('ada@example.com', 'Ada New')
ON CONFLICT (email) DO NOTHING;

не обновит name.

Если строка уже есть, она останется как была.

Для обновления нужен:

ON CONFLICT (email) DO UPDATE

Не указали conflict target

Так можно:

ON CONFLICT DO NOTHING

Но это может скрыть конфликт не по тому ограничению, которое вы ожидали.

Лучше явно:

ON CONFLICT (email) DO NOTHING

или:

ON CONFLICT (id) DO NOTHING

Думают, что RETURNING вернёт существующую строку

Не вернёт.

INSERT INTO users (email, name)
VALUES ('ada@example.com', 'Ada')
ON CONFLICT (email) DO NOTHING
RETURNING id;

Если email уже существует, результат будет пустым.

Нет уникального ограничения

Такой запрос требует уникальности на email:

ON CONFLICT (email) DO NOTHING

Если уникального индекса или ограничения нет, PostgreSQL выдаст ошибку.

ON CONFLICT не ищет дубли магически. Ему нужен реальный уникальный механизм в схеме.

Используют DO NOTHING вместо нормальной модели данных

DO NOTHING — полезный инструмент, но он не должен скрывать проблемы.

Если дубли появляются из-за ошибки бизнес-логики, лучше разобраться с причиной.

DO NOTHING хорош для ожидаемых повторов:

  • повторный сидер;
  • повторный webhook;
  • повторная миграция;
  • параллельная попытка вставить тот же ключ.

Но если дубли «случайно откуда-то берутся», не стоит просто молча их гасить.

Что важно запомнить

ON CONFLICT DO NOTHING делает INSERT безопасным при конфликте уникальности.

Пример:

INSERT INTO users (email, name)
VALUES ('ada@example.com', 'Ada')
ON CONFLICT (email) DO NOTHING;

Главные правила:

  • работает только при конфликте с уникальным индексом, unique constraint или primary key;
  • конфликтующая строка пропускается;
  • существующая строка не обновляется;
  • транзакция не падает из-за ожидаемого дубля;
  • в массовой вставке новые строки вставляются, конфликтующие пропускаются;
  • RETURNING возвращает только реально вставленные строки;
  • если строка пропущена, RETURNING будет пустым;
  • без conflict target PostgreSQL гасит любой подходящий уникальный конфликт;
  • лучше указывать конкретный target, если важна причина пропуска;
  • для обновления при конфликте нужен ON CONFLICT ... DO UPDATE;
  • DO NOTHING не глушит ошибки NOT NULL, CHECK или внешних ключей.

Короткий вывод

INSERT ... ON CONFLICT DO NOTHING нужен, когда вставка должна быть повторяемой и безопасной.

Например:

INSERT INTO processed_events (event_id)
VALUES ('evt_123')
ON CONFLICT (event_id) DO NOTHING
RETURNING event_id;

Если событие новое — строка вставится.

Если событие уже было — PostgreSQL ничего не сделает и не упадёт.

Главная мысль:

DO NOTHING превращает ожидаемый дубль из ошибки в безопасный пропуск.

Это отличный инструмент для сидеров, миграций, фоновых задач, webhook-обработчиков и любых сценариев, где одна и та же вставка может произойти повторно.

Но используйте его осознанно: указывайте конкретный conflict target, не ждите обновления существующей строки и не забывайте, что RETURNING покажет только реально вставленные данные.

Übe an echten Aufgaben

Löse Aufgaben im SQL-Trainer mit sofortiger Bewertung und Hinweisen.

Trainer öffnen