Hoje aprenderemos como ELIMINAR DADOS DUPLICADOS no SQL Server, assunto que recentemente enfrentei na vida prática (mais uma vez) e que também é cobrado nas provas de certificação da Microsoft.

Dados duplicados no SQL Server

Claro que se o banco fosse bem definido em termos de chaves primárias, estrangeiras, únicas, etc… você não teria esse problema, mas a vida é bandida certas horas, e quando lidamos com certos bancos de dados…

 

Um dia você terá de enfrentar um problema de dados duplicados em um banco de dados, seja SQL ou outro. Talvez ainda não tenha ocorrido com você, mas fique tranquilo, vai acontecer! Kkkkk

 

Essa situação é tão comum que cai até nas provas de certificação, e eu mesmo me deparei com ela a menos de 1 mês atrás.

 

Eliminando dados duplicados no SQL

Eliminar dados duplicados pode ser bem complexo, eu mesmo já tive situações bastante dramáticas, então hoje venho compartilhar com você algumas querys que me ajudaram tremendamente no troubleshooting e resolução desse problema.

 

A query mais básica, é claro, não poderia ser outra se não o select *.

 

Quando são poucos dados, em geral ele (o select *) resolve a parada, mas quase sempre não é assim. Na ocasião que mencionei a você (problema que enfrentei a menos de 1 mês), os dados duplicados eram em uma tabela com mais de 10 mil registros.

 

Nesses casos, o que temos de fazer é agrupar os problemas, e para isso eu uso as querys que apresentei na aula de hoje:

 

[sociallocker id=”5114″]



--------------------------------------------------------
-- Trabalhando com dados duplicados
--------------------------------------------------------
-----------------------------------------
-- Dados de teste
-----------------------------------------
-- Banco de testes
use master
if db_id('curso') is not null drop database curso
go
create database curso
go
use curso
go
-- Cadastro de amigos em tabela com PK
if object_id('amigos1') is not null drop table amigos1
create table amigos1 (id int identity(1,1) primary key, nome varchar(100), email varchar(100))
insert into amigos1 (nome, email) values 
	-- e-mail duplicado
	('João', '[email protected]'),
	('João DBA', '[email protected]'),
	-- e-mail e nome triplicados
	('Maria', '[email protected]'),
	('Maria', '[email protected]'),
	('Maria', '[email protected]'),
	-- dado não duplicado misturado com dados duplicados
	('Josué', '[email protected]')

