Remover duplicatas preservando o registro correto
Defina a identidade da duplicata e a regra de sobrevivência, confira ROW_NUMBER e impeça o retorno das duplicatas.
A parte difícil da deduplicação não é escrever DELETE. É decidir quais linhas representam a mesma entidade, quais informações devem sobreviver e como manter referências válidas. Duas linhas com o mesmo e-mail não são necessariamente o mesmo cliente. A maior identidade numérica também não é automaticamente o registro mais confiável.
Definir igualdade e sobrevivência
Escreva a chave duplicada como regra de negócio. Inclua tenant ou sistema de origem quando necessário. Defina tratamento de maiúsculas, acentos, espaços e valores ausentes. As comparações seguem a collation. Agrupar NULL pode ser incorreto quando ele significa identidade desconhecida, não identidade compartilhada.
Escolha uma regra determinística. No exercício, TenantId e ExternalKey formam a chave; vence a linha observada mais recentemente, com RowId desempatando horários iguais. Esse último critério único evita sobreviventes diferentes em execuções distintas.
ExternalKey ausente fica excluído deliberadamente. Cada identidade desconhecida permanece independente até uma reconciliação específica. Essa é uma política possível, não uma interpretação universal de NULL.
CREATE TABLE #CustomerStage
(RowId int PRIMARY KEY, TenantId int NOT NULL,
ExternalKey varchar(20) NULL, SeenAt datetime2(0) NOT NULL);
INSERT #CustomerStage VALUES
(1,1,'A','2020-01-01'),(2,1,'A','2020-02-01'),
(3,2,'A','2020-01-01'),(4,1,NULL,'2020-01-01'),
(5,1,NULL,'2020-02-01');
;WITH ranked AS
( SELECT *, ROW_NUMBER() OVER
(PARTITION BY TenantId, ExternalKey
ORDER BY SeenAt DESC, RowId DESC) AS rn
FROM #CustomerStage WHERE ExternalKey IS NOT NULL )
SELECT * FROM ranked WHERE rn > 1 ORDER BY RowId;
A vítima esperada é RowId 1. A linha 2 é mais recente para tenant 1 e chave A. A linha 3 pertence a outro tenant; 4 e 5 possuem identidade desconhecida. Valide esses resultados com o responsável pelo negócio antes de modificar dados reais.
Controlar a exclusão
O exercício seguinte afeta somente a tabela temporária e desfaz a exclusão. OUTPUT captura as linhas realmente removidas. Depois do rollback, os dados originais continuam intactos. Em produção, valide regra, quantidade, recuperação e concorrência de escrita antes de executar.
BEGIN TRAN;
;WITH ranked AS
( SELECT *, ROW_NUMBER() OVER
(PARTITION BY TenantId, ExternalKey
ORDER BY SeenAt DESC, RowId DESC) AS rn
FROM #CustomerStage WHERE ExternalKey IS NOT NULL )
DELETE FROM ranked
OUTPUT deleted.RowId, deleted.TenantId, deleted.ExternalKey
WHERE rn > 1;
ROLLBACK;
Uma prévia feita horas antes não garante a seleção posterior. Novas linhas podem chegar e horários mudar. Use uma janela controlada ou isolamento deliberado cobrindo seleção, migração de referências, exclusão e proteção de unicidade. Uma grande transação serializável pode bloquear bastante; não é uma alternativa gratuita.
Antes de excluir pais, inventarie chaves estrangeiras e relacionamentos não declarados. Se filhos apontam para identidades duplicadas, crie um mapa vítima-sobrevivente e redirecione conforme regras de negócio. Os filhos podem então colidir em suas próprias chaves únicas. Resolva isso explicitamente; desabilitar restrições apenas esconde o conflito.
Preservar conteúdo e impedir recorrência
A linha nova pode ter endereço vazio enquanto a antiga contém endereço verificado. Escolher uma delas simplesmente perderia informação. Consolide atributos com precedências documentadas e preserve origem quando necessário. O sobrevivente pode ser um registro combinado, não uma linha original intacta.
Mantenha mapeamento auditável e dados removidos suficientes durante o prazo de recuperação acordado. Backup é indispensável, mas pode ser inconveniente para recuperar poucas relações depois de outras escritas legítimas. O registro de recuperação precisa identificar chaves originais e transformação, protegendo adequadamente dados pessoais.
Depois imponha a unicidade verdadeira por restrição ou índice único filtrado coerente com o tratamento de chaves desconhecidas. Corrija também a corrida na importação ou falta de idempotência que criou o problema. Confira contagens por tenant, atributos preservados, referências e repetição do importador defeituoso. Inclua empates exatos de horário e registros incompletos nos testes. O sucesso remove somente duplicatas confirmadas, mantém informação útil e rejeita a próxima duplicata imediatamente.
Referências técnicas: Microsoft Learn: ROW_NUMBER · Microsoft Learn: Unique indexes.