202

I'm testing something in Oracle and populated a table with some sample data, but in the process I accidentally loaded duplicate records, so now I can't create a primary key using some of the columns.

How can I delete all duplicate rows and leave only one of them?

24 Answers 24

421

Use the rowid pseudocolumn.

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

Where column1, column2, and column3 make up the identifying key for each record. You might list all your columns.

7
  • 9
    +1 I had to find two duplicate phone numbers buried in 12,000+ records. Changed the DELETE to SELECT and this found them in seconds. Saved me a ton of time, thank you.
    – shimonyk
    Commented Sep 23, 2010 at 15:30
  • 3
    This approach did not work for me. I don't know why. When I replaced "DELETE" with "SELECT *", it returned the rows I wanted to delete, but when I executed with "DELETE" it was just hanging indefinitely.
    – aro_biz
    Commented Jun 25, 2012 at 12:05
  • 1
    Mine is also either hanging or just executing extremely long. Been running for about 22 hours and still going. Table have 21M records. Commented Aug 22, 2013 at 5:57
  • 1
    I suggest to add further filtering to the WHERE statement if you have a very large data set and if feasible, this might help folks with long running queries. Commented Apr 8, 2014 at 16:58
  • 3
    If the select works, but the delete does not, that might be due to the size of the resulting subquery. It might be interesting to first do a create table with the subquery result, build an index on the min(rowid) column, and then run the delete statement.
    – Wouter
    Commented May 15, 2014 at 13:51
19

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

(fixed the missing parenthesis)

0
17

From DevX.com:

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

Where column1, column2, etc. is the key you want to use.

15
DELETE FROM tablename a
      WHERE a.ROWID > ANY (SELECT b.ROWID
                             FROM tablename b
                            WHERE a.fieldname = b.fieldname
                              AND a.fieldname2 = b.fieldname2)
2
  • 1
    Re my comment above on the top-voted answer, it was this request which actually solved my problem.
    – aro_biz
    Commented Jun 25, 2012 at 12:06
  • 4
    This will be -a lot- slower on huge tables than Bill's solution.
    – Wouter
    Commented May 15, 2014 at 14:01
11

Solution 1)

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

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

Solution 3)

delete from emp e1
         where rowid not in
          (select max(rowid) from emp e2
           where e1.empno = e2.empno ); 
1
  • 1
    Could you tell us the pros and cons of each one of the approach?
    – Arun Gowda
    Commented Aug 26, 2020 at 17:56
7

create table t2 as select distinct * from t1;

3
  • not an answer - distinct * will take every record which differs in at least 1 symbol in 1 column. All you need is to select distinct values only from columns you want to make primary keys - Bill's answer is great example of this approach.
    – Nogard
    Commented Jan 11, 2013 at 17:28
  • 1
    That was what I needed (remove entirely identical lines). Thanks !
    – Emmanuel
    Commented Feb 20, 2013 at 11:43
  • Another disadvantage of this method is that you have to create a copy of your table. For huge tables, this implies providing additional tablespace, and deleting or shrinking the tablespace after the copy. Bill's method has more benefits, and no additional disadvantages.
    – Wouter
    Commented May 15, 2014 at 13:59
4

You should do a small pl/sql block using a cursor for loop and delete the rows you don't want to keep. For instance:

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;
2
  • I believe the downvote is because you are using PL/SQL when you can do it in SQL, incase you are wondering.
    – WW.
    Commented Feb 10, 2009 at 1:39
  • 9
    Just because you can do it in SQL, doesn't mean its the only solution. I posted this solution, after I had seen the SQL-only solution. I thought down votes were for incorrect answers.
    – Nick
    Commented Feb 10, 2009 at 2:43
4

This blog post was really helpful for general cases:

If the rows are fully duplicated (all values in all columns can have copies) there are no columns to use! But to keep one you still need a unique identifier for each row in each group. Fortunately, Oracle already has something you can use. The rowid. All rows in Oracle have a rowid. This is a physical locator. That is, it states where on disk Oracle stores the row. This unique to each row. So you can use this value to identify and remove copies. To do this, replace min() with min(rowid) in the uncorrelated delete:

