Tìm các hàng có nhiều trường trùng lặp với Active Record, Rails & Postgres


103

Cách tốt nhất để tìm các bản ghi có giá trị trùng lặp trên nhiều cột bằng Postgres và Activerecord là gì?

Tôi tìm thấy giải pháp này ở đây :

User.find(:all, :group => [:first, :email], :having => "count(*) > 1" )

Nhưng nó dường như không hoạt động với postgres. Tôi gặp lỗi này:

PG :: GroupingError: ERROR: cột "Parts.id" phải xuất hiện trong mệnh đề GROUP BY hoặc được sử dụng trong một hàm tổng hợp


3
Trong SQL thông thường, tôi sẽ sử dụng một tự nối, giống như select a.id, b.id, name, email FROM user a INNER JOIN user b USING (name, email) WHERE a.id > b.id. Không có ý tưởng làm thế nào để diễn đạt điều đó trong ActiveRecord-speak.
Craig Ringer

Câu trả lời:


221

Phiên bản đã thử nghiệm & làm việc

User.select(:first,:email).group(:first,:email).having("count(*) > 1")

Ngoài ra, điều này là một chút không liên quan nhưng tiện dụng. Nếu bạn muốn xem thời gian từng kết hợp được tìm thấy, hãy đặt .size ở cuối:

User.select(:first,:email).group(:first,:email).having("count(*) > 1").size

và bạn sẽ nhận được một tập hợp kết quả giống như sau:

{[nil, nil]=>512,
 ["Joe", "test@test.com"]=>23,
 ["Jim", "email2@gmail.com"]=>36,
 ["John", "email3@gmail.com"]=>21}

Nghĩ rằng điều đó khá tuyệt và chưa từng thấy.

Tín dụng cho Taryn, đây chỉ là phiên bản chỉnh sửa của câu trả lời của cô ấy.


7
Tôi đã phải chuyển một mảng explict vào select()as: User.select([:first,:email]).group(:first,:email).having("count(*) > 1").countđể hoạt động.
Rafael Oliveira

4
thêm các .countCung cấpPG::UndefinedFunction: ERROR: function count
Magne

1
Bạn có thể thử User.select ([: first,: email]). Group (: first,: email) .having ("count (*)> 1"). Map.count
Serhii Nadolynskyi

3
Tôi đang thử phương pháp tương tự nhưng cũng đang cố lấy User.id, thêm nó vào vùng chọn và nhóm trả về một mảng trống. Làm cách nào để trả về toàn bộ mô hình Người dùng, hoặc ít nhất là bao gồm: id?
Ashbury

5
sử dụng .sizethay vì.count
Charles Hamel

32

Lỗi đó xảy ra vì POSTGRES yêu cầu bạn đặt các cột nhóm trong mệnh đề SELECT.

thử:

User.select(:first,:email).group(:first,:email).having("count(*) > 1").all

(lưu ý: không được kiểm tra, bạn có thể cần phải tinh chỉnh nó)

ĐÃ CHỈNH SỬA để xóa cột id


7
Điều đó sẽ không hiệu quả; các idcột không phải là một phần của nhóm, vì vậy bạn không thể tham khảo nó trừ khi bạn tổng hợp nó (ví dụ array_agg(id)hay json_agg(id))
Craig Ringer

9

Nếu bạn cần các mô hình đầy đủ, hãy thử cách sau (dựa trên câu trả lời của @ newUserNameHere).

User.where(email: User.select(:email).group(:email).having("count(*) > 1").select(:email))

Thao tác này sẽ trả về các hàng mà địa chỉ email của hàng đó không phải là duy nhất.

Tôi không biết cách thực hiện điều này trên nhiều thuộc tính.


`` `User.where (email: User.select (: Địa chỉ email) .group (: Địa chỉ email) .having ( "count (*)> 1"))` ``
chet Corey

Cảm ơn bạn rằng công việc tuyệt vời :) Cũng có vẻ như nó cuối cùng .select(:email)là thừa. Tôi nghĩ rằng điều này là sạch hơn một chút, nhưng tôi có thể sai. User.where(email: User.select(:email).group(:email).having("count(*) > 1"))
chet corey

2

Nhận tất cả các bản sao với một truy vấn duy nhất nếu bạn sử dụng PostgreSQL :

def duplicated_users
  duplicated_ids = User
    .group(:first, :email)
    .having("COUNT(*) > 1")
    .select('unnest((array_agg("id"))[2:])')

  User.where(id: duplicated_ids)
end

irb> duplicated_users

-1

Dựa trên câu trả lời ở trên của @newUserName. Ở đây tôi tin rằng cách phù hợp để hiển thị số lượng cho mỗi là

res = User.select('first, email, count(1)').group(:first,:email).having('count(1) > 1')

res.each {|r| puts r.attributes } ; nil
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.