ลบระเบียนที่ซ้ำกันใน PostgreSQL


113

ฉันมีตารางในฐานข้อมูล PostgreSQL 8.3.8 ซึ่งไม่มีคีย์ / ข้อ จำกัด และมีหลายแถวที่มีค่าเหมือนกันทุกประการ

ฉันต้องการลบรายการที่ซ้ำกันทั้งหมดและเก็บไว้เพียง 1 สำเนาของแต่ละแถว

มีคอลัมน์หนึ่งคอลัมน์โดยเฉพาะ (ชื่อ "คีย์") ซึ่งอาจใช้เพื่อระบุรายการที่ซ้ำกัน (กล่าวคือควรมีเพียงรายการเดียวสำหรับ "คีย์" ที่แตกต่างกันแต่ละรายการ)

ฉันจะทำเช่นนี้ได้อย่างไร? (ตามหลักแล้วคำสั่ง SQL คำสั่งเดียว) ความเร็วไม่ใช่ปัญหาในกรณีนี้ (มีเพียงไม่กี่แถว)

คำตอบ:


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

20
ไม่ใช้มันช้าเกินไป!
Paweł Malisak

5
แม้ว่าโซลูชันนี้จะใช้งานได้จริง แต่โซลูชันของ @rapimo ด้านล่างจะทำงานได้เร็วขึ้นมาก ฉันเชื่อว่าสิ่งนี้เกี่ยวข้องกับคำสั่งการเลือกภายในที่นี่การดำเนินการ N ครั้ง (สำหรับ N แถวทั้งหมดในตาราง dupes) แทนที่จะเป็นการจัดกลุ่มที่เกิดขึ้นในโซลูชันอื่น
เดวิด

สำหรับตารางขนาดใหญ่ (หลายล้านระเบียน) อันนี้เหมาะกับหน่วยความจำจริง ๆ ซึ่งแตกต่างจากโซลูชันของ @ rapimo ดังนั้นในกรณีเหล่านี้จะเร็วกว่า (ไม่มีการแลกเปลี่ยน)
Giel

1
การเพิ่มคำอธิบาย: ใช้งานได้เนื่องจาก ctid เป็นคอลัมน์ postgres พิเศษที่ระบุตำแหน่งทางกายภาพของแถว คุณสามารถใช้สิ่งนี้เป็นรหัสเฉพาะแม้ว่าตารางของคุณจะไม่มีรหัสเฉพาะก็ตาม postgresql.org/docs/8.2/ddl-system-columns.html
Eric Burel

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
Roberto

3
เร็วกว่าเพราะรันเพียง 2 แบบสอบถาม ขั้นแรกให้เลือกรายการที่ซ้ำกันทั้งหมดจากนั้นเลือกหนึ่งรายการเพื่อลบรายการทั้งหมดออกจากตาราง คำค้นหาโดย @a_horse_with_no_name ทำการสืบค้นเพื่อดูว่าตรงกับคำค้นหาอื่น ๆ สำหรับทุกรายการในตารางหรือไม่
Aeolun

5
คือctidอะไร?
techkuz

6
จาก docs: ctid ตำแหน่งทางกายภาพของเวอร์ชันแถวภายในตาราง โปรดทราบว่าแม้ว่าจะสามารถใช้ ctid เพื่อค้นหาเวอร์ชันของแถวได้อย่างรวดเร็ว แต่ ctid ของแถวจะเปลี่ยนทุกครั้งที่มีการอัปเดตหรือย้ายโดย VACUUM FULL ดังนั้น ctid จึงไม่มีประโยชน์เป็นตัวระบุแถวระยะยาว
Saim

1
ดูเหมือนว่าจะใช้ไม่ได้เมื่อมีแถวที่ซ้ำกันมากกว่า 2 แถวเนื่องจากจะลบรายการที่ซ้ำกันเพียงรายการเดียวในแต่ละครั้ง
Frankie Drake

74

รวดเร็วและรัดกุม:

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 ย่อมาจากอะไร? นับ?
techkuz

4
@trthhrtz ctidชี้ไปที่ตำแหน่งทางกายภาพของระเบียนในตาราง ตรงกันข้ามกับสิ่งที่ฉันเขียนในตอนนั้นในความคิดเห็นการใช้ตัวดำเนินการน้อยกว่าไม่จำเป็นต้องชี้ไปที่เวอร์ชันที่เก่ากว่าเนื่องจาก ct สามารถล้อมรอบได้และค่าที่มี ctid ต่ำกว่าอาจจะใหม่กว่าก็ได้
isapir

