Removendo linhas duplicadas da tabela no Oracle


151

Estou testando algo no Oracle e preenchi uma tabela com alguns dados de amostra, mas no processo carreguei acidentalmente registros duplicados, agora não consigo criar uma chave primária usando algumas das colunas.

Como posso excluir todas as linhas duplicadas e deixar apenas uma delas?

Respostas:


306

Use a rowidpseudocoluna.

DELETE FROM your_table
WHERE rowid not in
(SELECT MIN(rowid)
FROM your_table
GROUP BY column1, column2, column3);

Onde column1, column2e column3componha a chave de identificação de cada registro. Você pode listar todas as suas colunas.


6
+1 Eu tive que encontrar dois números de telefone duplicados enterrados em mais de 12.000 registros. O DELETE foi alterado para SELECT e isso os encontrou em segundos. Economizei muito tempo, obrigado.
shimonyk 23/09/10

3
Essa abordagem não funcionou para mim. Não sei porque. Quando substituí "DELETE" por "SELECT *", ele retornou as linhas que desejava excluir, mas quando executei com "DELETE", ele estava suspenso indefinidamente.
Aro18iz

O meu também está pendurado ou está sendo executado por muito tempo. Está em funcionamento há cerca de 22 horas e continua em funcionamento. A tabela possui 21 milhões de registros.
Cameron Castillo

Sugiro adicionar mais filtragem à instrução WHERE se você tiver um conjunto de dados muito grande e, se possível, isso pode ajudar as pessoas com consultas demoradas.
Ricardo Sanchez

2
Se a seleção funcionar, mas a exclusão não, isso pode ser devido ao tamanho da subconsulta resultante. Pode ser interessante primeiro criar uma tabela com o resultado da subconsulta, criar um índice na coluna min (rowid) e depois executar a instrução delete.
Wouter 15/05

15

De Ask Tom

delete from t
 where rowid IN ( select rid
                    from (select rowid rid, 
                                 row_number() over (partition by 
                         companyid, agentid, class , status, terminationdate
                                   order by rowid) rn
                            from t)
                   where rn <> 1);

(corrigido o parêntese ausente)


Parênteses ausentes na declaração. Eu suponho que deveria estar no final?
Cameron Castillo

12

No DevX.com :

DELETE FROM our_table
WHERE rowid not in
(SELECT MIN(rowid)
FROM our_table
GROUP BY column1, column2, column3...) ;

Onde coluna1, coluna2, etc. é a chave que você deseja usar.


12
DELETE FROM tablename a
      WHERE a.ROWID > ANY (SELECT b.ROWID
                             FROM tablename b
                            WHERE a.fieldname = b.fieldname
                              AND a.fieldname2 = b.fieldname2)

1
Re meu comentário acima sobre a resposta mais votada, foi essa solicitação que realmente resolveu meu problema.
aro_biz 25/06/12

2
Isso será muito mais lento em mesas enormes do que a solução de Bill.
Wouter 15/05

8

Solução 1)

delete from emp
where rowid not in
(select max(rowid) from emp group by empno);

Solução 2)

delete from emp where rowid in
               (
                 select rid from
                  (
                    select rowid rid,
                      row_number() over(partition by empno order by empno) rn
                      from emp
                  )
                where rn > 1
               );

Solução 3)

delete from emp e1
         where rowid not in
          (select max(rowid) from emp e2
           where e1.empno = e2.empno ); 

6

crie a tabela t2 como selecione distinto * de t1;


não é uma resposta - distinct *aceita todos os registros que diferem em pelo menos 1 símbolo em 1 coluna. Tudo o que você precisa é selecionar valores distintos apenas das colunas que deseja criar chaves primárias - a resposta de Bill é um ótimo exemplo dessa abordagem.
Nogard

1
Era disso que eu precisava (remova linhas totalmente idênticas). Obrigado !
Emmanuel

Outra desvantagem desse método é que você precisa criar uma cópia da sua tabela. Para tabelas enormes, isso implica fornecer espaço de tabela adicional e excluir ou reduzir o espaço de tabela após a cópia. O método de Bill tem mais benefícios e nenhuma desvantagem adicional.
Wouter

3

Você deve fazer um pequeno bloco pl / sql usando um cursor para loop e excluir as linhas que não deseja manter. Por exemplo:

declare
prev_var my_table.var1%TYPE;

begin

for t in (select var1 from my_table order by var 1) LOOP

-- if previous var equal current var, delete the row, else keep on going.
end loop;

end;

Eu acredito que o voto negativo é porque você está usando PL / SQL quando pode fazê-lo no SQL, caso esteja se perguntando.
WW.