-- Cadastro de amigos em tabela sem PK (copia de amigos 1
if object_id('amigos2') is not null drop table amigos2
select nome, email into amigos2 from amigos1

-- Cadastro de amigos com volume de dados
if object_id('amigos3') is not null drop table amigos3
create table amigos3 (id int not null identity(1, 1) primary key, nome varchar(100), email varchar(100))
;with cte_seq as (select 1 as nr_cont union all select nr_cont + 1 from cte_seq where nr_cont < 100) insert into amigos3 (nome, email) select 'amigo' + convert(varchar, nr_cont), 'amigo' + convert(varchar, nr_cont) + '@email.com' from cte_seq go -- Duplicar dados de amigos3 insert into amigos3 (nome, email) select nome, email from amigos3 insert into amigos3 (nome, email) select nome, email from amigos2 ----------------------------------------- -- Levantamento & Analise ----------------------------------------- -- Levantamento visual (fossem milhares seria pior...) select * from amigos1 -- delete from amigos1 where id in (2, 3, 4) select * from amigos2 select * from amigos3 -- Levantamento com group by select email, count(1) ocorrencias from amigos1 group by email order by 2 desc select email, count(1) ocorrencias from amigos3 group by email order by 2 desc select * from amigos3 -- Levantamento com group by + having select email, count(1) ocorrencias from amigos1 group by email having count(1) > 1
order by 2 desc


-- Levantamento do primeiro e último ID cadastrado para cada email:
select
	email,
	min(id) id_primeiro_cadastro,
	max(id) id_ultimo_cadastro
from amigos1
group by email
having count(1) > 1
select * from amigos1


-- Levantamento com distinct (quantidade de "nomes diferentes" associados a um "e-mail duplicado")
select 
	email,
	count(1) qtd_nomes_associados
from (select distinct nome, email from amigos1) cadastro
group by email
select * from amigos1


-- Levantamento de nomes diferentes vs. e-mails duplicados (Lista de nomes)
select 
	email,
	count(1) qtd_nomes_associados,
	stuff((
		select ' | ' + nome 
		from amigos2 a
		where a.email = cadastro.email
		for xml path('')
	), 1, 3, '') nomes_associados
from (select distinct nome, email from amigos1) cadastro
group by email
--having count(1) > 1 -- somente emails com mais de um nome associado


-- Levantamento de nomes diferentes vs. e-mails duplicados (XML completo)
select 
	email,
	count(1) qtd_nomes_associados,
	(
		select id, nome 
		from amigos1 a
		where a.email = cadastro.email
		for xml auto, root('cadastros')
	) cadastros_associados
from (select distinct nome, email from amigos1) cadastro
group by email
--having count(1) > 1


-- Levantamento (parecer geral)
select 
	cadastro.email,
	contagem_eml_dup.ocorrencias qtd_ocorrencias_na_tabela,
	count(1) qtd_nomes_diferentes_associados,
	(
		select id, nome 
		from amigos1 a
		where a.email = cadastro.email
		for xml auto, root('cadastros')
	) cadastros_associados
from (select distinct nome, email from amigos1) cadastro
join (select email, count(1) ocorrencias from amigos1 group by email) contagem_eml_dup on cadastro.email = contagem_eml_dup.email
group by cadastro.email, contagem_eml_dup.ocorrencias




-----------------------------------------
-- Resolvendo dados duplicados (amigos1)
-----------------------------------------
-- Ajustes manuais: Ajustando emails duplicados com nomes diferentes
update amigos1 set nome = 'João DBA' where email = '[email protected]'
select * from amigos1

-- Ajustes "automatizados": Excluindo a segunda ocorrência de registros duplicados
;with cte_duplicados as (
	select
		max(id) id_max,
		min(id) id_min,
		email
	from amigos1
	group by email
	having count(1) > 1
)
--delete cadastro
select * 
from amigos1 cadastro
join cte_duplicados cte on cadastro.id = cte.id_max


select * from amigos1


-- Ajustes "automatizados": Registros triplicados e afins...
;with 
cte_manter as (
	-- menor ID dos registros duplicados (ex: primeira ocorrência do João e da Maria)
	select min(id) manter_id, email manter_email from amigos1 group by email having count(1) > 1
	union all
	-- registros únicos da tabela (ex: Josué)
	select min(id), email from amigos1 group by email having count(1) = 1
), 
cte_excluir as (
	select email excluir_email from amigos1 group by email having count(1) > 1
)
--delete T
select * 
from amigos1 T
left join cte_manter M on T.id = M.manter_id
left join cte_excluir E on T.email = E.excluir_email
where M.manter_id is null -- se o email não existe na CTE Manter, exclui


-- Ajustes "automatizados": Alternativa a registros triplicados com Ranking (CTE cai na prova)
-- Levantamento
select 
	*
from (
	select 
		id, 
		nome, 
		email, 
		row_number() over (partition by email order by nome) as ranking 
	from amigos1) emails 
where ranking > 1 -- rankins maiores que 1 são duplicados

-- Exclusão (com join ao invés de CTE):
--delete cadastro
select * 
from amigos1 cadastro
join (
	select * from (select id, nome, email, row_number() over (partition by email order by nome) as ranking from amigos1) cadastro where ranking > 1
) duplicados on duplicados.id = cadastro.id


-----------------------------------------
-- Resolvendo dados duplicados (amigos2)
-----------------------------------------
-- Alternativa 1: Incluir coluna "ID" (chave primária) 
-- OBS: não é recomendado usar uniqueid como chave primária por motivos de performance
-- newid (SQL 2012-)
alter table amigos2 add id_newid uniqueidentifier default(newid()) with values
select * from amigos2

-- Sequence (SQL 2012+)
create sequence sAmigos2 as int start with 1 increment by 1
alter table amigos2 add id_seq int default(next value for sAmigos2) with values
select * from amigos2


-- Alternativa 2: Migrar dados com distinct
if object_id('amigos2migracao') is not null drop table amigos2migracao
select distinct nome, email into amigos2migracao from amigos2
select * from amigos2migracao
select * from amigos2

-----------------------------------------
-- Resolvendo dados duplicados (amigos3)
-----------------------------------------
-- Mesma abordagem de amigos1...



-----------------------------------------
-- Excluindo dados do teste
-----------------------------------------
/*
use master
if db_id('curso') is not null drop database curso
go
*/


[/sociallocker]

CONCLUSÃO

Eliminar dados duplicados no SQL Server pode ser uma grande dor de cabeça, mas usando os scripts certos o seu trabalho com certeza será reduzido dramaticamente!

 

Espero que também tenha gostado desse conteúdo.

 

Abraço do seu amigo Josué 🙂

2 respostas

  1. Bom dia,
    amei as dicas, ótimo conteúdo. Parabéns.
    Tenho uma dúvida com relação aos dados duplicados.
    Para quem não tem permissão de exclusão, ou seja, apenas permissão para leitura. Como que faz para gerar uma query pegando as informações sem duplicações?

    1. Oi Angélica, bom dia!

      No vídeo demonstrei scripts para “demonstrar/investigar dados duplicados”. Creio que possa usá-los para apresentar o problema em detalhes a alguém que tenha o acesso de delete para efetuar o ajuste.

      Se quiseres apontar os dados que NÃO são duplicados, pode substituir o filtro “having count(1) > 1” por “having count(1) = 1”.

      Abraço,
      Josué

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *