Cách tìm bản ghi trùng lặp trong PostgreSQL


189

Tôi có một bảng cơ sở dữ liệu PostgreSQL có tên là "user_links" hiện đang cho phép các trường trùng lặp sau:

year, user_id, sid, cid

Các hạn chế duy nhất hiện nay là trường đầu tiên được gọi là "id", tuy nhiên bây giờ tôi đang tìm thêm một hạn chế để đảm bảo rằng year, user_id, sidcidtất cả đều độc đáo nhưng tôi không thể áp dụng các hạn chế vì giá trị nhân bản đã tồn tại vi phạm ràng buộc này.

Có cách nào để tìm tất cả các bản sao?


2

Câu trả lời:


333

Ý tưởng cơ bản sẽ sử dụng truy vấn lồng nhau với tổng hợp đếm:

select * from yourTable ou
where (select count(*) from yourTable inr
where inr.sid = ou.sid) > 1

Bạn có thể điều chỉnh mệnh đề where trong truy vấn bên trong để thu hẹp tìm kiếm.


Có một giải pháp tốt khác cho điều đó được đề cập trong các bình luận, (nhưng không phải ai cũng đọc chúng):

select Column1, Column2, count(*)
from yourTable
group by Column1, Column2
HAVING count(*) > 1

Hoặc ngắn hơn:

SELECT (yourTable.*)::text, count(*)
FROM yourTable
GROUP BY yourTable.*
HAVING count(*) > 1

65
Bạn cũng có thể sử dụng HAVING:select co1, col2, count(*) from tbl group by col1, col2 HAVING count(*)>1
alexkovelsky

1
Cảm ơn @alexkovelsky, câu lệnh có dễ sửa đổi hơn đối với tôi và chạy nhanh hơn. Tôi sẽ đề nghị một câu trả lời với nó cho tầm nhìn cao hơn.
Vesanto

các tùy chọn này đã làm việc cho tôi, các nhóm khác kết quả và các tùy chọn này đã cho tôi tất cả các bản ghi trùng lặp thay vì chỉ bản ghi trùng lặp, cảm ơn!
rome3ro

1
Tôi có câu trả lời của bạn là một chút chậm. Trên bảng 10k hàng * 18 cột, truy vấn mất 8 giây
aydow

1
đó là mứt ngay đó bro. chết tiệt cảm ơn. 💯
DPS

90

Từ " Tìm hàng trùng lặp với PostgreSQL " đây là giải pháp thông minh:

select * from (
  SELECT id,
  ROW_NUMBER() OVER(PARTITION BY column1, column2 ORDER BY id asc) AS Row
  FROM tbl
) dups
where 
dups.Row > 1

11
Cái này nhanh thật! Làm việc trên hàng triệu hàng trong một phần của giây. Những câu trả lời khác chỉ được treo ở đó ...
dmvianna

5
Như tôi thấy, truy vấn này không xem xét tất cả các hàng trong một nhóm. Nó chỉ hiển thị các bản sao với một cái gì đó, một phần của các bản sao sẽ có với rownum = 1. Sửa lỗi cho tôi nếu tôi sai
Vladimir Filipchenko

9
@vladimir Filipchenko Để có nó với tất cả các dòng, hãy thêm một cấp độ cho giải pháp Alexkovelsky:SELECT * FROM ( SELECT *, LEAD(row,1) OVER () AS nextrow FROM ( SELECT *, ROW_NUMBER() OVER(w) AS row FROM tbl WINDOW w AS (PARTITION BY col1, col2 ORDER BY col3) ) x ) y WHERE row > 1 OR nextrow > 1;
Le Droid

3
@VladimirFilipchenko Chỉ cần thay thế ROW_NUMBER()bằng COUNT(*)và thêm rows between unbounded preceding and unbounded followingsauORDER BY id asc
alexkovelsky

2
tốt hơn nhiều so với các giải pháp khác mà tôi đã tìm thấy. cũng hoạt động tốt như nhau để xóa các bản sao với DELETE ...USINGvà một số điều chỉnh nhỏ
Brandon

6

Bạn có thể tham gia vào cùng một bảng trên các trường sẽ được sao chép và sau đó chống tham gia trên trường id. Chọn trường id từ bí danh bảng đầu tiên (tn1) và sau đó sử dụng hàm Array_agg trên trường id của bí danh bảng thứ hai. Cuối cùng, để hàm Array_agg hoạt động chính xác, bạn sẽ nhóm các kết quả theo trường tn1.id. Điều này sẽ tạo ra một tập kết quả chứa id của bản ghi và một mảng của tất cả các id phù hợp với điều kiện nối.

select tn1.id,
       array_agg(tn2.id) as duplicate_entries, 
from table_name tn1 join table_name tn2 on 
    tn1.year = tn2.year 
    and tn1.sid = tn2.sid 
    and tn1.user_id = tn2.user_id 
    and tn1.cid = tn2.cid
    and tn1.id <> tn2.id
group by tn1.id;

Rõ ràng, các id sẽ nằm trong mảng trùng lặp cho một id, cũng sẽ có các mục riêng trong tập kết quả. Bạn sẽ phải sử dụng tập kết quả này để quyết định id nào bạn muốn trở thành nguồn gốc của 'sự thật'. Một bản ghi không nên bị xóa. Có lẽ bạn có thể làm một cái gì đó như thế này:

with dupe_set as (
select tn1.id,
       array_agg(tn2.id) as duplicate_entries, 
from table_name tn1 join table_name tn2 on 
    tn1.year = tn2.year 
    and tn1.sid = tn2.sid 
    and tn1.user_id = tn2.user_id 
    and tn1.cid = tn2.cid
    and tn1.id <> tn2.id
group by tn1.id
order by tn1.id asc)
select ds.id from dupe_set ds where not exists 
 (select de from unnest(ds.duplicate_entries) as de where de < ds.id)

Chọn số ID thấp nhất có trùng lặp (giả sử ID đang tăng int PK). Đây sẽ là những ID mà bạn sẽ giữ xung quanh.


3

Để dễ dàng hơn, tôi giả sử rằng bạn muốn áp dụng một ràng buộc duy nhất chỉ cho năm cột và khóa chính là một cột có tên id.

Để tìm các giá trị trùng lặp bạn nên chạy,

SELECT year, COUNT(id)
FROM YOUR_TABLE
GROUP BY year
HAVING COUNT(id) > 1
ORDER BY COUNT(id);

Sử dụng câu lệnh sql ở trên, bạn nhận được một bảng chứa tất cả các năm trùng lặp trong bảng của bạn. Để xóa tất cả các mục trùng lặp ngoại trừ mục trùng lặp mới nhất, bạn nên sử dụng câu lệnh sql ở trên.

DELETE
FROM YOUR_TABLE A USING YOUR_TABLE_AGAIN B
WHERE A.year=B.year AND A.id<B.id;
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.