7
Só porque você pode fazer isso no SQL, não significa que é a única solução. Publiquei esta solução, depois de ter visto a solução somente para SQL. Eu pensei que votos eram para respostas incorretas.
1076 Nick

3

Para selecionar as duplicatas, apenas o formato da consulta pode ser:

SELECT GroupFunction(column1), GroupFunction(column2),..., 
COUNT(column1), column1, column2...
FROM our_table
GROUP BY column1, column2, column3...
HAVING COUNT(column1) > 1

Portanto, a consulta correta conforme outra sugestão é:

DELETE FROM tablename a
      WHERE a.ROWID > ANY (SELECT b.ROWID
                             FROM tablename b
                            WHERE a.fieldname = b.fieldname
                              AND a.fieldname2 = b.fieldname2
                              AND ....so on.. to identify the duplicate rows....)

Essa consulta manterá o registro mais antigo no banco de dados para os critérios escolhidos no WHERE CLAUSE.

Associado certificado Oracle (2008)


2

A maneira mais rápida para mesas realmente grandes

  1. Crie uma tabela de exceção com a estrutura abaixo: exceptions_table

    ROW_ID ROWID
    OWNER VARCHAR2(30)
    TABLE_NAME VARCHAR2(30)
    CONSTRAINT VARCHAR2(30)
  2. Tente criar uma restrição exclusiva ou chave primária que será violada pelas duplicatas. Você receberá uma mensagem de erro porque possui duplicatas. A tabela de exceções conterá os rowids para as linhas duplicadas.

    alter table add constraint
    unique --or primary key
    (dupfield1,dupfield2) exceptions into exceptions_table;
  3. Entre na sua tabela com exceptions_table por rowid e exclua dups

    delete original_dups where rowid in (select ROW_ID from exceptions_table);
  4. Se a quantidade de linhas a excluir for grande, crie uma nova tabela (com todas as concessões e índices) que se junte a exceptions_table por rowid e renomeie a tabela original para a tabela original_dups e renomeie new_table_with_no_dups para a tabela original

    create table new_table_with_no_dups AS (
        select field1, field2 ........ 
        from original_dups t1
        where not exists ( select null from exceptions_table T2 where t1.rowid = t2.row_id )
    )

2

Usando rowid-

delete from emp
 where rowid not in
 (select max(rowid) from emp group by empno);

Usando auto-junção

delete from emp e1
 where rowid not in
 (select max(rowid) from emp e2
 where e1.empno = e2.empno );

Oi Tandale, Use a ferramenta de formatação de código ao enviar respostas, pois aumenta a legibilidade.
NSNoob

2

Solução 4)

 delete from emp where rowid in
            (
             select rid from
                (
                  select rowid rid,
                  dense_rank() over(partition by empno order by rowid
                ) rn
             from emp
            )
 where rn > 1
);

Você pode explicar um pouco?
Dieter Meemken

classificação densa com partição fornece a classificação para linhas duplicadas com o mesmo número, por exemplo, três linhas com classificação 1, 1, 1 e rowid criadas para cada linha como unic e estamos tentando excluir as linhas que não são correspondentes.
DoOrDie

podemos usar as funções rank e dense_rank, mas acho que o rank funciona perfeitamente nesse cenário.
DoOrDie

2

1. solução

delete from emp
    where rowid not in
    (select max(rowid) from emp group by empno);

2. sloution

delete from emp where rowid in
               (
                 select rid from
                  (
                    select rowid rid,
                      row_number() over(partition by empno order by empno) rn
                      from emp
                  )
                where rn > 1
               );

3. solução

delete from emp e1
         where rowid not in
          (select max(rowid) from emp e2
           where e1.empno = e2.empno ); 

4. solução

 delete from emp where rowid in
            (
             select rid from
                (
                  select rowid rid,
                  dense_rank() over(partition by empno order by rowid
                ) rn
             from emp
            )
 where rn > 1
);

2

5. solução

delete from emp where rowid in 
    (
      select  rid from
       (
         select rowid rid,rank() over (partition by emp_id order by rowid)rn from emp     
       )
     where rn > 1
    );

2
DELETE from table_name where rowid not in (select min(rowid) FROM table_name group by column_name);

e você também pode excluir registros duplicados de outra maneira

DELETE from table_name a where rowid > (select min(rowid) FROM table_name b where a.column=b.column);

2
create table abcd(id number(10),name varchar2(20))

insert into abcd values(1,'abc')

insert into abcd values(2,'pqr')


insert into abcd values(3,'xyz')

