Мне нужно добавить уникальное ограничение к существующей таблице. Это нормально, за исключением того, что в таблице уже есть миллионы строк, и многие из них нарушают уникальное ограничение, которое мне нужно добавить.
Каков самый быстрый способ удаления ошибочных строк? У меня есть оператор SQL, который находит дубликаты и удаляет их, но его выполнение занимает вечность. Есть ли другой способ решить эту проблему? Может быть, сделать резервную копию таблицы, а затем восстановить после добавления ограничения?
CREATE TABLE tmp AS SELECT ...;
. Тогда не нужно даже разбираться, что такое макетtmp
. :)Некоторые из этих подходов кажутся немного сложными, и я обычно делаю это следующим образом:
Для
table
данной таблицы нужно сделать ее уникальной (field1, field2), сохраняя строку с максимальным field3:DELETE FROM table USING table alias WHERE table.field1 = alias.field1 AND table.field2 = alias.field2 AND table.max_field < alias.max_field
Например, у меня есть таблица,
user_accounts
и я хочу добавить уникальное ограничение для электронной почты, но у меня есть несколько дубликатов. Скажите также, что я хочу сохранить последний созданный (максимальный идентификатор среди дубликатов).DELETE FROM user_accounts USING user_accounts ua2 WHERE user_accounts.email = ua2.email AND user_account.id < ua2.id;
USING
Это не стандартный SQL, это расширение PostgreSQL (но очень полезное), но в исходном вопросе конкретно упоминается PostgreSQL.источник
USING
в postgresql?WHERE table1.ctid<table2.ctid
- столбец с порядковымВместо создания новой таблицы вы также можете повторно вставить уникальные строки в ту же таблицу после ее усечения. Сделайте все за одну транзакцию . При желании вы можете автоматически удалить временную таблицу в конце транзакции с помощью
ON COMMIT DROP
. Увидеть ниже.Этот подход полезен только в том случае, если нужно удалить много строк по всей таблице. Для нескольких дубликатов используйте простой
DELETE
.Вы упомянули миллионы строк. Чтобы сделать операцию быстрой, вам нужно выделить достаточно временных буферов для сеанса. Этот параметр необходимо изменить до того, как в текущем сеансе будет использоваться какой-либо временный буфер. Узнайте размер вашего стола:
SELECT pg_size_pretty(pg_relation_size('tbl'));
Установите
temp_buffers
соответственно. Обильно округлите, потому что для представления в памяти требуется немного больше ОЗУ.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;
Этот метод может быть лучше создания новой таблицы, если существуют зависимые объекты. Представления, индексы, внешние ключи или другие объекты, ссылающиеся на таблицу.
TRUNCATE
заставляет вас начать с чистого листа в любом случае (новый файл в фоновом режиме) и много быстрее, чемDELETE FROM tbl
с большими таблицами (наDELETE
самом деле может быть быстрее с маленькими таблицами).Для больших столов регулярно быстрее отбрасывать индексы и внешние ключи, заполнять таблицу и воссоздавать эти объекты. Что касается ограничений fk, вы, конечно, должны быть уверены, что новые данные действительны, иначе вы столкнетесь с исключением при попытке создать fk.
Обратите внимание, что
TRUNCATE
требуется более агрессивная блокировка, чемDELETE
. Это может быть проблемой для таблиц с большой одновременной нагрузкой.Если
TRUNCATE
это не вариант или, как правило, для небольших и средних таблиц существует аналогичная техника с 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.
Медленнее для больших столов, потому что
TRUNCATE
там быстрее. Но может быть быстрее (и проще!) Для небольших столов.Если у вас вообще нет зависимых объектов, вы можете создать новую таблицу и удалить старую, но вы вряд ли получите что-либо от этого универсального подхода.
Для очень больших таблиц, которые не помещаются в доступную оперативную память , создание новой таблицы будет значительно быстрее. Вам придется взвесить это с возможными проблемами / накладными расходами с зависимыми объектами.
источник
TRUNCATE
. Как сказал Эрвин, обязательно убедитесь, что он существует, прежде чем обрезать вашу таблицу. См. Ответ @ codebykatON COMMIT DROP
, чтобы люди, пропустившие ту часть, где я написал «за одну транзакцию», не потеряли данные. И я добавил BEGIN / COMMIT, чтобы уточнить «одну транзакцию».Вы можете использовать oid или ctid, которые обычно являются «невидимыми» столбцами в таблице:
DELETE FROM table WHERE ctid NOT IN (SELECT MAX(s.ctid) FROM table s GROUP BY s.column_has_be_distinct);
источник
NOT EXISTS
должно быть значительно быстрее :DELETE FROM tbl t WHERE EXISTS (SELECT 1 FROM tbl t1 WHERE t1.dist_col = t.dist_col AND t1.ctid > t.ctid)
- или использовать любой другой столбец или набор столбцов для сортировки , чтобы выбрать выживший.NOT EXISTS
?EXISTS
здесь. Прочтите это так: «Удалите все строки, в которых существует другая строка с тем же значением,dist_col
но с большимctid
». Единственным выжившим из группы обманутых будет тот, у кого больше всехctid
.LIMIT
если известно количество дубликатов.Оконная функция 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);
См. Удаление дубликатов .
источник
Обобщенный запрос на удаление дубликатов:
DELETE FROM table_name WHERE ctid NOT IN ( SELECT max(ctid) FROM table_name GROUP BY column1, [column 2, ...] );
Столбец
ctid
- это специальный столбец, доступный для каждой таблицы, но не видимый, если специально не указано иное. Значениеctid
столбца считается уникальным для каждой строки в таблице. См. Системные столбцы PostgreSQL, чтобы узнать большеctid
.источник
GROUP BY
предложение - это должен быть «критерий уникальности», который сейчас нарушен, или если вы хотите, чтобы ключ обнаруживал дубликаты. Если указано неверное значение, он не будет работать правильноИз старого списка рассылки postgresql.org :
create table test ( a text, b text );
Уникальные ценности
insert into test values ( 'x', 'y'); insert into test values ( 'x', 'x'); insert into test values ( 'y', 'y' ); insert into test values ( 'y', 'x' );
Повторяющиеся значения
insert into test values ( 'x', 'y'); insert into test values ( 'x', 'x'); insert into test values ( 'y', 'y' ); insert into test values ( 'y', 'x' );
Еще один двойной дубликат
insert into test values ( 'x', 'y'); select oid, a, b from test;
Выберите повторяющиеся строки
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 );
Удалить повторяющиеся строки
Примечание. PostgreSQL не поддерживает псевдонимы для таблицы, упомянутой в
from
пункте удаления.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 );
источник
Я просто использовал ответ Эрвина Брандштеттера успешно для удаления дубликатов в таблице соединений (таблица без собственных основных идентификаторов), но обнаружил, что есть одно важное предостережение.
Включение
ON COMMIT DROP
означает, что временная таблица будет удалена в конце транзакции. Для меня это означало, что временная таблица больше не была доступна к тому времени, когда я пошел вставлять ее!Я просто сделал,
CREATE TEMPORARY TABLE t_tmp AS SELECT DISTINCT * FROM tbl;
и все работало нормально.Временная таблица удаляется в конце сеанса.
источник
Эта функция удаляет дубликаты без удаления индексов и делает это с любой таблицей.
Применение:
select remove_duplicates('mytable');
источник
DELETE FROM table WHERE something NOT IN (SELECT MAX(s.something) FROM table As s GROUP BY s.this_thing, s.that_thing);
источник
Если у вас есть только одна или несколько дублированных записей, и они действительно дублируются (то есть появляются дважды), вы можете использовать «скрытый»
ctid
столбец, как предложено выше, вместе сLIMIT
:DELETE FROM mytable WHERE ctid=(SELECT ctid FROM mytable WHERE […] LIMIT 1);
Это удалит только первую из выбранных строк.
источник
Во-первых, вам нужно решить, какие из ваших «дубликатов» вы сохраните. Если все столбцы равны, хорошо, вы можете удалить любой из них ... Но, может быть, вы хотите оставить только самые последние или какой-то другой критерий?
Самый быстрый способ зависит от вашего ответа на вопрос выше, а также от процента дубликатов в таблице. Если вы выбросите 50% строк, вам будет лучше
CREATE TABLE ... AS SELECT DISTINCT ... FROM ... ;
, а если вы удалите 1% строк, лучше использовать DELETE.Также для таких операций обслуживания, как правило, хорошо установить
work_mem
хороший кусок вашей RAM: запустите EXPLAIN, проверьте количество N сортов / хэшей и установите work_mem равным RAM / 2 / N. Используйте много RAM; это хорошо для скорости. Пока у вас есть только одно одновременное соединение ...источник
Я работаю с PostgreSQL 8.4. Когда я запустил предложенный код, я обнаружил, что на самом деле он не удаляет дубликаты. Выполняя некоторые тесты, я обнаружил, что добавление «DISTINCT ON (duplicate_column_name)» и «ORDER BY duplicate_column_name» помогло. Я не гуру SQL, я нашел это в документе 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;
источник
Это работает очень хорошо и очень быстро:
CREATE INDEX otherTable_idx ON otherTable( colName ); CREATE TABLE newTable AS select DISTINCT ON (colName) col1,colName,col2 FROM otherTable;
источник
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);
Удалите дубликаты по столбцам и оставьте строку с наименьшим идентификатором. Шаблон взят из вики postgres
Используя CTE, вы можете получить более читаемую версию вышеуказанного с помощью этого
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)
источник
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);
источник