Xóa các bản ghi trùng lặp trong PostgreSQL


113

Tôi có một bảng trong cơ sở dữ liệu PostgreSQL 8.3.8, không có khóa / ràng buộc trên đó và có nhiều hàng với các giá trị giống hệt nhau.

Tôi muốn xóa tất cả các bản sao và chỉ giữ lại 1 bản sao của mỗi hàng.

Có một cột cụ thể (được đặt tên là "khóa") có thể được sử dụng để xác định các bản sao (tức là chỉ nên tồn tại một mục nhập cho mỗi "khóa" riêng biệt).

Tôi có thể làm cái này như thế nào? (lý tưởng nhất là với một lệnh SQL duy nhất) Tốc độ không phải là vấn đề trong trường hợp này (chỉ có một vài hàng).

Câu trả lời:


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

20
Đừng sử dụng nó, nó là quá chậm!
Paweł Malisak

5
Mặc dù giải pháp này chắc chắn hoạt động, nhưng giải pháp của @rapimo bên dưới thực thi nhanh hơn nhiều. Tôi tin rằng điều này liên quan đến câu lệnh select bên trong ở đây được thực thi N lần (cho tất cả N hàng trong bảng dupes) hơn là việc nhóm đang diễn ra trong giải pháp khác.
David

Đối với các bảng khổng lồ (vài triệu bản ghi), bảng này thực sự phù hợp với bộ nhớ, không giống như giải pháp của @ rapimo. Vì vậy, trong những trường hợp này, đây là một trong những nhanh hơn (không hoán đổi).
Giel

1
Thêm giải thích: nó hoạt động vì ctid là một cột postgres đặc biệt chỉ ra vị trí thực của hàng. Bạn có thể sử dụng nó làm id duy nhất ngay cả khi bảng của bạn không có id duy nhất. postgresql.org/docs/8.2/ddl-system-columns.html
Eric Burel

194

Một giải pháp nhanh hơn là

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
Tại sao nó nhanh hơn giải pháp của a_horse_with_no_name?
Roberto

3
Điều này nhanh hơn vì điều này chỉ chạy 2 truy vấn. Đầu tiên, một để chọn tất cả các mục trùng lặp, sau đó một để xóa tất cả các mục khỏi bảng. Truy vấn của @a_horse_with_no_name thực hiện một truy vấn để xem liệu nó có khớp với bất kỳ mục nào khác cho mỗi mục trong bảng hay không.
Aeolun

5
ctidgì?
techkuz

6
từ tài liệu: ctid. Vị trí thực của phiên bản hàng trong bảng của nó. Lưu ý rằng mặc dù ctid có thể được sử dụng để định vị phiên bản hàng rất nhanh chóng, nhưng ctid của hàng sẽ thay đổi mỗi khi nó được cập nhật hoặc di chuyển bởi VACUUM FULL. Do đó ctid không có tác dụng như một định danh hàng dài hạn.
Saim

1
Có vẻ như điều này không hoạt động khi có nhiều hơn 2 hàng trùng lặp, vì nó chỉ xóa một hàng trùng lặp tại một thời điểm.
Frankie Drake

73

Điều này nhanh chóng và ngắn gọn:

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

Xem thêm câu trả lời của tôi tại Cách xóa các hàng trùng lặp không có số nhận dạng duy nhất bao gồm nhiều thông tin hơn.


ct viết tắt của gì? đếm?
techkuz

4
@trthhrtz ctidtrỏ đến vị trí thực của bản ghi trong bảng. Trái ngược với những gì tôi đã viết vào thời điểm đó trong nhận xét, việc sử dụng toán tử less than không nhất thiết phải trỏ đến phiên bản cũ hơn vì ct có thể quấn quanh và một giá trị có ctid thấp hơn thực sự có thể mới hơn.
isapir

1
Chỉ là FYI, tôi đã thử giải pháp này và hủy bỏ nó sau 15 phút chờ đợi. Đã thử giải pháp của rapimo và nó hoàn thành trong khoảng 10 giây (đã xóa ~ 700.000 hàng).
Patrick