delete films
where  rowid not in (
  select min(rowid)
  from   films
  group  by title, uk_release_date
)
3

To select the duplicates only the query format can be:

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

So the correct query as per other suggestion is:

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

This query will keep the oldest record in the database for the criteria chosen in the WHERE CLAUSE.

Oracle Certified Associate (2008)

3
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
3

solution :

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

The Fastest way for really big tables

  1. Create exception table with structure below: exceptions_table

    ROW_ID ROWID
    OWNER VARCHAR2(30)
    TABLE_NAME VARCHAR2(30)
    CONSTRAINT VARCHAR2(30)
    
  2. Try create a unique constraint or primary key which will be violated by the duplicates. You will get an error message because you have duplicates. The exceptions table will contain the rowids for the duplicate rows.

    alter table add constraint
    unique --or primary key
    (dupfield1,dupfield2) exceptions into exceptions_table;
    
  3. Join your table with exceptions_table by rowid and delete dups

    delete original_dups where rowid in (select ROW_ID from exceptions_table);
    
  4. If the amount of rows to delete is big, then create a new table (with all grants and indexes) anti-joining with exceptions_table by rowid and rename the original table into original_dups table and rename new_table_with_no_dups into original table

    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

Using rowid-

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

Using self join-

delete from emp e1
 where rowid not in
 (select max(rowid) from emp e2
 where e1.empno = e2.empno );
1
  • Hi Tandale, Please use code formatting tool while submitting answers as it increases readability.
    – NSNoob
    Commented Dec 28, 2015 at 14:17
2

Solution 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
);
3
  • Can you explain a bit? Commented Dec 31, 2015 at 13:04
  • dense rank with partition by gives the rank for duplicate rows with same number for example three rows having rank 1 , 1 , 1 and rowid create for every row as unic and we are trying to delete those rowids which are not matching.
    – DoOrDie
    Commented Dec 31, 2015 at 13:18
  • we can use both rank and dense_rank functions but i think rank works perfectly in this scenario.
    – DoOrDie
    Commented Dec 31, 2015 at 13:40
2

1. solution

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

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

4. solution

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

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

and you can also delete duplicate records in another way

DELETE from table_name a where rowid > (select min(rowid) FROM table_name b where a.column=b.column);
1
DELETE FROM tableName  WHERE ROWID NOT IN (SELECT   MIN (ROWID) FROM table GROUP BY columnname);
1
  • 1
    Same answer as the more elaborate answer of Bill the Lizard.
    – Wouter
    Commented May 15, 2014 at 13:55
1
delete from dept
where rowid in (
     select rowid
     from dept
     minus
     select max(rowid)
     from dept
     group by DEPTNO, DNAME, LOC
);
1
  • Can you add more information about your way? Thanks.
    – Reporter
    Commented May 20, 2014 at 9:16
1

For best performance, here is what I wrote :
(see execution plan)

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

Check below scripts -

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;    

You will see here 6-records.
4.run below query -

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;

You will see that duplicate records have been deleted.
Hope this solves your query. Thanks :)

1

I didn't see any answers that use common table expressions and window functions. This is what I find easiest to work with.

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

Somethings to note:

1) We are only checking for duplication on the fields in the partition clause.

2) If you have some reason to pick one duplicate over others you can use an order by clause to make that row will have row_number() = 1

3) You can change the number duplicate preserved by changing the final where clause to "Where RN > N" with N >= 1 (I was thinking N = 0 would delete all rows that have duplicates, but it would just delete all rows).

4) Added the Sum partition field the CTE query which will tag each row with the number rows in the group. So to select rows with duplicates, including the first item use "WHERE cnt > 1".

1

This is similar to the top answer but gives me a much better explain plan:

delete from your_table
 where rowid in (
        select max(rowid)
          from your_table
         group by column1, column2, column3
        having count(*) > 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;
1
  • A major disadvantage of this method is the inner join. For big tables this will be a lot slower than Bill's method. Also, using PL/SQL to do this is overkill, you could also use this by simply using sql.
    – Wouter
    Commented May 15, 2014 at 13:57

Not the answer you're looking for? Browse other questions tagged or ask your own question.