1
เพียงแค่ FYI ฉันลองวิธีแก้ปัญหานี้และยกเลิกหลังจากรอ 15 นาที ลองใช้วิธีแก้ปัญหาของ Rapimo แล้วเสร็จในเวลาประมาณ 10 วินาที (ลบ ~ 700,000 แถว)
Patrick

@Patrick ไม่สามารถจินตนาการได้ว่าฐานข้อมูลของคุณไม่มีตัวระบุเฉพาะหรือไม่เนื่องจากคำตอบของ Rapimo ไม่ได้ผลในกรณีนั้น
stucash

@isapir ฉันแค่อยากรู้คำตอบข้างต้นพวกเขากำลังเก็บบันทึกที่เก่ากว่าตามที่พวกเขาเลือกmin(ctid)? ในขณะที่คุณเก็บของที่ใหม่กว่าไว้? ขอบคุณ!
stucash

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รวมอยู่ด้วย
ibizaman

คำค้นหานี้จะลบทั้งสำเนาต้นฉบับและรายการที่ซ้ำกัน คำถามเกี่ยวกับการรักษาอย่างน้อยหนึ่งแถว
pyBomb

@pyBomb ผิดมันจะทำให้idคอลัมน์แรก1 ... 3 ซ้ำกัน
เจฟฟ์

ตั้งแต่ postgresql 12 นี่คือ BY FAR เป็นโซลูชันที่เร็วที่สุด (เทียบกับ 300 ล้านแถว) ฉันเพิ่งทดสอบทุกอย่างที่เสนอในคำถามนี้รวมถึงคำตอบที่ยอมรับและโซลูชัน "อย่างเป็นทางการ" นี้เร็วที่สุดและตรงตามข้อกำหนดทั้งหมดจาก OP (และของฉัน)
Jeff

7

ฉันจะใช้ตารางชั่วคราว:

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

จากนั้นลบtabและเปลี่ยนชื่อลงไปtab_temptab


9
แนวทางนี้ไม่ได้อธิบายถึงทริกเกอร์ดัชนีและสถิติ แน่นอนว่าคุณสามารถเพิ่มได้ แต่ก็เพิ่มงานได้มากเช่นกัน
จอร์แดน

1
ทุกคนไม่ต้องการสิ่งนั้น วิธีนี้เร็วมากและทำงานได้ดีกว่าอีเมล 200k ที่เหลือ (varchar 250) ที่ไม่มีดัชนี
Sergey Telshevsky

1
รหัสเต็ม: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

ฉันต้องสร้างเวอร์ชันของฉันเอง เวอร์ชันที่เขียนโดย @a_horse_with_no_name ช้าเกินไปบนตารางของฉัน (แถว 21M) และ @rapimo ก็ไม่ได้ลบ dups

นี่คือสิ่งที่ฉันใช้กับ 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);

1

อีกวิธีหนึ่ง (ใช้ได้เฉพาะในกรณีที่คุณมีเขตข้อมูลที่ไม่ซ้ำกันเช่นidในตารางของคุณ) เพื่อค้นหารหัสที่ไม่ซ้ำกันทั้งหมดตามคอลัมน์และลบรหัสอื่น ๆ ที่ไม่ได้อยู่ในรายการที่ไม่ซ้ำกัน

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

ในคำถามของฉันตารางไม่มีรหัสเฉพาะ "รายการที่ซ้ำกัน" คือหลายแถวที่มีค่าเหมือนกันทุกประการในทุกคอลัมน์
AndréMorujão

ใช่ฉันได้เพิ่มบันทึกบางส่วน
Zaytsev Dmitry

1

เกี่ยวกับ:

ด้วย
  คุณเป็น (เลือก DISTINCT * จาก your_table)
  x AS (ลบจาก your_table)
INSERT INTO your_table SELECT * FROM u;

ฉันกังวลเกี่ยวกับคำสั่งการดำเนินการ DELETE จะเกิดขึ้นก่อนที่จะเลือก DISTINCT แต่มันก็ใช้ได้ดีสำหรับฉัน และมีโบนัสเพิ่มเติมโดยไม่จำเป็นต้องมีความรู้เกี่ยวกับโครงสร้างตาราง


ข้อเสียเปรียบเพียงประการเดียวคือหากคุณมีประเภทข้อมูลที่ไม่รองรับความเท่าเทียมกัน (เช่น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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.