Вопрос

Я должен добавить уникальное ограничение к существующей таблице.Это нормально, за исключением того, что в таблице уже есть миллионы строк, и многие из строк нарушают ограничение уникальности, которое мне нужно добавить.

Каков самый быстрый подход к удалению строк-нарушителей?У меня есть оператор SQL, который находит дубликаты и удаляет их, но для его запуска требуется вечность.Есть ли другой способ решить эту проблему?Может быть, создать резервную копию таблицы, а затем восстановить после добавления ограничения?

Это было полезно?

Решение

Например, вы можете:

CREATE TABLE tmp ...
INSERT INTO tmp SELECT DISTINCT * FROM t;
DROP TABLE t;
ALTER TABLE tmp RENAME TO t;

Другие советы

Некоторые из этих подходов кажутся немного сложными, и я обычно делаю это следующим образом:

Данная таблица table, хотите сделать его уникальным в (поле1, поле2), сохраняя строку с максимальным полем3:

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.

Вместо создания новой таблицы вы также можете повторно вставить уникальные строки в ту же таблицу после ее усечения.Делай все это в одной транзакции.При желании вы можете автоматически удалить временную таблицу в конце транзакции с помощью 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, изменяющий данные (Постгрес 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 там быстрее.Но может быть быстрее (и проще!) для небольших таблиц.

Если у вас вообще нет зависимых объектов, вы можете создать новую таблицу и удалить старую, но вы вряд ли что-то получите от этого универсального подхода.

Для очень больших таблиц, которые не помещаются в доступная оперативная память, создавая новый table будет работать значительно быстрее.Вам придется сопоставить это с возможными проблемами/накладными расходами на зависимых объектах.

Вы можете использовать oid или ctid, которые обычно являются «невидимыми» столбцами в таблице:

DELETE FROM table
 WHERE ctid NOT IN
  (SELECT MAX(s.ctid)
    FROM table s
    GROUP BY s.column_has_be_distinct);

Оконная функция 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.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');

---
--- remove_duplicates(tablename) removes duplicate records from a table (convert from set to unique set)
---
CREATE OR REPLACE FUNCTION remove_duplicates(text) RETURNS void AS $$
DECLARE
  tablename ALIAS FOR $1;
BEGIN
  EXECUTE 'CREATE TEMPORARY TABLE _DISTINCT_' || tablename || ' AS (SELECT DISTINCT * FROM ' || tablename || ');';
  EXECUTE 'DELETE FROM ' || tablename || ';';
  EXECUTE 'INSERT INTO ' || tablename || ' (SELECT * FROM _DISTINCT_' || tablename || ');';
  EXECUTE 'DROP TABLE _DISTINCT_' || tablename || ';';
  RETURN;
END;
$$ LANGUAGE plpgsql;
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 на большую часть вашей оперативной памяти:запустите EXPLAIN, проверьте количество N сортировок/хэшей и установите для work_mem значение вашей RAM / 2 / N.Используйте много оперативной памяти;это хорошо для скорости.Пока у вас есть только одно одновременное соединение...

Я работаю с PostgreSQL 8.4.Когда я предложил код, я обнаружил, что он не был удаляя дубликаты.Запустив несколько тестов, я обнаружил, что добавление "DISTINCT ON (дубликат_колоно_имя)" и "ORDER BY дубликат_колоно_имя" сделало свое дело.Я не гуру SQL, я нашел это в PostgreSQL 8.4 SELECT...ОТДЕЛЬНЫЙ документ.

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);

Удалите дубликаты по столбцам и сохраните строку с наименьшим идентификатором.Выкройка взята из Постгрес вики

Используя 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);
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top