views:

120

answers:

3

I have a table with fields (simplified):

id, fld1, fld2, fld3.

id is a numeric primary key field.

There are duplicates: id differs but fld1, fld2 and fld3 are identical over 2 or more rows. There are also entries where the values occur only once, i.e. non-duplicates, of course.

Of each set of duplicate entries, I want to retain only the entry with the highest ID. I was planning to first list the doomed rows and then to delete them.

My first stab at it was this:

SELECT * FROM tab1 t1 WHERE EXISTS (
  SELECT COUNT(*) FROM tab1 t2
  WHERE t1.fld1 = t2.fld1 AND t1.fld2 = t2.fld2 AND t1.fld3 = t2.fld3
    AND t1.id < MAX(t2.id)
  HAVING COUNT(*) > 1
  GROUP BY t2.fld1, t2.fld2, t2.fld3)

But (in Oracle) I'm getting a Missing right parenthesis error message. I think this needs a new approach altogether, but my SQL-fu is not up to the task. Help appreciated!


Edit:

With 'real' data fields:

select x.leg_id, x.airline_des, x.flight_nr, x.suffix, x.flight_id_date, x.lt_flight_id_date
from fdb_leg x
join ( select max(t.leg_id) 'max_id', 
t.airline_des, t.flight_nr, t.suffix, t.flight_id_date, t.lt_flight_id_date
from fdb_leg t
group by t.airline_des, t.flight_nr, t.suffix, t.flight_id_date, t.lt_flight_id_date
having count(*) > 1) y on y.max_id > x.leg_id
and y.airline_des = x.airline_des and y.flight_nr = x.flight_nr and y.suffix = x.suffix
and y.flight_id_date = x.flight_id_date and x.lt_flight_id_date = y.lt_flight_id_date

Response is:

ORA-00923: FROM keyword not found where expected

A: 

Ugh, I get it. Scratch that.

This will identify the ID's needed to delete.

Select 
       fld1
     , fld2
     , fld3
     , Max(ID)
From table_name
Group By
       fld1
     , fld2
     , fld3
Jeff O
Actually, that will list the records the OP wants to keep: "...I want to retain only the entry with the highest ID..."
OMG Ponies
@Jeff O: This looks wrong for 2 reasons: (1) there are many non-duplicate rows, this would list all distinct rows; and (2) I want to delete not the max valued row but all rows having a lesser key.
Carl Smotricz
+2  A: 

Oracle 9i+, Using WITH:


To get the list of doomed entries, use:

WITH keepers AS (
   SELECT MAX(t.id) 'max_id',
          t.fld1, t.fld2, t.fld3
     FROM TABLE_1 t
 GROUP BY t.fld1, t.fld2, t.fld3
   HAVING COUNT(*) > 1)
SELECT x.id,
       x.fld1, x.fld2, x.fld3
  FROM TABLE_1 x
  JOIN keepers y ON y.max_id > x.id
                AND y.fld1 = x.fld1
                AND y.fld2 = x.fld2
                AND y.fld3 = x.fld3

Non-WITH Equivalent:


To get the list of doomed entries, use:

SELECT x.id,
       x.fld1, x.fld2, x.fld3
  FROM TABLE_1 x
  JOIN (SELECT MAX(t.id) 'max_id',
               t.fld1, t.fld2, t.fld3
          FROM TABLE_1 t
      GROUP BY t.fld1, t.fld2, t.fld3
        HAVING COUNT(*) > 1) y ON y.max_id > x.id
                              AND y.fld1 = x.fld1
                              AND y.fld2 = x.fld2
                              AND y.fld3 = x.fld3
OMG Ponies
Hmm. Syntax error (pls see question update) :(
Carl Smotricz
If it's easier to get right, I could also go with the WITH solution.
Carl Smotricz
@Carl Smotricz: They're equivalent - you should get the same error in theory.
OMG Ponies
Re: ORA-00923 error, try replacing `MAX(t.leg_id) 'max_id'` with `MAX(t.leg_id) AS max_id`. Dunno what you're using, but I know Toad for Oracle doesn't like single quotes for column alias definition.
OMG Ponies
DBVisualizer. This means the SQL is going thru JDBC. Moment, testing...
Carl Smotricz
Verily, it executes! `AS max_id` seems to have done it. Results look plausible too.
Carl Smotricz
@Carl Smotricz: Sleep is for the weak. SQL via JDBC means the single quotes are very likely mucking with the query if they aren't escaped properly.
OMG Ponies
Thanks to your help I can now sleep well. And dream of subselects. Happy SO'ing!
Carl Smotricz
+2  A: 

You can delete them in one shot, like this:

SQL> create table mytable (id, fld1, fld2, fld3)
  2  as
  3  select 1, 1, 1, 1 from dual union all
  4  select 2, 1, 1, 1 from dual union all
  5  select 3, 2, 2, 2 from dual union all
  6  select 4, 2, 3, 2 from dual union all
  7  select 5, 2, 3, 2 from dual union all
  8  select 6, 2, 3, 2 from dual
  9  /

Table created.

SQL> delete mytable
  2   where id not in
  3         ( select max(id)
  4             from mytable
  5            group by fld1
  6                , fld2
  7                , fld3
  8         )
  9  /

3 rows deleted.

SQL> select * from mytable
  2  /

        ID       FLD1       FLD2       FLD3
---------- ---------- ---------- ----------
         2          1          1          1
         3          2          2          2
         6          2          3          2

3 rows selected.

Regards, Rob.

Rob van Wijk
Looks good and is arguably simpler than OMG Ponies'. I'll be giving that a try later today. Thanks!
Carl Smotricz
+1: This is the better answer. Also be aware that `NOT IN`, `LEFT JOIN/IS NULL` and `NOT EXISTS` have the same performance in Oracle: http://explainextended.com/2009/09/17/not-in-vs-not-exists-vs-left-join-is-null-oracle/
OMG Ponies
@OMG Ponies: Thanks for the confirmation! This is what I ended up using, and it seems to be doing a fine job. Performance is not much of a problem as it's a nightly batch.
Carl Smotricz