0

My query deletes the whole table instead of duplicate rows. Video as proof: https://streamable.com/3s843

create table customer_info (
    id INT,
    first_name VARCHAR(50),
    last_name VARCHAR(50),
    phone_number VARCHAR(50)
);
insert into customer_info (id, first_name, last_name, phone_number) values
(1, 'Kevin', 'Binley', '600-449-1059'),
(1, 'Kevin', 'Binley', '600-449-1059'),
(2, 'Skippy', 'Lam', '779-278-0889');

My query:

with t1 as (
select *, row_number() over(partition by id order by id) as rn
from customer_info)

delete
from customer_info 
where id in (select id from t1 where rn > 1);
Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
Kay
  • 335
  • 3
  • 14

2 Answers2

1

Your query would delete all rows from each set of dupes (as all share the same id by which you select - that's what @wildplasser hinted at with subtle comments) and only initially unique rows would survive. So if it "deletes the whole table", that means there were no unique rows at all.

In your query, dupes are defined by (id) alone, not by the whole row as your title suggests.

Either way, there is a remarkably simple solution:

DELETE FROM customer_info c
WHERE  EXISTS (
   SELECT FROM customer_info c1
   WHERE  ctid < c.ctid
   AND    c1 = c  -- comparing whole rows
   );

Since you deal with completely identical rows, the remaining way to tell them apart is the internal tuple ID ctid.

My query deletes all rows, where an identical row with a smaller ctid exists. Hence, only the "first" row from each set of dupes survives.

Notably, NULL values compare equal in this case - which is most probably as desired. The manual:

The SQL specification requires row-wise comparison to return NULL if the result depends on comparing two NULL values or a NULL and a non-NULL. PostgreSQL does this only when comparing the results of two row constructors (as in Section 9.23.5) or comparing a row constructor to the output of a subquery (as in Section 9.22). In other contexts where two composite-type values are compared, two NULL field values are considered equal, [...]

If dupes are defined by id alone (as your query suggests), then this would work:

DELETE FROM customer_info c
WHERE  EXISTS (
   SELECT FROM customer_info c1
   WHERE  ctid < c.ctid
   AND    id = c.id
   );

But then there might be a better way to decide which rows to keep than ctid as a measure of last resort!

Obviously, you would then add a PRIMARY KEY to avoid the initial dilemma from reappearing. For the second interpretation, id is the candidate.

Related:

About ctid:

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
0

You can't if the table does not have a key.

Tables have "keys" that identify each row uniquely. If your table does not have any key, then you won't be able to identify one row from the other one.

The only workaround to delete duplicate rows I can think of would be to:

  1. Add a key on the table.
  2. Use the key to delete the rows that are in excess.

For example:

create sequence seq1;
alter table customer_info add column k1 int;
update customer_info set k1 = nextval('seq1');

delete from customer_info where k1 in (
  select k1 
  from (
    select
      k1,
      row_number() over(partition by id, first_name, last_name, phone_number) as rn
    from customer_info
  ) x
  where rn > 1
) 

Now you only have two rows.

The Impaler
  • 45,731
  • 9
  • 39
  • 76