sqlpostgresqlinsertupsert

INSERT ... ON CONFLICT DO NOTHING no PostgreSQL: insercoes idempotentes sem erros

Como ON CONFLICT DO NOTHING torna o INSERT idempotente, como o conflict target difere do nome de uma restricao, por que o RETURNING ignora linhas puladas e como semear dados em lote.

3 min de leituraReferencesql · postgresql · insert · upsert · idempotency

ON CONFLICT DO NOTHING transforma o INSERT em uma operacao segura e repetivel: se uma linha viola uma restricao unica, o PostgreSQL nao quebra com erro, apenas a pula em silencio. E o cavalo de batalha de migracoes idempotentes, seeders e tarefas em segundo plano que podem rodar duas vezes.

A insercao idempotente

Um INSERT comum de uma chave duplicada lanca duplicate key value violates unique constraint e reverte o comando inteiro. Adicione ON CONFLICT DO NOTHING e a linha em conflito simplesmente sai do conjunto a inserir, enquanto as demais passam.

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

Rode esta consulta dez vezes seguidas e o estado da tabela depois da primeira nunca muda. Isso e idempotencia: o resultado depende dos dados, nao de quantas vezes voce disparou o comando.

  • O comando nao falha: a transacao continua viva, sem ROLLBACK.
  • Apenas a linha em conflito e pulada, nao o lote inteiro.
  • A linha existente fica intacta -- seus valores permanecem como estavam.

Conflict target versus nome da restricao

O PostgreSQL precisa saber qual conflito voce esta disposto a ignorar. Ha duas formas de explicitar isso.

A primeira e um conflict target: uma lista de colunas (ou uma expressao) sustentada por um indice unico.

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

A segunda e um nome de restricao explicito via ON CONSTRAINT:

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

E ha uma terceira forma, a mais ampla -- ON CONFLICT DO NOTHING sem nenhum alvo: entao o PostgreSQL silencia um conflito em qualquer restricao unica ou chave primaria.

Pegadinha: o DO NOTHING sem alvo e perigoso. Se a tabela tem ao mesmo tempo PRIMARY KEY (id) e UNIQUE (email), a forma sem alvo engole um conflito em qualquer uma das duas. Uma linha com um id novo mas um email ja ocupado some em silencio, e voce assume que a insercao deu certo. Nomeie um alvo especifico quando o motivo do descarte importar.

RETURNING ignora as linhas puladas

RETURNING devolve apenas as linhas que foram realmente inseridas. As linhas puladas por um conflito nunca aparecem na saida -- uma fonte frequente de confusao.

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

Se id = 5001 ja existe, a consulta retorna zero linhas. No nivel da aplicacao isso e pratico: um RETURNING vazio significa "a linha ja estava la". Se voce quer a linha tendo ela sido inserida ou nao, precisa de um SELECT separado ou de um DO UPDATE (veja o artigo sobre upsert).

  • Inserida -- a linha esta no RETURNING.
  • Pulada por conflito -- nao ha linha.
  • O GET DIAGNOSTICS / rowcount do driver tambem informa 0 para um descarte.

Seed de dados em lote

O cenario mais comum e carregar um conjunto de referencia sem saber quais ja existem. DO NOTHING elimina a necessidade de WHERE NOT EXISTS e das condicoes de corrida que vem junto.

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;

Detalhes uteis para insercoes em lote:

  • Dentro de um unico comando, duplicatas no conjunto de entrada tambem sao filtradas: a primeira linha vence, as posteriores com a mesma chave sao puladas.
  • E seguro sob concorrencia: uma insercao concorrente da mesma chave nao derruba o seu INSERT.
  • Combina bem com INSERT ... SELECT para mover dados de um staging para a tabela principal sem duplicatas.

Outros motores escrevem isso de outra forma. No MySQL o analogo mais proximo e INSERT IGNORE INTO ..., mas ele tambem silencia alguns erros nao relacionados (truncamento, valores invalidos), entao e menos preciso. No ClickHouse a unicidade geralmente nao e imposta na insercao: a deduplicacao e feita pelo motor ReplacingMergeTree durante as mesclagens, de forma assincrona, e nao no momento do INSERT.

Quando voce precisa atualizar a linha existente em vez de pula-la, isso e ON CONFLICT ... DO UPDATE (upsert): uma ferramenta separada com sua propria semantica e a pseudotabela EXCLUDED, tratada em seu proprio artigo.

Pratique com exercícios reais

Resolva exercícios no treinador de SQL com correção instantânea e dicas.

Abrir o treinador