PostgreSQL'de Yinelenen Kayıtları Silin


113

Bir PostgreSQL 8.3.8 veritabanında, üzerinde anahtar / kısıtlama olmayan ve tamamen aynı değerlere sahip birden çok satıra sahip bir tablom var.

Tüm kopyaları kaldırmak ve her satırın yalnızca 1 kopyasını saklamak istiyorum.

Yinelenenleri tanımlamak için kullanılabilen özellikle bir sütun ("anahtar" olarak adlandırılır) vardır (yani, her farklı "anahtar" için yalnızca bir giriş olmalıdır).

Bunu nasıl yapabilirim? (ideal olarak tek bir SQL komutuyla) Bu durumda hız bir sorun değildir (yalnızca birkaç satır vardır).

Yanıtlar:


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

20
Kullanmayın, çok yavaş!
Paweł Malisak

5
Bu çözüm kesinlikle işe yararken, @rapimo'nun aşağıdaki çözümü çok daha hızlı çalışıyor. Bunun, diğer çözümde devam eden gruplandırma yerine, burada N kez (dupes tablosundaki tüm N satırlar için) çalıştırılan iç select deyimi ile ilgisi olduğuna inanıyorum.
David

Büyük tablolar (birkaç milyon kayıt) için bu, @ rapimo'nun çözümünün aksine aslında belleğe sığar. Yani bu durumlarda bu daha hızlı olanıdır (takas yok).
Giel

1
Açıklama ekleme: işe yarar çünkü ctid, satırın fiziksel konumunu gösteren özel bir postgres sütunu. Tablonuzun benzersiz bir kimliği olmasa bile bunu benzersiz bir kimlik olarak kullanabilirsiniz. postgresql.org/docs/8.2/ddl-system-columns.html
Eric Burel

194

Daha hızlı bir çözüm

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
Neden a_horse_with_no_name çözümünden daha hızlı?
Roberto

3
Bu daha hızlıdır çünkü bu yalnızca 2 sorgu çalıştırır. Birincisi tüm kopyaları seçmek, ardından tablodaki tüm öğeleri silmek için. @A_horse_with_no_name tarafından yapılan sorgu, tablodaki her bir öğe için başka herhangi bir öğe ile eşleşip eşleşmediğini görmek için bir sorgu yapar.
Aeolun

5
nedir ctid?
techkuz

6
docs: ctid. Satır sürümünün tablosu içindeki fiziksel konumu. Ctid, satır versiyonunu çok hızlı bir şekilde bulmak için kullanılabilse de, VACUUM FULL ile her güncellendiğinde veya taşındığında satırın ctid'ının değişeceğini unutmayın. Bu nedenle ctid, uzun vadeli bir satır tanımlayıcı olarak işe yaramaz.
Saim

1
Görünüşe göre bu, 2'den fazla yinelenen satır olduğunda işe yaramıyor, çünkü aynı anda yalnızca bir kopyayı siliyor.
Frankie Drake

74

Bu hızlı ve özlüdür:

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

Daha fazla bilgi içeren benzersiz tanımlayıcı olmadan yinelenen satırlar nasıl silinir ?


ct ne anlama geliyor? Miktar?
techkuz

4
@trthhrtz ctid, kaydın tablodaki fiziksel konumunu gösterir. Yorumda o sırada yazdığımın aksine, küçüktür operatörünü kullanmak, ct etrafına sarılabildiğinden ve daha düşük bir ctid değerine sahip bir değer aslında daha yeni olabileceğinden, eski sürüme işaret etmek zorunda değildir.
isapir

1
Bilginize, bu çözümü denedim ve 15 dakika bekledikten sonra iptal ettim. Rapimo'nun çözümünü denedi ve yaklaşık 10 saniyede tamamlandı (~ 700.000 satır silindi).
Patrick

@Patrick, db'nizin benzersiz bir tanımlayıcısına sahip olmadığını hayal edemez, çünkü bu durumda rapimo'nun cevabı işe yaramaz.
stucash

@isapir Sadece merak ediyorum, yukarıdaki cevaplar eski kayıtları seçtikleri gibi saklıyorlar min(ctid)mı? sizinki yenilerini tutarken? Teşekkürler!
stucash

17

Bunu denedim:

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 tarafından sağlanan:

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


@ Rapimo'nun yanıtı ve kabul edilen (@a_horse_with_no_name) ile karşılaştırıldığında performans hakkında bir fikriniz var mı?
tuxayo

3
Soru durumları gibi, tüm sütunlar aynı ise, bu sütun çalışmayacaktır id.
ibizaman

Bu sorgu hem orijinal kopyayı hem de kopyaları silecektir. soru, en az bir satırı korumakla ilgilidir.
pyBomb

@pyBomb yanlış, sütun1 id... 3'ün yinelendiği ilk yeri tutacak
Jeff

Postgresql 12 itibariyle, bu en hızlı çözümdür (300 milyon satıra karşı). Kabul edilen cevap da dahil olmak üzere bu soruda önerilen her şeyi test ettim ve bu "resmi" çözüm aslında en hızlı olanı ve OP'nin (ve benimki) tüm gereksinimleri karşılıyor
Jeff

7

Geçici bir tablo kullanırım:

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

Ardından, silmek tabve yeniden adlandırmak tab_tempiçine tab.


9
Bu yaklaşım tetikleyicileri, dizinleri ve istatistikleri hesaba katmaz. Kesinlikle onları ekleyebilirsin, ama çok daha fazla iş de ekler.
Ürdün

1
Herkesin buna ihtiyacı yok. Bu yaklaşım son derece hızlıdır ve dizin içermeyen 200.000 e-postada (varchar 250) çok daha iyi sonuç verir.
Sergey Telshevsky

1
Tam kod: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;
Eric Burel

7

Kendi versiyonumu yaratmalıydım. @A_horse_with_no_name tarafından yazılan sürüm, masamda çok yavaş (21M satır). Ve @rapimo, çiftleri silmez.

İşte PostgreSQL 9.5'te kullandığım şey

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

1

idTüm benzersiz kimlikleri sütunlara göre bulmak ve benzersiz listede olmayan diğer kimlikleri kaldırmak için başka bir yaklaşım (yalnızca tablonuzdaki gibi benzersiz bir alanınız varsa işe yarar )

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

Mesele şu ki, soruma göre tabloların benzersiz kimlikleri yoktu; "kopyalar", tüm sütunlarda tam olarak aynı değerlere sahip birden çok satırdı.
André Morujão

Doğru, bazı notlar ekledim
Zaytsev Dmitry

1

Peki ya:

İLE
  u AS (masanızdan DISTINCT * SEÇİN),
  x AS (tablonuzdan SİLİN)
INSERT your_table SELECT * FROM u;

Yürütme sırası konusunda endişeliydim, DELETE SELECT DISTINCT'den önce olur mu, ama benim için iyi çalışıyor. Ve masa yapısı hakkında herhangi bir bilgiye ihtiyaç duymama avantajına sahiptir.


Tek dezavantajı, eğer eşitliği desteklemeyen veri türünüz varsa (örneğin json) bunun işe yaramayacağıdır.
a_horse_with_no_name

0

Bu benim için iyi çalıştı. Yinelenen değerler içeren bir tablom, terimlerim vardı. Tüm yinelenen satırlarla geçici bir tablo doldurmak için bir sorgu çalıştırıldı. Sonra temp tablosundaki bu kimliklerle bir silme ifadesini çalıştırdım. değer, kopyaları içeren sütundur.

        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

İşte kullanan bir çözüm 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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.