domingo, 26 de janeiro de 2014

Localizando e Excluindo Registros Duplicados

Evitar a ocorrência de registros duplicados garante a integridade de um banco de dados, pra isso, planejar e prever situações no ato da modelagem do banco são imprescindíveis para não ter dor de cabeça.

No entanto, é comum nos deparamos com bases de dados repletas de registros duplicados, o que pode ter ocorrido por conta de um mal planejamento ou, forçado devido a importação de dados externos.

Localizar e eliminar estes registros, será o que veremos neste artigo.


  • Simulando Registros Duplicados


A forma mais simples de localizar os duplicados é através dos comandos GROUP BY e HAVING. Vamos popular uma tabela de produtos a fim de duplicar registros:
 Create Table produto (  
  id_produto bigint not null,  
  descricao varchar (100),  
  estoque int not null default 0  
 ) ;
  Insert Into produto    
  Values   
  (1, "Produto A", 10),   
  (2, "Produto B", 10),   
  (3, "Produto C", 10),   
  (4, "Produto B", 12),   
  (5, "Produto C", 8),   
  (6, "Produto D", 10),   
  (7, "Produto E", 10),   
  (8, "Produto E", 10),   
  (9, "Produto F", 10);   

  • Identificando os Duplicados


Vejam que os produtos B, C e E estão duplicados, vamos então apresenta-los utilizando um simples select:
 select   
  *   
 from  
   produto   
 group by descricao   
 having count(descricao) > 1 ;  
 id_produto descricao estoque  
 ---------- --------- -------  
      2 Produto B    10  
      3 Produto C    10  
      7 Produto E    10  
Localizar os registros foi possível pois, agrupamos os diferentes produtos em um só (group by), após, adicionamos a condição para que somente os registros que contenham mais de uma ocorrência (having...) fossem apresentados. (Mais de uma ocorrência após agrupado). No exemplo, filtramos somente os registros com mais de uma ocorrência, mas você pode especificar a quantidade desejada.

[Dica]
Observe que os registros encontrados como duplicados foram os com menor id.


Além de lista-los, podemos identificar todos eles e, saber quantas vezes cada um está repetido, vejamos:
 select   
  descricao,  
  count(descricao) as Quantidade  
 from  
  produto   
 group by descricao   
 having count(descricao) > 1 ;  
 descricao Quantidade  
 --------- ----------  
 Produto B      2  
 Produto C      2  
 Produto E      2  
Com o count, agora sabemos que para cada registro duplicado, existem duas linhas em nossa tabela.

Agora que já sabemos como identificar os registros, como excluí-los?

  • Excluindo os Duplicados


Antes de excluir os registros, é importante saber se eles não são chaves estrangeiras em outras tabelas, o que pode causar um grande estrago em seu banco de dados se o relacionamento foi criado com on delete cascade.

Precauções a parte, vamos excluir os registros. Podemos fazer isso de várias formas, focarei somente na forma mais prática e usual que aplico:

1º - Crio uma tabela com os registros duplicados, esta tabela pode ser apagada posteriormente, no entanto, recomendo não fazer isso, pois, ela pode ser usada posteriormente para fins de consulta:
 create table produto_duplicado   
 (select   
  *   
 from  
  produto   
 group by descricao   
 having count(descricao) > 1) ;  
Não entendeu a forma que criei a tabela? Leia esta matéria que entenderá: Criando uma tabela com base em outra já existente.

Lembra da dica que dei acima? Então, neste momento você pode optar por excluir os registros mais antigos, ou mais recentes, no caso, se preferir manter os mais antigos, coloque um max na coluna id_produto.

2º - Com a tabela criada, excluiremos os registros:
 delete   
 from  
  produto   
 where produto.id_produto in   
  (select   
   produto_duplicado.id_produto   
  from  
   produto_duplicado   
  where produto_duplicado.id_produto = produto.id_produto) ;  
Nenhum segredo na linha do delete, o x da questão está na tabela que criamos, usamos ela em um sub-select, desta forma, apagamos somente os registros duplicados.

Caso tenha ficado alguma dúvida, queira dar uma sugestão ou reclamar também (:d), poste nos comentários, terei o maior prazer em responder, um grande abraço.

  • Perguntas Frequentes


Você menciona que existem outras formas de excluir os registros, quais?
  1. http://www.devmedia.com.br/forum/como-retornar-e-excluir-registros-duplicados-via-select/32873
  2. http://msmvps.com/blogs/cmattos/archive/2011/05/07/sql-como-excluir-registros-duplicados-numa-tabela-sem-chave-prim-225-ria.aspx
  3. http://gustavomaiaaguiar.wordpress.com/2009/05/30/como-eliminar-linhas-duplicadas-e-repeticoes-no-sql-server/
  4. http://ericsilva.wordpress.com/2013/07/11/como-excluir-registros-duplicados-no-mysql/
Você menciona on delete cascade, o que é isso?
http://magaweb.com.br/blog/?p=450

No seu caso, existem apenas dois registros duplicados para cada produto, se houvessem mais, como eu faria para manter somente um?
Crie uma tabela com os registros que serão mantidos (vide exemplo no 4º link da primeira pergunta), após, apague os registros com o delete apresentado acima (Adaptações poderão ser necessárias).

3 comentários:

  1. Olá, eu também uso (dentre outros bancos de dados) o SQL Server. Eu o uso muito para tratar dados em processos de migração de sistemas – clientes que mudam seus programas para outro fornecedor; o SQL Server é perfeito pra mim. Essa questão de linhas duplicadas é uma pegadinha, especialmente porque cada situação traz um cenário particular. Acabei de resolver um problema que estava me “torrando o juízo”: precisei juntar duas tabelas com UNION em uma terceira tabela, onde criei um campo “chave” do tipo IDENTITY (autonumeração ou sequencial do SQL Server), já que eu tinha “id’s” repetidos para os mesmos clientes/fornecedores, no entanto, teria que eliminar as duplicidades mesmo assim. Observando bem, vi que poderia usar duas colunas como critério de desempate: o nome e o CNPJ, e eis o script que usei pra resolver este problema:

    delete from pessoa where chave in (select dup.chave from
    (select nome, cpf_cnpj, max(chave) as chave, COUNT(*) as contador from pessoa
    group by nome, cpf_cnpj
    having COUNT(*) > 1) dup)

    Espero poder ajudar a outros com o mesmo problema.

    DEIXE UM COMENTÁRIO

    ResponderExcluir
  2. Ao usar o seguinte comando:
    create table produto_duplicado
    (select
    *
    from
    produto
    group by descricao
    having count(descricao) > 1) ;

    ELE ME RETORNA A SEGUINTE MENSAGEM:

    ERRO: erro de sintaxe em ou próximo a "select"
    LINE 2: (select
    ^

    ********** Error **********

    ERRO: erro de sintaxe em ou próximo a "select"
    SQL state: 42601
    Character: 39

    Podes me ajudar? agradeçop

    ResponderExcluir
  3. select * INTO DUPLICADA
    from
    produto
    group by descricao
    having count(descricao) > 1) ;

    ResponderExcluir