Видалити копії записів у PostgreSQL


113

У мене є таблиця в базі даних PostgreSQL 8.3.8, яка не має на ній ключів / обмежень і має кілька рядків з абсолютно однаковими значеннями.

Я хотів би видалити всі дублікати і зберегти лише 1 примірник кожного рядка.

Зокрема, є один стовпець (з назвою "ключ"), який може використовуватися для ідентифікації дублікатів (тобто повинен існувати лише один запис для кожного окремого "ключа").

Як я можу це зробити? (в ідеалі з однією командою SQL) Швидкість не є проблемою в цьому випадку (є лише кілька рядків).

Відповіді:


80
DELETE FROM dupes a
WHERE a.ctid <> (SELECT min(b.ctid)
                 FROM   dupes b
                 WHERE  a.key = b.key);

20
Не використовуйте це, це занадто повільно!
Paweł Malisak

5
Хоча це рішення безумовно працює, рішення @rapimo нижче виконується набагато швидше. Я вважаю, що це стосується внутрішнього оператора select тут, який виконується N разів (для всіх N рядків у таблиці дупів), а не з групуванням, що відбувається в іншому рішенні.
Девід

Для величезних таблиць (кілька мільйонів записів) ця насправді вписується в пам’ять, на відміну від рішення @ rapimo. Тож у цих випадках це швидше (немає заміни).
Giel

1
Додавання пояснення: він працює, тому що ctid - це спеціальний стовпчик postgres із зазначенням фізичного розташування рядка. Ви можете використовувати це як унікальний ідентифікатор, навіть якщо у вашій таблиці немає унікального ідентифікатора. postgresql.org/docs/8.2/ddl-system-columns.html
Ерік

194

Більш швидке рішення

DELETE FROM dups a USING (
      SELECT MIN(ctid) as ctid, key
        FROM dups 
        GROUP BY key HAVING COUNT(*) > 1
      ) b
      WHERE a.key = b.key 
      AND a.ctid <> b.ctid

20
Чому це швидше, ніж рішення a_horse_with_no_name?
Роберто

3
Це швидше, оскільки це виконує лише 2 запити. Спочатку виберіть усі дублікати, потім - один, щоб видалити всі елементи з таблиці. Запит від @a_horse_with_no_name виконує запит, щоб побачити, чи відповідає він будь-якому іншому для кожного окремого елемента таблиці.
Еолун

5
що таке ctid?
техкуз

6
від docs: ctid. Фізичне розташування рядкової версії в межах її таблиці. Зауважте, що хоча ctid може бути використаний для пошуку версії рядка дуже швидко, ctid рядка буде змінюватися щоразу, коли він оновлюється або переміщується VACUUM FULL. Тому ctid марний як довгостроковий ідентифікатор рядків.
Saim

1
Здається, що це не працює, якщо є більше двох повторюваних рядків, оскільки він видаляє лише один дублікат одночасно.
Френкі Дрейк

73

Це швидко і стисло:

DELETE FROM dupes T1
    USING   dupes T2
WHERE   T1.ctid < T2.ctid  -- delete the older versions
    AND T1.key  = T2.key;  -- add more columns if needed

Дивіться також мою відповідь у розділі Як видалити повторювані рядки без унікального ідентифікатора, який містить додаткову інформацію.


що означає ct? рахувати?
техкуз

4
@trthhrtz ctidвказує на фізичне розташування запису в таблиці. На відміну від того, про що я писав у коментарі, використання оператора менше ніж оператор не обов'язково вказує на старішу версію, оскільки ct може обертатися, а значення з нижчим ctid насправді може бути новішим.
isapir

1
Тільки FYI, я спробував це рішення, і перервав його, зачекавши 15 хвилин. Спробував рішення Рапімо, і воно завершилося приблизно за 10 секунд (видалено ~ 700 000 рядків).
Патрік

@Patrick не може уявити, якщо у вашого db немає унікального ідентифікатора, оскільки відповідь rapimo у цьому випадку не працює.
ліпнина

@isapir Мені просто цікаво, відповіді вище, вони зберігають старі записи правильно, як вони вибрали min(ctid)? тоді як ваш зберігає новіші? Дякую!
ліпнина

17

Я спробував це:

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 wiki:

https://wiki.postgresql.org/wiki/Deleting_duplicates


