Eliminar filas duplicadas de la tabla en Oracle

151

Estoy probando algo en Oracle y llené una tabla con algunos datos de muestra, pero en el proceso cargué accidentalmente registros duplicados, por lo que ahora no puedo crear una clave principal con algunas de las columnas.

¿Cómo puedo eliminar todas las filas duplicadas y dejar solo una de ellas?

juan
fuente

Respuestas:

306

Usa la rowidpseudocolumna.

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

Donde column1, column2y column3componen la clave de identificación para cada registro. Puede enumerar todas sus columnas.

Bill el lagarto
fuente
66
+1 Tuve que encontrar dos números de teléfono duplicados enterrados en más de 12,000 registros. Cambió DELETE a SELECT y esto los encontró en segundos. Me ahorró un montón de tiempo, gracias.
shimonyk
3
Este enfoque no funcionó para mí. No se porque. Cuando reemplacé "BORRAR" con "SELECCIONAR *", devolvió las filas que quería eliminar, pero cuando ejecuté con "BORRAR" simplemente estaba colgando indefinidamente.
aro_biz
El mío también está colgando o simplemente ejecutándose extremadamente largo. He estado corriendo durante aproximadamente 22 horas y todavía voy. La mesa tiene 21 millones de registros.
Cameron Castillo
Sugiero agregar más filtros a la instrucción WHERE si tiene un conjunto de datos muy grande y, si es posible, esto podría ayudar a las personas con consultas de larga duración.
Ricardo Sanchez
2
Si la selección funciona, pero la eliminación no, puede deberse al tamaño de la subconsulta resultante. Puede ser interesante hacer primero una tabla de creación con el resultado de la subconsulta, crear un índice en la columna min (rowid) y luego ejecutar la instrucción delete.
Wouter
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);

(arreglado el paréntesis faltante)

Programador muerto
fuente
Falta paréntesis en la declaración. Supongo que debería ser al final?
Cameron Castillo
12

De DevX.com :

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

Donde column1, column2, etc. es la clave que desea usar.

marca
fuente
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)

fuente
1
Re mi comentario anterior sobre la respuesta más votada, fue esta solicitud la que realmente resolvió mi problema.
aro_biz
2
Esto será -mucho- más lento en tablas enormes que la solución de Bill.
Wouter
8

Solución 1)

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

Solución 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
               );

Solución 3)

delete from emp e1
         where rowid not in
          (select max(rowid) from emp e2
           where e1.empno = e2.empno ); 
Haz o muere
fuente
6

crear la tabla t2 como seleccionar distinto * de t1;

Mahoma Khaled
fuente
no es una respuesta: distinct *tomará todos los registros que difieran en al menos 1 símbolo en 1 columna. Todo lo que necesita es seleccionar valores distintos solo de las columnas que desea que sean claves principales: la respuesta de Bill es un gran ejemplo de este enfoque.
Nogard el
1
Eso era lo que necesitaba (eliminar líneas completamente idénticas). Gracias !
Emmanuel
Otra desventaja de este método es que debe crear una copia de su tabla. Para tablas enormes, esto implica proporcionar espacio de tabla adicional y eliminar o reducir el espacio de tabla después de la copia. El método de Bill tiene más beneficios y no tiene desventajas adicionales.
Wouter
3

Debe hacer un pequeño bloque pl / sql con un cursor para bucle y eliminar las filas que no desea conservar. Por ejemplo:

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;
Mella
fuente
Creo que el voto negativo se debe a que está utilizando PL / SQL cuando puede hacerlo en SQL, en caso de que se pregunte.
WW.
77
El hecho de que pueda hacerlo en SQL no significa que sea la única solución. Publiqué esta solución, después de haber visto la solución solo SQL. Pensé que los votos negativos eran por respuestas incorrectas.
Nick
3

Para seleccionar los duplicados, solo el formato de consulta puede ser:

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

Entonces, la consulta correcta según otra sugerencia es:

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....)

Esta consulta mantendrá el registro más antiguo en la base de datos para los criterios elegidos en el WHERE CLAUSE.

Asociado certificado de Oracle (2008)

usuario1799846
fuente
2

