Muszę dodać unikalne ograniczenie do istniejącej tabeli. Jest to w porządku, z wyjątkiem tego, że tabela ma już miliony wierszy, a wiele z nich narusza unikalne ograniczenie, które muszę dodać.
Jaka jest najszybsza metoda usuwania nieprawidłowych wierszy? Mam instrukcję SQL, która znajduje duplikaty i usuwa je, ale jej uruchomienie trwa wieczność. Czy jest inny sposób rozwiązania tego problemu? Może utworzyć kopię zapasową tabeli, a następnie przywrócić ją po dodaniu ograniczenia?
CREATE TABLE tmp AS SELECT ...;
. Wtedy nie musisz nawet zastanawiać się, jaki jest układtmp
. :)Niektóre z tych podejść wydają się nieco skomplikowane i generalnie robię to jako:
Podana tabela
table
, chcesz ją unikatową na (field1, field2) zachowując wiersz z max field3:DELETE FROM table USING table alias WHERE table.field1 = alias.field1 AND table.field2 = alias.field2 AND table.max_field < alias.max_field
Na przykład mam tabelę
user_accounts
i chcę dodać unikalne ograniczenie dotyczące poczty elektronicznej, ale mam kilka duplikatów. Powiedz również, że chcę zachować ostatnio utworzony (maksymalny identyfikator wśród duplikatów).DELETE FROM user_accounts USING user_accounts ua2 WHERE user_accounts.email = ua2.email AND user_account.id < ua2.id;
USING
nie jest to standardowy SQL, jest to rozszerzenie PostgreSQL (ale bardzo przydatne), ale w oryginalnym pytaniu jest mowa o PostgreSQL.źródło
USING
w postgresql?WHERE table1.ctid<table2.ctid
- nie ma potrzeby dodawania kolumny szeregowejZamiast tworzyć nową tabelę, możesz również ponownie wstawić unikalne wiersze do tej samej tabeli po jej obcięciu. Zrób to wszystko w jednej transakcji . Opcjonalnie możesz automatycznie usunąć tabelę tymczasową na końcu transakcji za pomocą
ON COMMIT DROP
. Zobacz poniżej.To podejście jest przydatne tylko wtedy, gdy istnieje wiele wierszy do usunięcia z całej tabeli. W przypadku kilku duplikatów użyj zwykłego
DELETE
.Wspomniałeś o milionach wierszy. Aby wykonać operację FAST chcesz przeznaczyć wystarczających buforów tymczasowych dla sesji. To ustawienie należy zmienić przed użyciem jakiegokolwiek bufora tymczasowego w bieżącej sesji. Sprawdź rozmiar swojego stołu:
SELECT pg_size_pretty(pg_relation_size('tbl'));
Ustaw
temp_buffers
odpowiednio. Zaokrąglij hojnie, ponieważ reprezentacja w pamięci wymaga nieco więcej pamięci RAM.SET temp_buffers = 200MB; -- example value BEGIN; -- CREATE TEMPORARY TABLE t_tmp ON COMMIT DROP AS -- drop temp table at commit CREATE TEMPORARY TABLE t_tmp AS -- retain temp table after commit SELECT DISTINCT * FROM tbl; -- DISTINCT folds duplicates TRUNCATE tbl; INSERT INTO tbl SELECT * FROM t_tmp; -- ORDER BY id; -- optionally "cluster" data while being at it. COMMIT;
Ta metoda może być lepsza niż tworzenie nowej tabeli, jeśli istnieją zależne obiekty. Widoki, indeksy, klucze obce lub inne obiekty odwołujące się do tabeli.
TRUNCATE
sprawia, że i tak zaczynasz z czystym kontem (nowy plik w tle) i jest znacznie szybszy niżDELETE FROM tbl
przy dużych stołach (wDELETE
rzeczywistości może być szybszy przy małych stołach).W przypadku dużych tabel regularnie szybciej usuwa się indeksy i klucze obce, uzupełnia tabelę i ponownie tworzy te obiekty. Jeśli chodzi o ograniczenia fk, musisz oczywiście mieć pewność, że nowe dane są prawidłowe, w przeciwnym razie napotkasz wyjątek podczas próby utworzenia fk.
Należy pamiętać, że
TRUNCATE
wymaga bardziej agresywnego blokowania niżDELETE
. Może to być problem w przypadku tabel z dużym, równoczesnym obciążeniem.Jeśli
TRUNCATE
nie jest to opcja lub ogólnie w przypadku małych i średnich tabel, istnieje podobna technika z modyfikacją danych CTE (Postgres 9.1 +):WITH del AS (DELETE FROM tbl RETURNING *) INSERT INTO tbl SELECT DISTINCT * FROM del; -- ORDER BY id; -- optionally "cluster" data while being at it.
Wolniej przy dużych stołach, bo
TRUNCATE
tam jest szybciej. Ale może być szybszy (i prostszy!) Dla małych stołów.Jeśli nie masz żadnych obiektów zależnych, możesz utworzyć nową tabelę i usunąć starą, ale prawie nic nie zyskujesz dzięki temu uniwersalnemu podejściu.
W przypadku bardzo dużych tabel, które nie mieszczą się w dostępnej pamięci RAM , tworzenie nowej tabeli będzie znacznie szybsze. Będziesz musiał rozważyć to z możliwymi problemami / kosztami związanymi z zależnymi obiektami.
źródło
TRUNCATE
. Jak powiedział Erwin, przed skróceniem tabeli upewnij się, że istnieje. Zobacz odpowiedź @ codebykatON COMMIT DROP
, aby osoby, którym brakuje fragmentu, w którym napisałem „w jednej transakcji”, nie tracą danych. I dodałem BEGIN / COMMIT, aby wyjaśnić „jedną transakcję”.Możesz użyć oid lub ctid, które zwykle są „niewidocznymi” kolumnami w tabeli:
DELETE FROM table WHERE ctid NOT IN (SELECT MAX(s.ctid) FROM table s GROUP BY s.column_has_be_distinct);
źródło
NOT EXISTS
powinno być znacznie szybsze :DELETE FROM tbl t WHERE EXISTS (SELECT 1 FROM tbl t1 WHERE t1.dist_col = t.dist_col AND t1.ctid > t.ctid)
- lub użyj dowolnej innej kolumny lub zestawu kolumn do sortowania, aby wybrać ocalałego.NOT EXISTS
?EXISTS
tutaj. Przeczytaj to w ten sposób: „Usuń wszystkie wiersze, w których istnieje inny wiersz o tej samej wartości w,dist_col
ale większymctid
”. Jedynym ocalałym z każdej grupy oszustów będzie ten z największymctid
.LIMIT
jeśli znasz liczbę duplikatów.W przypadku tego problemu przydatna jest funkcja okna PostgreSQL.
DELETE FROM tablename WHERE id IN (SELECT id FROM (SELECT id, row_number() over (partition BY column1, column2, column3 ORDER BY id) AS rnum FROM tablename) t WHERE t.rnum > 1);
Zobacz Usuwanie duplikatów .
źródło
Uogólnione zapytanie do usuwania duplikatów:
DELETE FROM table_name WHERE ctid NOT IN ( SELECT max(ctid) FROM table_name GROUP BY column1, [column 2, ...] );
Kolumna
ctid
jest specjalną kolumną dostępną dla każdej tabeli, ale niewidoczną, o ile nie zaznaczono inaczej. Wartośćctid
kolumny jest uważana za unikalną dla każdego wiersza w tabeli. Zobacz kolumny systemowe PostgreSQL, aby dowiedzieć się więcejctid
.źródło
GROUP BY
klauzuli - powinny to być „kryteria unikalności”, które są obecnie naruszane lub jeśli chcesz, aby klucz wykrywał duplikaty. Jeśli podano źle, nie będzie działać poprawnieZe starej listy mailingowej postgresql.org :
create table test ( a text, b text );
Unikalne wartości
insert into test values ( 'x', 'y'); insert into test values ( 'x', 'x'); insert into test values ( 'y', 'y' ); insert into test values ( 'y', 'x' );
Zduplikowane wartości
insert into test values ( 'x', 'y'); insert into test values ( 'x', 'x'); insert into test values ( 'y', 'y' ); insert into test values ( 'y', 'x' );
Jeszcze jeden podwójny duplikat
insert into test values ( 'x', 'y'); select oid, a, b from test;
Wybierz zduplikowane wiersze
select o.oid, o.a, o.b from test o where exists ( select 'x' from test i where i.a = o.a and i.b = o.b and i.oid < o.oid );
Usuń zduplikowane wiersze
Uwaga: PostgreSQL nie obsługuje aliasów w tabeli wymienionej w
from
klauzuli usuwania.delete from test where exists ( select 'x' from test i where i.a = test.a and i.b = test.b and i.oid < test.oid );
źródło
Właśnie użyłem odpowiedzi Erwina Brandstettera z powodzeniem, aby usunąć duplikaty w tabeli łączenia (tabela bez własnych podstawowych identyfikatorów), ale stwierdziłem, że jest jedno ważne zastrzeżenie.
W tym
ON COMMIT DROP
oznacza, że tymczasowa tabela zostanie usunięta po zakończeniu transakcji. Dla mnie oznaczało to, że tymczasowy stół nie był już dostępny , zanim poszedłem go wstawić!Właśnie zrobiłem
CREATE TEMPORARY TABLE t_tmp AS SELECT DISTINCT * FROM tbl;
i wszystko działało dobrze.Tabela tymczasowa zostaje usunięta pod koniec sesji.
źródło
Ta funkcja usuwa duplikaty bez usuwania indeksów i robi to w dowolnej tabeli.
Stosowanie:
select remove_duplicates('mytable');
źródło
DELETE FROM table WHERE something NOT IN (SELECT MAX(s.something) FROM table As s GROUP BY s.this_thing, s.that_thing);
źródło
Jeśli masz tylko jeden lub kilka zduplikowanych wpisów i rzeczywiście są one zduplikowane (to znaczy pojawiają się dwukrotnie), możesz użyć
ctid
kolumny „ukryte” , jak zaproponowano powyżej, razem zLIMIT
:DELETE FROM mytable WHERE ctid=(SELECT ctid FROM mytable WHERE […] LIMIT 1);
Spowoduje to usunięcie tylko pierwszego z wybranych wierszy.
źródło
Najpierw musisz zdecydować, które ze swoich „duplikatów” zatrzymasz. Jeśli wszystkie kolumny są równe, OK, możesz usunąć dowolną z nich ... Ale może chcesz zachować tylko najnowsze lub inne kryterium?
Najszybszy sposób zależy od twojej odpowiedzi na powyższe pytanie, a także od% duplikatów na stole. Jeśli wyrzucisz 50% wierszy, lepiej to zrobisz
CREATE TABLE ... AS SELECT DISTINCT ... FROM ... ;
, a jeśli usuniesz 1% wierszy, użycie DELETE jest lepsze.Również w przypadku takich czynności konserwacyjnych, ogólnie dobrze jest ustawić
work_mem
dobrą część pamięci RAM: uruchom EXPLAIN, sprawdź liczbę N rodzajów / skrótów i ustaw work_mem na pamięć RAM / 2 / N. Użyj dużej ilości pamięci RAM; to dobre dla szybkości. O ile masz tylko jedno równoczesne połączenie ...źródło
Pracuję z PostgreSQL 8.4. Kiedy uruchomiłem proponowany kod, stwierdziłem, że w rzeczywistości nie usuwa on duplikatów. Podczas wykonywania niektórych testów stwierdziłem, że dodanie „DISTINCT ON (duplicate_column_name)” i „ORDER BY duplicate_column_name” załatwiło sprawę. Nie jestem guru SQL, znalazłem to w dokumencie PostgreSQL 8.4 SELECT ... DISTINCT.
CREATE OR REPLACE FUNCTION remove_duplicates(text, text) RETURNS void AS $$ DECLARE tablename ALIAS FOR $1; duplicate_column ALIAS FOR $2; BEGIN EXECUTE 'CREATE TEMPORARY TABLE _DISTINCT_' || tablename || ' AS (SELECT DISTINCT ON (' || duplicate_column || ') * FROM ' || tablename || ' ORDER BY ' || duplicate_column || ' ASC);'; EXECUTE 'DELETE FROM ' || tablename || ';'; EXECUTE 'INSERT INTO ' || tablename || ' (SELECT * FROM _DISTINCT_' || tablename || ');'; EXECUTE 'DROP TABLE _DISTINCT_' || tablename || ';'; RETURN; END; $$ LANGUAGE plpgsql;
źródło
Działa to bardzo ładnie i jest bardzo szybkie:
CREATE INDEX otherTable_idx ON otherTable( colName ); CREATE TABLE newTable AS select DISTINCT ON (colName) col1,colName,col2 FROM otherTable;
źródło
DELETE FROM tablename WHERE id IN (SELECT id FROM (SELECT id,ROW_NUMBER() OVER (partition BY column1, column2, column3 ORDER BY id) AS rnum FROM tablename) t WHERE t.rnum > 1);
Usuń duplikaty według kolumn i zachowaj wiersz o najniższym identyfikatorze. Wzorzec jest pobierany z wiki postgres
Używając CTE, możesz dzięki temu uzyskać bardziej czytelną wersję powyższego
WITH duplicate_ids as ( SELECT id, rnum FROM num_of_rows WHERE rnum > 1 ), num_of_rows as ( SELECT id, ROW_NUMBER() over (partition BY column1, column2, column3 ORDER BY id) AS rnum FROM tablename ) DELETE FROM tablename WHERE id IN (SELECT id from duplicate_ids)
źródło
CREATE TABLE test (col text); INSERT INTO test VALUES ('1'), ('2'), ('2'), ('3'), ('4'), ('4'), ('5'), ('6'), ('6'); DELETE FROM test WHERE ctid in ( SELECT t.ctid FROM ( SELECT row_number() over ( partition BY col ORDER BY col ) AS rnum, ctid FROM test ORDER BY col ) t WHERE t.rnum >1);
źródło