insert into abcd values(1,'abc')

insert into abcd values(2,'pqr')

insert into abcd values(3,'xyz')


select * from abcd
id  Name
1   abc
2   pqr
3   xyz
1   abc
2   pqr
3   xyz

Delete Duplicate record but keep Distinct Record in table 

DELETE 
FROM abcd a
WHERE ROWID > (SELECT MIN(ROWID) FROM abcd b
WHERE b.id=a.id
);

run the above query 3 rows delete 

select * from abcd

id  Name 
1   abc
2   pqr
3   xyz

1
DELETE FROM tableName  WHERE ROWID NOT IN (SELECT   MIN (ROWID) FROM table GROUP BY columnname);

Mesma resposta que a resposta mais elaborada de Bill, o Lagarto.
Wouter 15/05

1
delete from dept
where rowid in (
     select rowid
     from dept
     minus
     select max(rowid)
     from dept
     group by DEPTNO, DNAME, LOC
);

Você pode adicionar mais informações sobre o seu caminho? Obrigado.
Reporter

1

Para obter o melhor desempenho, eis o que escrevi:
(consulte o plano de execução)

DELETE FROM your_table
WHERE rowid IN 
  (select t1.rowid from your_table  t1
      LEFT OUTER JOIN (
      SELECT MIN(rowid) as rowid, column1,column2, column3
      FROM your_table 
      GROUP BY column1, column2, column3
  )  co1 ON (t1.rowid = co1.rowid)
  WHERE co1.rowid IS NULL
);

1

Verifique os scripts abaixo -

1

Create table test(id int,sal int); 

2)

    insert into test values(1,100);    
    insert into test values(1,100);    
    insert into test values(2,200);    
    insert into test values(2,200);    
    insert into test values(3,300);    
    insert into test values(3,300);    
    commit;

3)

 select * from test;    

Você verá aqui 6 registros.
4.run abaixo da consulta -

delete from 
   test
where rowid in
 (select rowid from 
   (select 
     rowid,
     row_number()
    over 
     (partition by id order by sal) dup
    from test)
  where dup > 1)
  1. select * from test;

Você verá que os registros duplicados foram excluídos.
Espero que isso resolva sua consulta. Obrigado :)


1

Não vi respostas que usassem expressões comuns de tabela e funções de janela. É com isso que acho mais fácil trabalhar.

DELETE FROM
 YourTable
WHERE
 ROWID IN
    (WITH Duplicates
          AS (SELECT
               ROWID RID, 
               ROW_NUMBER() 
               OVER(
               PARTITION BY First_Name, Last_Name, Birth_Date)
                  AS RN
               SUM(1)
               OVER(
               PARTITION BY First_Name, Last_Name, Birth_Date
               ORDER BY ROWID ROWS BETWEEN UNBOUNDED PRECEDING 
                                       AND UNBOUNDED FOLLOWING)
                   AS CNT
              FROM
               YourTable
              WHERE
               Load_Date IS NULL)
     SELECT
      RID
     FROM
      duplicates
     WHERE
      RN > 1);

Algumas coisas a serem observadas:

1) Estamos apenas checando a duplicação nos campos da cláusula de partição.

2) Se você tiver algum motivo para escolher uma duplicata em detrimento de outras, poderá usar uma cláusula order by para fazer com que essa linha tenha row_number () = 1

3) Você pode alterar o número duplicado preservado alterando a cláusula where final para "Where RN> N" com N> = 1 (eu estava pensando que N = 0 excluiria todas as linhas com duplicatas, mas apenas excluiria todas as linhas) .

4) Adicionado o campo Partição de soma à consulta CTE, que marcará cada linha com o número de linhas no grupo. Portanto, para selecionar linhas com duplicatas, incluindo o primeiro item, use "WHERE cnt> 1".


0
create or replace procedure delete_duplicate_enq as
    cursor c1 is
    select *
    from enquiry;
begin
    for z in c1 loop
        delete enquiry
        where enquiry.enquiryno = z.enquiryno
        and rowid > any
        (select rowid
        from enquiry
        where enquiry.enquiryno = z.enquiryno);
    end loop;
 end delete_duplicate_enq;

Uma grande desvantagem desse método é a junção interna. Para mesas grandes, isso será muito mais lento que o método de Bill. Além disso, usar PL / SQL para fazer isso é um exagero, você também pode usar isso simplesmente usando sql.
Wouter 15/05

0

solução:

delete from emp where rowid in
(
    select rid from
    (
        select rowid rid,
        row_number() over(partition by empno order by empno) rn
        from emp
    )
    where rn > 1
);
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.