Como fazer upsert numa tabela Delta no Databricks (MERGE)
Atualizar os registos que já existem e inserir os que são novos, tudo numa única operação atómica: é exatamente isto que o comando MERGE faz numa tabela Delta. Em vez de apagar e reescrever a tabela inteira, o MERGE compara uma origem com o destino através de uma chave e aplica apenas as alterações necessárias — o chamado upsert. É a forma correta de manter uma tabela Delta sincronizada no Databricks sem duplicar dados nem correr pipelines pesados.
Pré-requisitos
- Um workspace Databricks com acesso a um cluster ou SQL warehouse ativo.
- Permissões para criar e escrever tabelas no catálogo (Unity Catalog ou hive_metastore).
- Conhecimentos básicos de SQL ou de PySpark (DataFrames).
- Saber qual é a coluna-chave que identifica cada registo de forma única (por exemplo, um id).
Passo 1: Criar uma tabela Delta de destino
Começa por criar a tabela que vais manter atualizada ao longo do tempo. No Databricks, qualquer tabela gerida usa o formato Delta por omissão, o que lhe dá transações ACID — e é precisamente isso que torna o MERGE seguro. Corre este SQL numa célula de notebook:
CREATE TABLE clientes (
id INT,
nome STRING,
cidade STRING
) USING DELTA;
INSERT INTO clientes VALUES
(1, 'Ana', 'Lisboa'),
(2, 'Bruno', 'Porto');
Ficaste com dois clientes na tabela de destino. É sobre esta tabela que o MERGE vai atuar, comparando-a com os dados novos que preparas a seguir.
Passo 2: Preparar a origem com os dados novos
A origem contém as alterações que queres aplicar e pode ser uma tabela, uma view ou um DataFrame. Neste exemplo, o cliente 2 mudou de cidade e o cliente 3 é completamente novo. Para simplificar, cria uma view temporária como origem:
CREATE OR REPLACE TEMP VIEW clientes_novos AS
SELECT * FROM VALUES
(2, 'Bruno', 'Coimbra'),
(3, 'Carla', 'Braga')
AS t(id, nome, cidade);
Repara que a coluna id é a chave que liga as duas tabelas: o id 2 já existe no destino (vai ser atualizado) e o id 3 não existe (vai ser inserido). Escolher bem esta chave é o passo mais importante de todo o processo.
Passo 3: Executar o MERGE em SQL
Chegámos ao passo central. O MERGE percorre a origem linha a linha e, para cada uma, decide o que fazer com base na condição ON que compara as chaves:
MERGE INTO clientes AS destino
USING clientes_novos AS origem
ON destino.id = origem.id
WHEN MATCHED THEN
UPDATE SET destino.cidade = origem.cidade
WHEN NOT MATCHED THEN
INSERT (id, nome, cidade)
VALUES (origem.id, origem.nome, origem.cidade);
Lê-se de forma quase literal: quando o id da origem corresponde (MATCHED) a um id já presente no destino, atualiza a cidade; quando não corresponde (NOT MATCHED), insere um registo novo. O mais importante é que tudo acontece numa só transação atómica — ou corre por completo, ou não altera nada, mesmo que o cluster falhe a meio.
Dica: a condição ON deve usar sempre uma chave única. Se a origem tiver ids repetidos que correspondam à mesma linha de destino, o MERGE falha de propósito, para evitar resultados ambíguos.
Passo 4: Fazer o mesmo em PySpark (opcional)
Se preferes trabalhar em Python, a API DeltaTable faz exatamente o mesmo com uma sintaxe encadeada. É a opção natural quando a origem já é um DataFrame que resulta de outro processo de transformação:
from delta.tables import DeltaTable
destino = DeltaTable.forName(spark, "clientes")
origem = spark.createDataFrame(
[(2, "Bruno", "Coimbra"), (3, "Carla", "Braga")],
["id", "nome", "cidade"],
)
(destino.alias("d")
.merge(origem.alias("o"), "d.id = o.id")
.whenMatchedUpdate(set={"cidade": "o.cidade"})
.whenNotMatchedInsertAll()
.execute())
O método whenNotMatchedInsertAll() insere todas as colunas automaticamente quando os nomes coincidem entre origem e destino, poupando-te de as listar uma a uma. Tanto o SQL como o PySpark produzem o mesmo resultado — usa o que for mais confortável para a tua equipa.
Verificar o resultado
Consulta a tabela para confirmar que o upsert correu como esperado:
SELECT * FROM clientes ORDER BY id;
Deves ver três linhas: o cliente 1 inalterado, o cliente 2 com a cidade atualizada para Coimbra e o cliente 3 (Carla) recém-inserido. Para confirmar o histórico da operação, corre DESCRIBE HISTORY clientes e vais ver uma entrada do tipo MERGE com o número de linhas inseridas e atualizadas.
Conclusão
Com um único MERGE mantiveste a tabela Delta sincronizada, sem apagar dados nem correr o risco de duplicados — a base de qualquer pipeline incremental no Databricks. O próximo passo natural é ligar a origem a dados reais, como um ficheiro que chega todos os dias, e agendar o MERGE num Databricks Workflow. Uma dica final: acrescenta uma cláusula WHEN MATCHED AND ... THEN DELETE se precisares de remover registos que já não existem na origem. Que outra regra de sincronização faria sentido no teu caso?