Como remover duplicados em ELT: passo a passo
Depois de carregar dados em bruto para a camada de staging, é normal encontrar registos duplicados: ficheiros reprocessados, tentativas repetidas de ingestão ou duplicados vindos da própria origem. Estes duplicados inflacionam métricas (uma venda contada duas vezes), partem junções (joins) e podem duplicar linhas em relatórios de Power BI, por isso convém tratá-los cedo. Veja a seguir, passo a passo, como remover duplicados em ELT na camada de transformação, com SQL padrão que funciona em SQL Server, Snowflake, BigQuery, Databricks e Microsoft Fabric.
Pré-requisitos
- Uma tabela de staging já carregada — o "L" (Load) do ELT —, por exemplo
staging.customers. - Permissões para executar SQL e criar tabelas ou vistas no seu data warehouse.
- Saber qual é a chave de negócio que identifica um registo único (ex.:
customer_id). - Uma coluna de data/hora para desempatar (ex.:
updated_at).
Passo 1: Confirmar que existem duplicados
Antes de apagar seja o que for, confirme o problema. Agrupe pela chave de negócio e conte as linhas por chave: se alguma chave tiver mais do que uma linha, existem duplicados a tratar. Se a tabela for muito grande, corra primeiro esta verificação para dimensionar o problema antes de reescrever dados.
SELECT customer_id, COUNT(*) AS n_rows
FROM staging.customers
GROUP BY customer_id
HAVING COUNT(*) > 1
ORDER BY n_rows DESC;
Passo 2: Definir a chave e o critério de desempate
Para decidir qual das linhas repetidas manter, precisa de uma regra. O mais comum é ficar com o registo mais recente. Defina então dois elementos: a chave de negócio (o que torna um registo único) e a coluna que ordena do mais novo para o mais antigo, normalmente uma data de atualização como updated_at.
Atenção:SELECT DISTINCTsó elimina linhas totalmente iguais. Não resolve duplicados de negócio, em que a mesma chave aparece com valores diferentes. Por isso usamosROW_NUMBER().
Passo 3: Numerar as linhas com ROW_NUMBER()
A função de janela ROW_NUMBER() atribui um número sequencial a cada linha dentro de um grupo. Com PARTITION BY definimos o grupo (a chave de negócio) e com ORDER BY definimos quem fica em primeiro lugar. A linha que queremos manter recebe sempre o número 1.
Ao contrário de GROUP BY, que colapsa as linhas e obriga a agregar todas as colunas, a função de janela mantém intactas todas as colunas originais. É por isso que ROW_NUMBER() é ideal para deduplicar: escolhe uma linha inteira por chave, sem perder informação.
WITH ranked AS (
SELECT
customer_id, name, email, updated_at,
ROW_NUMBER() OVER (
PARTITION BY customer_id
ORDER BY updated_at DESC
) AS rn
FROM staging.customers
)
SELECT customer_id, name, email, updated_at
FROM ranked
WHERE rn = 1;
Passo 4: Materializar a tabela sem duplicados
Agora guarde o resultado numa tabela limpa, pronta para as camadas seguintes. Use o padrão CREATE TABLE ... AS SELECT (CTAS) e liste as colunas finais, para não arrastar a coluna auxiliar rn.
CREATE TABLE clean.customers AS
WITH ranked AS (
SELECT
customer_id, name, email, updated_at,
ROW_NUMBER() OVER (
PARTITION BY customer_id
ORDER BY updated_at DESC
) AS rn
FROM staging.customers
)
SELECT customer_id, name, email, updated_at
FROM ranked
WHERE rn = 1;
Alguns warehouses — Snowflake, BigQuery e Databricks — oferecem a cláusula QUALIFY, que filtra pela função de janela sem precisar da CTE e deixa o código mais curto:
SELECT customer_id, name, email, updated_at
FROM staging.customers
QUALIFY ROW_NUMBER() OVER (
PARTITION BY customer_id
ORDER BY updated_at DESC
) = 1;
Verificar o resultado
Confirme que a tabela final não tem duplicados repetindo a contagem do Passo 1, agora sobre a tabela limpa: o resultado deve vir vazio. Compare ainda o total de linhas antes e depois — a diferença corresponde exatamente aos duplicados removidos.
-- Não deve devolver nenhuma linha
SELECT customer_id, COUNT(*)
FROM clean.customers
GROUP BY customer_id
HAVING COUNT(*) > 1;
Conclusão
Com uma função de janela e um critério de desempate claro, a deduplicação torna-se um passo fiável e repetível da sua transformação ELT. O passo seguinte é integrar esta query no seu orquestrador — por exemplo Azure Data Factory ou dbt — para correr em cada carga e manter as camadas silver e gold sempre limpas. Fica a pergunta para o seu caso: qual deve ser o critério de desempate — a data mais recente, ou uma regra de prioridade por fonte?