@Patrick không thể tưởng tượng được nếu db của bạn không có số nhận dạng duy nhất vì câu trả lời của rapimo không hoạt động trong trường hợp đó.
stucash

@isapir Tôi chỉ tò mò, các câu trả lời ở trên, họ đang giữ các bản ghi cũ hơn đúng như họ đã chọn min(ctid)? trong khi của bạn đang giữ những cái mới hơn? cảm ơn!
stucash

17

Tôi đã thử điều này:

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

được cung cấp bởi Postgres wiki:

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


Có ý kiến ​​gì về màn trình diễn so với câu trả lời của @ rapimo và câu trả lời được chấp nhận (@a_horse_with_no_name) không?
tuxayo

3
Cái này sẽ không hoạt động nếu, giống như các câu hỏi đã nêu, tất cả các cột đều giống hệt nhau, idbao gồm.
ibizaman

Truy vấn này sẽ xóa cả bản sao gốc và các bản sao. câu hỏi là về việc giữ lại ít nhất một hàng.
pyBomb

@pyBomb sai, nó sẽ giữ vị trí đầu tiên idtrong đó cột1 ... 3 trùng lặp
Jeff

Kể từ postgresql 12, đây là BY FAR là giải pháp nhanh nhất (so với 300 triệu hàng). Tôi vừa thử nghiệm mọi thứ được đề xuất trong câu hỏi này, bao gồm cả câu trả lời được chấp nhận và giải pháp "chính thức" này thực sự là nhanh nhất và đáp ứng mọi yêu cầu từ OP (và của tôi)
Jeff

7

Tôi đã phải tạo ra phiên bản của riêng mình. Phiên bản do @a_horse_with_no_name viết trên bảng của tôi quá chậm (21 triệu hàng). Và @rapimo chỉ đơn giản là không xóa dups.

Đây là những gì tôi sử dụng trên 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

Tôi sẽ sử dụng một bảng tạm thời:

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

Sau đó, xóa tabvà đổi tên tab_tempthành tab.


8
Phương pháp này không tính đến trình kích hoạt, chỉ mục và thống kê. Chắc chắn bạn có thể thêm chúng, nhưng nó cũng làm thêm rất nhiều công việc.
Jordan

Không phải ai cũng cần điều đó. Cách tiếp cận này cực kỳ nhanh và hoạt động tốt hơn nhiều so với cách còn lại trên 200 nghìn email (varchar 250) không có chỉ mục.
Sergey Telshevsky

Mã đầy đủ: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

1

Một cách tiếp cận khác (chỉ hoạt động nếu bạn có bất kỳ trường duy nhất nào giống như idtrong bảng của mình) để tìm tất cả các id duy nhất theo cột và xóa các id khác không có trong danh sách duy nhất

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

Vấn đề là, trong câu hỏi của tôi, các bảng không có id duy nhất; "bản sao" là nhiều hàng với các giá trị giống hệt nhau trên tất cả các cột.
André Morujão

Phải, tôi đã thêm một số ghi chú
Grigoryevich Zaytsev Dmitry

1

Làm thế nào về:

VỚI
  u AS (CHỌN DISTINCT * FROM your_table),
  x AS (XÓA khỏi bảng_bạn)
CHÈN VÀO your_table SELECT * FROM u;

Tôi đã lo lắng về thứ tự thực hiện, liệu DELETE có xảy ra trước CHỌN DISTINCT không, nhưng nó hoạt động tốt đối với tôi. Và có thêm phần thưởng là không cần bất kỳ kiến ​​thức nào về cấu trúc bảng.


Hạn chế duy nhất là, nếu bạn có kiểu dữ liệu không hỗ trợ bình đẳng (ví dụ json), điều này sẽ không hoạt động.
a_horse_with_no_name

0

Điều này làm việc tốt cho tôi. Tôi có một bảng, các điều khoản, chứa các giá trị trùng lặp. Chạy một truy vấn để điền vào bảng tạm thời với tất cả các hàng trùng lặp. Sau đó, tôi chạy câu lệnh xóa với các id đó trong bảng tạm thời. giá trị là cột chứa các bản sao.

        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

Đây là một giải pháp sử dụng 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.