Як знайти дублікати записів у PostgreSQL


189

У мене є таблиця баз даних PostgreSQL під назвою "user_links", яка в даний час дозволяє наступні дублікати полів:

year, user_id, sid, cid

Унікальне обмеження в даний час є першим поле під назвою «ідентифікатор», проте зараз я шукаю , щоб додати обмеження , щоб переконатися year, user_id, sidі cidвсе унікальні , але я не можу застосувати обмеження , оскільки повторювані значення вже існують , які порушують це обмеження.

Чи є спосіб знайти всі дублікати?


Відповіді:


335

Основною ідеєю буде використання вкладеного запиту з агрегуванням підрахунків:

select * from yourTable ou
where (select count(*) from yourTable inr
where inr.sid = ou.sid) > 1

Ви можете налаштувати пункт де у внутрішньому запиті, щоб звузити пошук.


Є ще одне хороше рішення для зазначеного в коментарях (але не кожен читає їх):

select Column1, Column2, count(*)
from yourTable
group by Column1, Column2
HAVING count(*) > 1

Або коротше:

SELECT (yourTable.*)::text, count(*)
FROM yourTable
GROUP BY yourTable.*
HAVING count(*) > 1

65
Ви також можете скористатись HAVING:select co1, col2, count(*) from tbl group by col1, col2 HAVING count(*)>1
alexkovelsky

1
Дякую @alexkovelsky, що заяву було легше змінити для мене і біг швидше. Я б запропонував відповідь з нею для більшої наочності.
Весанто

ці параметри працювали на мене, інші групують результати, і ці параметри дали мені всі дублювані записи замість того, щоб просто дублювати запис, дякую!
rome3ro

1
У мене ваша відповідь трохи повільна. На таблиці 10k рядків * 18 стовпців запит зайняв 8 секунд
вівторок

1
ось варення прямо, брате. чорт так. Дякую. 💯
дпс

90

З " Знайти дублікати рядків за допомогою PostgreSQL " ось розумне рішення:

select * from (
  SELECT id,
  ROW_NUMBER() OVER(PARTITION BY column1, column2 ORDER BY id asc) AS Row
  FROM tbl
) dups
where 
dups.Row > 1

11
Це швидко! Попрацювали над мільйонами рядків за частку секунди. Інші відповіді просто повісили там ...
dmvianna

5
Як я бачу, цей запит не враховує всіх рядків у групі. Він показує лише дублікати до чогось, частина дублікатів буде з rownum = 1. Виправте мене, якщо я помиляюся
Володимир Філіпченко

9
@vladimir Filiplipchenko Щоб мати всі рядки, додайте рівень до рішення Алесковельського:SELECT * FROM ( SELECT *, LEAD(row,1) OVER () AS nextrow FROM ( SELECT *, ROW_NUMBER() OVER(w) AS row FROM tbl WINDOW w AS (PARTITION BY col1, col2 ORDER BY col3) ) x ) y WHERE row > 1 OR nextrow > 1;
Le Droid

4
@VladimirFilipchenko Просто замініть ROW_NUMBER()на COUNT(*), і додайте rows between unbounded preceding and unbounded followingпісляORDER BY id asc
alexkovelsky

2
набагато краще, ніж інші рішення, які я знайшов. також працює однаково добре для видалення дупів із DELETE ...USINGдеякими незначними корективами
Брендон,

6

Ви можете приєднатись до тієї ж таблиці на полях, які будуть дублюватись, а потім антиприєднатись до поля id. Виберіть поле id з псевдоніму першої таблиці (tn1), а потім скористайтеся функцією array_agg у полі id другого псевдоніма таблиці. Нарешті, щоб функція array_agg працювала належним чином, ви згрупуєте результати за полем tn1.id. Це створить набір результатів, який містить ідентифікатор запису та масив усіх ідентифікаторів, які відповідають умовам з'єднання.

select tn1.id,
       array_agg(tn2.id) as duplicate_entries, 
from table_name tn1 join table_name tn2 on 
    tn1.year = tn2.year 
    and tn1.sid = tn2.sid 
    and tn1.user_id = tn2.user_id 
    and tn1.cid = tn2.cid
    and tn1.id <> tn2.id
group by tn1.id;

Очевидно, що ідентифікатори, які будуть знаходитись у масиві duplicate_entries для одного id, також матимуть власні записи у наборі результатів. Вам доведеться використовувати цей набір результатів, щоб вирішити, який ідентифікатор ви хочете стати джерелом "правди". Один запис, який не слід видаляти. Можливо, ви могли б зробити щось подібне:

with dupe_set as (
select tn1.id,
       array_agg(tn2.id) as duplicate_entries, 
from table_name tn1 join table_name tn2 on 
    tn1.year = tn2.year 
    and tn1.sid = tn2.sid 
    and tn1.user_id = tn2.user_id 
    and tn1.cid = tn2.cid
    and tn1.id <> tn2.id
group by tn1.id
order by tn1.id asc)
select ds.id from dupe_set ds where not exists 
 (select de from unnest(ds.duplicate_entries) as de where de < ds.id)

Вибирає ідентифікатори з найменшим числом, у яких є дублікати (припускаючи, що ідентифікатор збільшується в PK). Це були б посвідчення особи, які ви б зберегли.


3

З метою полегшення я припускаю, що ви хочете застосувати унікальне обмеження лише для року стовпця, а первинний ключ - це стовпець з іменем id.

Щоб знайти повторювані значення, слід запустити,

SELECT year, COUNT(id)
FROM YOUR_TABLE
GROUP BY year
HAVING COUNT(id) > 1
ORDER BY COUNT(id);

Використовуючи оператор sql вище, ви отримуєте таблицю, яка містить усі повторювані роки у вашій таблиці. Для того, щоб видалити всі дублікати, крім останньої копії дублікату, слід скористатися вищевказаним оператором sql.

DELETE
FROM YOUR_TABLE A USING YOUR_TABLE_AGAIN B
WHERE A.year=B.year AND A.id<B.id;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.