Voltar para o conteúdo

UPSERT sem duplicidade: o mínimo para uma carga incremental confiável

Como estruturar um UPSERT simples e seguro para carga incremental sem duplicar registros, perder recência nem reprocessar a base inteira no escuro.

Raphael Carvalho · 07 de jun. de 2026 · 7 min de leitura

Resumo rápido

  • UPSERT só funciona bem quando chave, recência e staging estão sob controle.
  • Aplicar MERGE direto em origem suja é um atalho para duplicidade e sobrescrita errada.
  • Em base pequena, full refresh ainda pode ser a decisão mais segura.

Quando a base deixa de ser brinquedo e passa a atualizar todo dia, aparece uma decisão que muda a qualidade do seu dado: você vai reprocessar tudo ou atualizar só o que mudou?

O UPSERT existe para resolver isso. O problema é que muita gente pula direto para o MERGE e trata a parte difícil como detalhe. Não é detalhe. A parte difícil é decidir qual linha vale, como a origem chega e o que fazer quando ela chega errada.

Antes do UPSERT, três perguntas precisam estar respondidas

Você precisa saber:

  1. qual é a chave do registro;
  2. qual versão vence em caso de conflito;
  3. o que fazer com atraso, correção e ausência no lote.

Sem essas respostas, a carga incremental não está modelada. Está só empurrando linha de um lado para o outro.

O erro clássico é confiar demais na origem crua

Um dos atalhos mais perigosos é aplicar MERGE diretamente em uma tabela que já chega com duplicidade, atraso ou registro corrigido.

Se a staging traz duas versões do mesmo pedido, por exemplo, o banco pode:

  • atualizar mais de uma vez;
  • escolher uma versão errada;
  • falhar por conflito;
  • consolidar um histórico que ninguém pretendia manter.

É por isso que UPSERT ruim quase sempre nasce de staging ruim.

O fluxo mínimo que segura a carga

O desenho mais simples continua sendo o mais útil:

  • origem bruta;
  • staging tratada;
  • tabela final.

A origem bruta guarda o lote como ele chegou. A staging resolve padronização, deduplicação e regra de recência. A tabela final recebe só o que já está pronto para ser consolidado.

Essa separação parece burocracia até o dia em que alguém precisa explicar por que um pedido voltou para o status antigo.

A regra de recência precisa ser explícita

Imagine uma chave pedido_id com dois registros no mesmo lote. Um veio às 09:00, outro às 11:00. Qual deles vale?

Se você não responder isso antes, o banco vai aceitar a ambiguidade do jeito que a sua implementação permitir. E a tabela final vai parecer correta até o momento em que alguém comparar com a operação.

Na maioria dos cenários, o caminho mais seguro é deduplicar antes do UPSERT, usando uma coluna como updated_at para manter apenas a versão mais recente por chave.

O MERGE é a etapa visível, não a proteção principal

Com a origem já tratada, a escrita fica parecida com isto:

MERGE INTO pedidos_final AS destino
USING staging_pedidos AS origem
  ON destino.pedido_id = origem.pedido_id
WHEN MATCHED THEN
  UPDATE SET
    status = origem.status,
    valor = origem.valor,
    updated_at = origem.updated_at
WHEN NOT MATCHED THEN
  INSERT (pedido_id, status, valor, updated_at)
  VALUES (origem.pedido_id, origem.status, origem.valor, origem.updated_at);

O MERGE importa, mas ele não compensa uma origem mal resolvida. Se a staging ainda estiver ambígua, você só vai formalizar o erro com mais velocidade.

Tem um ponto cego que quase sempre fica de fora

Nem toda fonte manda delete explícito. Às vezes o registro some. Às vezes muda de status. Às vezes o lote chega parcial.

Por isso, UPSERT sozinho não responde tudo. Você ainda precisa decidir:

  • ausência no lote significa exclusão;
  • ausência significa atraso de ingestão;
  • ausência significa que a origem mandou só um recorte.

Essa conversa não é detalhe de engenharia. Ela muda o significado do dado final.

Validação depois da carga continua obrigatória

Depois do UPSERT, confira pelo menos:

  • quantidade de chaves únicas na tabela final;
  • duplicidade por chave;
  • quantos registros entraram;
  • quantos foram atualizados;
  • se alguma métrica de controle saiu do intervalo esperado.

Se a tabela final passou a ter mais de uma linha por chave, a carga falhou, mesmo que a execução tenha terminado sem erro.

Quando full refresh ainda é a melhor decisão

Tem um pragmatismo importante aqui: nem toda base precisa de UPSERT.

Se o volume é pequeno, a janela é curta e reprocessar custa pouco, full refresh ainda pode ser melhor. Menos elegante, talvez. Mais confiável, muitas vezes.

O erro não é escolher full refresh. O erro é escolher incremental só porque parece mais avançado.

O mínimo confiável

Se você quiser resumir o padrão sem floreio, ele cabe nesta sequência:

origem bruta -> staging deduplicada -> UPSERT -> validação

É simples. E justamente por isso resolve bastante coisa. Boa carga incremental não nasce de sofisticação. Nasce de critério.

SQL para criação de tabelas e ETL

Aprenda a criar estruturas, manipular dados e montar fluxos simples de ETL em SQL.

Avançar em SQL operacional

Perguntas frequentes

UPSERT é sempre a melhor opção?

Não. Em algumas bases pequenas, uma carga completa continua mais simples e mais robusta.

Qual o maior risco do UPSERT?

Duplicidade ou sobrescrita errada causada por chave mal definida, origem repetida ou regra de recência mal resolvida.

Preciso de MERGE para fazer UPSERT?

Não obrigatoriamente. MERGE ajuda bastante, mas a lógica também pode ser implementada com etapas separadas dependendo do banco.

Curso em português para brasileiros

SQL do Zero ao Avançado

A plataforma interativa de SQL feita para analistas. Pare de depender da fila de engenharia de dados.

Conheça o curso

Advertisement: SQL do Zero ao Avançado

Sobre o autor

Raphael Carvalho

Founder & Principal Consultant

Compartilhar Twitter LinkedIn

Leituras recomendadas