null

Удаление дублирующихся строк в PostgreSQL

Как удалить дублирующиеся строки в Postgres и оставить по одному уникальному значению.

Поставновка проблемы: существует таблица в которой необходимо удалить дублирующиеся строки, но так, чтобы при этом осталось по одному значению из дублей.

Пусть у нас будет простая таблица emails для примера: 

+----+--------------------+
| id | user_email         |
+----+--------------------+
| 1  | test1@test.test    |
| 2  | test2@test.test    |
| 3  | test3@test.test    |
| 4  | test4@test.test    |
| 5  | dplc1@test.test    |
| 6  | dplc1@test.test    |
| 7  | dplc2@test.test    |
| 8  | dplc2@test.test    |
| 9  | dplc2@test.test    |
+----+--------------------+

Выведем дублирующиеся строки командой:

select email, count(*) from emails 
group by user_email
having count(*) > 1;

- сгруппируем строки по email’ам и посчитаем, сколько раз встречается каждый из них. Выберем только те значения, которые встречаются чаще одного раза и выведем их количество. 

Результат команды:
 

+-----------------+-------+
| user_email      | count |
+-----------------+-------+
| dplc1@test.test | 2     |
| dplc2@test.test | 3     |
+-----------------+-------+

Удалить дублирующиеся строки можно при помощи DELETE USING.

Оператор USING объединяет таблицы, используя для объединения таблиц одинаковые имена столбцов. Для наших целей он необходим, чтобы среди значений в таблице найти дубли, путем попарного сравнения значений. 

 

Таким образом, запрос будет выглядеть так:

delete from emails a
    using emails b
where a.id < b.id
    and a.email = b.email;


Эта команда позволит удалить только один дубль из пары.

Попробуем посмотреть, что осталось в таблице, после выполнения этой команды:
 

+----+-----------------+
| id | user_email      |
+----+-----------------+
| 1  | test1@test.test |
| 2  | test2@test.test |
| 3  | test3@test.test |
| 4  | test4@test.test |
| 5  | dplc1@test.test |
| 7  | dplc2@test.test |
+----+-----------------+

Дублирующиеся записи удалены, оставив по одной уникальной копии. То, что нужно.

 

Что же делать, если в таблице нет столбца, который может помочь унифицировать строку? Допустим, у нас имеется таблица emails, которая хранит в себе соответствия конкретных username к email’ам:

+----------+-----------------+
| username | user_email      |
+----------+-----------------+
| test1    | test1@test.test |
| test2    | test2@test.test |
| test3    | test3@test.test |
| test4    | test4@test.test |
| dplc1    | dplc1@test.test |
| dplc1    | dplc1@test.test |
| dplc2    | dplc2@test.test |
| dplc2    | dplc2@test.test |
| dplc2    | dplc2@test.test |
+----------+-----------------+

Выведем и посчитаем дублирующиеся строки тут:

select username, email, count(*) from emails 
group by user_email
having count(*) > 1;

 

В результате видим, что в таблице имеются дублирующиеся строки:

+----------+-----------------+-------+
| username | user_email      | count |
+----------+-----------------+-------+
| dplc1    | dplc1@test.test | 2     |
| dplc2    | dplc2@test.test | 3     |
+----------+-----------------+-------+

 

Что делать в этом случае, когда у нас нет явного идентификатора в строке? А все просто, каждая строка в PostgreSQL имеет скрытый неявный идентификатор, который мы можем также использовать в качестве отличительного признака между дублирующимися строками. 

А именно поле CTID, который предназначен для расположения кортежа. CTID — это скрытая и уникальная запись для каждой таблицы в PostgreSQL. Имеет тип данных системного столбца CTID и представляет собой пару (номер блока, индекс кортежа внутри блока).


Таким образом, для удаления дублирующихся строк в таблице можно использовать такую команду: 
 

delete
from emails a
   using emails b
where a.CTID < b.CTID
 and a.email = b.email
 and a.username = b.username;

Посмотрим, что получится в результате: 
 

+----------+-----------------+
| username | user_email      |
+----------+-----------------+
| test1    | test1@test.test |
| test2    | test2@test.test |
| test3    | test3@test.test |
| test4    | test4@test.test |
| dplc1    | dplc1@test.test |
| dplc2    | dplc2@test.test |
+----------+-----------------+