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.
ON CONFLICT DO NOTHINGtransforma oINSERTem 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
INSERTcomum de uma chave duplicada lancaduplicate key value violates unique constrainte reverte o comando inteiro. AdicioneON CONFLICT DO NOTHINGe 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.
ROLLBACK.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 NOTHINGsem nenhum alvo: entao o PostgreSQL silencia um conflito em qualquer restricao unica ou chave primaria.RETURNING ignora as linhas puladas
RETURNINGdevolve 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 = 5001ja existe, a consulta retorna zero linhas. No nivel da aplicacao isso e pratico: umRETURNINGvazio significa "a linha ja estava la". Se voce quer a linha tendo ela sido inserida ou nao, precisa de umSELECTseparado ou de umDO UPDATE(veja o artigo sobre upsert).RETURNING.GET DIAGNOSTICS/rowcountdo 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 NOTHINGelimina a necessidade deWHERE NOT EXISTSe 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:
INSERT.INSERT ... SELECTpara 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 motorReplacingMergeTreedurante as mesclagens, de forma assincrona, e nao no momento doINSERT.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 pseudotabelaEXCLUDED, tratada em seu proprio artigo.