So finden Sie doppelte Datensätze in PostgreSQL

188

Ich habe eine PostgreSQL-Datenbanktabelle mit dem Namen "user_links", die derzeit die folgenden doppelten Felder zulässt:

year, user_id, sid, cid

Die eindeutige Einschränkung ist derzeit das erste Feld „id“ genannt, aber ich bin jetzt schauen , um eine Einschränkung hinzuzufügen , dass die zu machen year, user_id, sidund cidsind alle einzigartig , aber ich kann die Einschränkung nicht anwenden , da doppelte Werte bereits die diese Bedingung verletzen existieren.

Gibt es eine Möglichkeit, alle Duplikate zu finden?

John
quelle
2
Mögliches Duplikat von Finde doppelte Zeilen mit PostgreSQL
drs

Antworten:

332

Die Grundidee besteht darin, eine verschachtelte Abfrage mit Zählaggregation zu verwenden:

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

Sie können die where-Klausel in der inneren Abfrage anpassen, um die Suche einzugrenzen.


Es gibt eine andere gute Lösung für das, was in den Kommentaren erwähnt wird (aber nicht jeder liest sie):

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

Oder kürzer:

SELECT (yourTable.*)::text, count(*)
FROM yourTable
GROUP BY yourTable.*
HAVING count(*) > 1
Marcin Zablocki
quelle
65
Sie könnten auch HAVING verwenden:select co1, col2, count(*) from tbl group by col1, col2 HAVING count(*)>1
Alexkovelsky
1
Vielen Dank an @alexkovelsky, dass die Aussage von have für mich einfacher zu ändern war und schneller lief. Ich würde eine Antwort damit für eine bessere Sichtbarkeit vorschlagen.
Vesanto
Diese Optionen haben bei mir funktioniert, die anderen gruppieren die Ergebnisse, und diese Optionen haben mir alle duplizierten Datensätze anstatt nur den duplizierten Datensatz gegeben, danke!
Rome3ro
1
Ich habe deine Antwort etwas langsam. Auf einer Tabelle 10k Zeilen * 18 Spalten dauerte die Abfrage 8 Sekunden
aydow
1
Das ist die Marmelade genau dort, Bruder. zum Teufel ja. Vielen Dank. 💯
dps
90

Unter " Doppelte Zeilen mit PostgreSQL suchen " finden Sie hier eine clevere Lösung:

select * from (
  SELECT id,
  ROW_NUMBER() OVER(PARTITION BY column1, column2 ORDER BY id asc) AS Row
  FROM tbl
) dups
where 
dups.Row > 1
alexkovelsky
quelle
11
Das geht schnell! Arbeitete über Millionen von Zeilen in Sekundenbruchteilen. Andere Antworten
hingen
5
Soweit ich sehe, berücksichtigt diese Abfrage nicht alle Zeilen innerhalb einer Gruppe. Es zeigt nur Duplikate zu etwas, ein Teil der Duplikate wird mit rownum = 1 sein. Korrigieren Sie mich, wenn ich falsch
liege
9
@vladimir Filipchenko Um es mit allen Zeilen zu haben, fügen Sie eine Ebene zu Alexkovelsky Lösung hinzu: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
3
@VladimirFilipchenko Ersetzen Sie einfach ROW_NUMBER()mit COUNT(*), und fügen Sie rows between unbounded preceding and unbounded followingnachORDER BY id asc
alexkovelsky
2
so viel besser als andere Lösungen, die ich gefunden habe. funktioniert auch gleich gut zum Löschen von Dupes mit DELETE ...USINGund einigen geringfügigen Anpassungen
Brandon
6

Sie können in den Feldern, die dupliziert werden sollen, derselben Tabelle beitreten und dann im ID-Feld eine Anti-Verknüpfung durchführen. Wählen Sie das ID-Feld aus dem ersten Tabellenalias (tn1) aus und verwenden Sie dann die Funktion array_agg für das ID-Feld des zweiten Tabellenalias. Damit die Funktion array_agg ordnungsgemäß funktioniert, gruppieren Sie die Ergebnisse nach dem Feld tn1.id. Dadurch wird eine Ergebnismenge erstellt, die die ID eines Datensatzes und ein Array aller IDs enthält, die den Verknüpfungsbedingungen entsprechen.

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;

Offensichtlich haben IDs, die sich für eine ID im Array duplicate_entries befinden, auch eigene Einträge in der Ergebnismenge. Sie müssen diese Ergebnismenge verwenden, um zu entscheiden, welche ID Sie zur Quelle der 'Wahrheit' werden möchten. Der eine Datensatz, der nicht gelöscht werden sollte. Vielleicht könnten Sie so etwas tun:

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)

Wählt die IDs mit der niedrigsten Anzahl aus, die Duplikate aufweisen (vorausgesetzt, die ID erhöht sich in PK). Dies wären die IDs, die Sie behalten würden.

pwnyexpress
quelle
3

Zur Vereinfachung gehe ich davon aus, dass Sie eine eindeutige Einschränkung nur für das Spaltenjahr anwenden möchten und der Primärschlüssel eine Spalte mit dem Namen id ist.

Um doppelte Werte zu finden, sollten Sie Folgendes ausführen:

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

Mit der obigen SQL-Anweisung erhalten Sie eine Tabelle, die alle doppelten Jahre in Ihrer Tabelle enthält. Um alle Duplikate mit Ausnahme des letzten Duplikateintrags zu löschen , sollten Sie die obige SQL-Anweisung verwenden.

DELETE
FROM YOUR_TABLE A USING YOUR_TABLE_AGAIN B
WHERE A.year=B.year AND A.id<B.id;
George Siggouroglou
quelle