Find and delete duplicate posts in WordPress
Recently a customer called me:
“We have duplicate posts on our Site, can you fix it?”
“Of course.” I said. And here’s how we did it.
The first and fastest (in other words: simplest) thing to do is to search for duplicate entries in the database. For example with the following SQL query.
SELECT a.ID, a.post_title, a.post_type, a.post_status FROM wp_posts AS a INNER JOIN ( SELECT post_title, MIN( id ) AS min_id FROM wp_posts WHERE post_type = 'post' AND post_status = 'publish' GROUP BY post_title HAVING COUNT( * ) > 1 ) AS b ON b.post_title = a.post_title AND b.min_id <> a.id AND a.post_type = 'post' AND a.post_status = 'publish'
The result shows us all post that we have in our database, which share their title at least with one other post. To make the result faster and easier readable, our SQL statement only shows us the
post_status columns from the
wp_posts table. Here are our fictional results.
|12109||Aus dem Leben eines Taugenichts||post||publish|
|23||Aus dem Leben eines Taugenichts||post||publish|
|12123||Narziß und Goldmund||post||publish|
|121||Narziß und Goldmund||post||publish|
|2123||Narziß und Goldmund||post||publish|
|145||Jakob der Lügner||post||publish|
|12||Jakob der Lügner||post||publish|
(Yes, these are all German language literature classics.)
You could now double check by performing a search for “Narziß und Goldmund” in the WordPress admin UI posts list screen. There you should get the same post duplicates as you’ve found by using the previous SQL query. As you’ll soon encounter, the reason why we choose to use the SQL admin UI over the WordPress admin UI is that we can search for all duplicate titles at once instead of running one search query for each title. And that is a huge time saver.
To proceed further with our task of cleaning out duplicates in our database, we only need to change two minor things: First we change
DELETE to actually remove the entries. And second, we switch from selecting only a few columns, that we needed for a quick glance at the duplicates, to all columns:
*. Take a look at our new SQL query.
DELETE a.* FROM wp_posts AS a INNER JOIN ( SELECT post_title, MIN( id ) AS min_id FROM wp_posts WHERE post_type = 'post' AND post_status = 'publish' GROUP BY post_title HAVING COUNT( * ) > 1 ) AS b ON b.post_title = a.post_title AND b.min_id <> a.id AND a.post_type = 'post' AND a.post_status = 'publish'
After you run this query from your database UI (that would mostly be phpMyAdmin or Adminer) or a console, you’re mostly done. Keep in mind, we recommend that you back up your database before running such a query.