Ferry Boender

Programmer, DevOpper, Open Source enthusiast.

Email: ferry.boender@gmail.com
Github: https://www.github.com/fboender
Résumé: PDF, Dutch
PGP key: PGP key
Blog: Electricmonk.nl
Keybase: fboender

SQL: Remove duplicate rows

Tuesday, January 22nd, 2008

In the post SQL: Find duplicate rows I explained how you can find duplicate rows in a table. However, if you want to delete them, you'll run into a little problem as you have to delete all the duplicate rows except for one. Lots of solutions are floating around on the Internet, but most either don't work at all or don't work with MySQL.

Temporary table

One solution that works with MySQL is to create a temporary table to hold the data without duplicates and using the SELECT described in the post mentioned above (without the HAVING clause) and then dropping the original table and recreating it.

Suppose we have a table 'Foo' with three columns:

id int(8) auto_increment
field1 int(8)
field2 int(8)

'field1' and 'field2' don't have a unique constraint, so they might contain duplicates. To remove these duplicates, we do:

  SELECT min(id) as min, field1, field2
  FROM Foo
  GROUP BY Foo.field1, Foo.field2;


  SELECT * FROM tmp;

This will fill the 'tmp' table with unique rows from table 'Foo', and uses the lowest 'id' value for each row. So if we have two rows:

id  field1  field2
1   hi      bye 
2   hey     see ya
3   hello   goodbye
4   hi      bye

We will end up with:

id  field1  field2
1   hi      bye 
2   hey     see ya
3   hello   goodbye

In place

The above solution works well, but for some reason I don't like it. I don't like making temporary tables. I want to do it with a single query, modifying the table in place. You might call it pigheaded, as the solution presented below doesn't work as well on large datasets as the one above, but still, I found a way.

Another way to remove duplicates in MySQL is by using the following query:

  Foo, Foo t2 
  Foo.field1=t2.field1 AND 
  Foo.field2=t2.field2 AND 
  Foo.id < t2.id

There we go. Explanation: This first does a Cartesion Join on every row where the two fields 'field1' and 'field2' are the same. So if we start with this data:

1 A B  <- duplicate
2 A B  <- duplicate
3 A C

we get the Cartesian product (all possible combinations) for the 'id' column for every unique set of the 'field1' and 'field2' columns:

1 A B 1 A B
1 A B 2 A B
2 A B 1 A B
2 A B 2 A B
3 A C 3 A C

Next we say 'AND Foo.id < t2.id'. This will leave us with following row, which is the only one where the left id is smaller than the right id:

1 A B 2 A B

It then DELETE's that row from Foo (that is, the row '1 A B' - remember that the two rows with left id '2' are actually one and the same row), leaving:

2 A B
3 A C

There we go. If you want to keep the smallest 'id' for each unique set of 'field1' and 'field2', simple use 'AND Foo.id > t2.id'. It works the same way.

Remember that this is incredibly slow for large tables due to the Cartesion Join, unless you've got indexes on 'field1' and 'field2' and don't have a lot of duplicates. Otherwise, use the temporary table solution above.

The text of all posts on this blog, unless specificly mentioned otherwise, are licensed under this license.