Будь-яка ідея виступу порівняно з відповіддю @ rapimo та прийнятою (@a_horse_with_no_name)?
tuxayo

3
Цей не працюватиме, якщо, як і у запитаннях, усі стовпці однакові, idвключені.
ібізаман

Цей запит видалить як оригінальну копію, так і дублікати. питання полягає у збереженні хоча б одного ряду.
pyBomb

@pyBomb помиляється, він збереже перше місце, idде колонка1 ... 3 дублюється
Джефф

Станом на postgresql 12, це НАЙДОБРЕ найшвидше рішення (проти 300 мільйонів рядків). Я просто перевірив все, що пропонується в цьому питанні, включаючи прийняту відповідь, і це "офіційне" рішення насправді є найшвидшим і відповідає всім вимогам від ОП (і моїх)
Джефф

7

Мені довелося створити власну версію. Версія, написана @a_horse_with_no_name, занадто повільна на моєму столі (21М рядків). І @rapimo просто не видаляє дупи.

Ось що я використовую у PostgreSQL 9.5

DELETE FROM your_table
WHERE ctid IN (
  SELECT unnest(array_remove(all_ctids, actid))
  FROM (
         SELECT
           min(b.ctid)     AS actid,
           array_agg(ctid) AS all_ctids
         FROM your_table b
         GROUP BY key1, key2, key3, key4
         HAVING count(*) > 1) c);

6

Я б використовував тимчасову таблицю:

create table tab_temp as
select distinct f1, f2, f3, fn
  from tab;

Потім видаліть tabі перейменуйте tab_tempв tab.


8
Цей підхід не враховує тригери, індекси та статистику. Звичайно, ви можете їх додати, але це також додає набагато більше роботи.
Йорданія

Не всім це потрібно. Цей підхід надзвичайно швидкий і працював набагато краще, ніж решта на 200k електронних листів (varchar 250) без індексів.
Сергій Тельшевський

Повний код:DROP TABLE IF EXISTS tmp; CREATE TABLE tmp as ( SELECT * from (SELECT DISTINCT * FROM your_table) as t ); DELETE from your_table; INSERT INTO your_table SELECT * from tmp; DROP TABLE tmp;
Ерік

1

Інший підхід (працює лише в тому випадку, якщо у вас є якесь унікальне поле, як idу вашій таблиці) для пошуку всіх унікальних ідентифікаторів за стовпцями та видалення інших ідентифікаторів, які не є в унікальному списку

DELETE
FROM users
WHERE users.id NOT IN (SELECT DISTINCT ON (username, email) id FROM users);

Річ у тому, що в моєму питанні таблиці не мали унікальних ідентифікаторів; "дублікати" були декількома рядками з абсолютно однаковими значеннями у всіх стовпцях.
Андре Моруджао

Правильно, я додав кілька записок
Зайцев Дмитро

1

Як щодо:

З
  u ЯК (ВИБІРТЕ ДИСТАНТ * від ВІД_таблиці),
  x ЯК (Зняти з вашого_таблиці)
ВСТАВИТИ У your_table SELECT * ВІД u;

Я був стурбований замовленням на виконання, чи відбудеться ВИДАЛЕНО до вибору ВИБІРУВАННЯ, але це працює добре для мене. І має додатковий бонус за те, що не потрібно ніяких знань про структуру таблиці.


Єдиним недоліком є ​​те, що якщо у вас є тип даних, який не підтримує рівність (наприклад json), це не працюватиме.
a_horse_with_no_name

0

Це добре спрацювало для мене. У мене була таблиця, терміни, яка містила повторювані значення. Запустив запит, щоб заповнити таблицю темпів усіма повторюваними рядками. Потім я запустив операцію видалення з тими ідентифікаторами в темп таблиці. значення - стовпець, який містив дублікати.

        CREATE TEMP TABLE dupids AS
        select id from (
                    select value, id, row_number() 
over (partition by value order by value) 
    as rownum from terms
                  ) tmp
                  where rownum >= 2;

delete from [table] where id in (select id from dupids)

0

Ось рішення з використанням PARTITION BY:

DELETE FROM dups
USING (
  SELECT
    ctid,
    (ctid != min(ctid) OVER (PARTITION BY key_column1, key_column2 [...])) AS is_duplicate
  FROM dups 
) dups_find_duplicates
WHERE dups.ctid == dups_find_duplicates.ctid
AND dups_find_duplicates.is_duplicate
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.