La forma más rápida para mesas realmente grandes

  1. Cree una tabla de excepciones con la estructura a continuación: excepciones_tabla

    ROW_ID ROWID
    OWNER VARCHAR2(30)
    TABLE_NAME VARCHAR2(30)
    CONSTRAINT VARCHAR2(30)
  2. Intente crear una restricción única o clave principal que será violada por los duplicados. Recibirá un mensaje de error porque tiene duplicados. La tabla de excepciones contendrá los rowids para las filas duplicadas.

    alter table add constraint
    unique --or primary key
    (dupfield1,dupfield2) exceptions into exceptions_table;
  3. Únase a su tabla con exceptions_table por rowid y elimine dups

    delete original_dups where rowid in (select ROW_ID from exceptions_table);
  4. Si la cantidad de filas para eliminar es grande, cree una nueva tabla (con todas las concesiones e índices) anti-unión con exceptions_table by rowid y cambie el nombre de la tabla original a la tabla original_dups y cambie el nombre de new_table_with_no_dups a la tabla 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 )
    )
usuario2158672
fuente
2

Usando rowid-

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

Usando auto unirse

delete from emp e1
 where rowid not in
 (select max(rowid) from emp e2
 where e1.empno = e2.empno );
Dnyaneshwar Tandale
fuente
Hola, Tandale: utiliza la herramienta de formato de código mientras envías respuestas, ya que aumenta la legibilidad.
NSNoob
2

Solución 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
);
Haz o muere
fuente
¿Puedes explicar un poco?
Dieter Meemken
el rango denso con partición da el rango para filas duplicadas con el mismo número, por ejemplo, tres filas que tienen rango 1, 1, 1 y rowid crean para cada fila como unic y estamos tratando de eliminar esos rowids que no coinciden.
DoOrDie
podemos usar las funciones rank y dense_rank pero creo que rank funciona perfectamente en este escenario.
DoOrDie
2

1. solución

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.solución

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

4. solución

 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
);

fuente
2

5. solución

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
    );
Haz o muere
fuente
2
DELETE from table_name where rowid not in (select min(rowid) FROM table_name group by column_name);

y también puedes eliminar registros duplicados de otra manera

DELETE from table_name a where rowid > (select min(rowid) FROM table_name b where a.column=b.column);
Md Wasi
fuente
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
Krunal Patel
fuente
1
DELETE FROM tableName  WHERE ROWID NOT IN (SELECT   MIN (ROWID) FROM table GROUP BY columnname);
JgSudhakar
fuente
La misma respuesta que la respuesta más elaborada de Bill el Lagarto.
Wouter
1
delete from dept
where rowid in (
     select rowid
     from dept
     minus
     select max(rowid)
     from dept
     group by DEPTNO, DNAME, LOC
);
usuario3655760
fuente
¿Puedes agregar más información sobre tu camino? Gracias.
Reportero
1

Para un mejor rendimiento, esto es lo que escribí:
(ver plan de ejecución)

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
);
Enguerrand JORE
fuente
1

Verifique los guiones a continuación:

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;    

Verá aquí 6 registros.
4.ejecutar debajo de la 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;

Verá que se han eliminado registros duplicados.
Espero que esto resuelva tu consulta. Gracias :)

Rakesh Roshan
fuente
1

No vi ninguna respuesta que use expresiones de tabla comunes y funciones de ventana. Esto es con lo que me resulta más fácil trabajar.

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);

Algo a tener en cuenta:

1) Solo estamos verificando la duplicación en los campos de la cláusula de partición.

2) Si tiene alguna razón para elegir un duplicado sobre otros, puede usar una orden por cláusula para hacer que esa fila tenga row_number () = 1

3) Puede cambiar el número duplicado conservado cambiando la cláusula where final a "Where RN> N" con N> = 1 (estaba pensando que N = 0 eliminaría todas las filas que tienen duplicados, pero simplemente eliminaría todas las filas) .

4) Se agregó el campo Suma partición a la consulta CTE que etiquetará cada fila con las filas de números en el grupo. Entonces, para seleccionar filas con duplicados, incluido el primer elemento, use "WHERE cnt> 1".

Darrel Lee
fuente
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;
Ashish sinha
fuente
Una desventaja importante de este método es la unión interna. Para mesas grandes, esto será mucho más lento que el método de Bill. Además, usar PL / SQL para hacer esto es excesivo, también podría usar esto simplemente usando sql.
Wouter
0

solución:

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
);
sandeep gupta
fuente