Temukan baris dengan beberapa bidang duplikat dengan Rekaman Aktif, Rel & Postgres

103

Apa cara terbaik untuk menemukan record dengan nilai duplikat di beberapa kolom menggunakan Postgres, dan Activerecord?

Saya menemukan solusi ini di sini :

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

Tetapi tampaknya tidak berfungsi dengan postgres. Saya mendapatkan kesalahan ini:

PG :: GroupingError: ERROR: kolom "parts.id" harus muncul di klausa GROUP BY atau digunakan dalam fungsi agregat

newUserNameHere
sumber
3
Dalam SQL biasa, saya akan menggunakan self-join, seperti select a.id, b.id, name, email FROM user a INNER JOIN user b USING (name, email) WHERE a.id > b.id. Tidak tahu bagaimana mengungkapkannya di ActiveRecord-speak.
Craig Ringer

Jawaban:

224

Versi Teruji & Bekerja

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

Juga, ini sedikit tidak berhubungan tetapi berguna. Jika Anda ingin melihat berapa kali setiap kombinasi ditemukan, letakkan .size di akhir:

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

dan Anda akan mendapatkan hasil yang terlihat seperti ini:

{[nil, nil]=>512,
 ["Joe", "[email protected]"]=>23,
 ["Jim", "[email protected]"]=>36,
 ["John", "[email protected]"]=>21}

Saya pikir itu cukup keren dan belum pernah melihatnya sebelumnya.

Penghargaan untuk Taryn, ini hanyalah versi tweak dari jawabannya.

newUserNameHere
sumber
7
Saya harus melewati array explict ke select()seperti: User.select([:first,:email]).group(:first,:email).having("count(*) > 1").countuntuk bekerja.
Rafael Oliveira
4
menambahkan .countmemberiPG::UndefinedFunction: ERROR: function count
Magne
1
Anda dapat mencoba User.select ([: first,: email]). Group (: first,: email) .having ("count (*)> 1"). Map.count
Serhii Nadolynskyi
3
Saya mencoba metode yang sama tetapi mencoba untuk mendapatkan User.id juga, menambahkannya ke seleksi dan grup mengembalikan array kosong. Bagaimana saya dapat mengembalikan seluruh model User, atau setidaknya menyertakan: id?
Ashbury
5
gunakan .sizesebagai pengganti.count
Charles Hamel
32

Kesalahan itu terjadi karena POSTGRES mengharuskan Anda untuk meletakkan kolom pengelompokan di klausa SELECT.

mencoba:

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

(catatan: tidak diuji, Anda mungkin perlu menyesuaikannya)

DIEDIT untuk menghapus kolom id

Taryn East
sumber
7
Itu tidak akan berhasil; yang idkolom bukan bagian dari kelompok, sehingga Anda tidak dapat merujuk kecuali Anda agregat (misalnya array_agg(id)atau json_agg(id))
Craig Ringer
9

Jika Anda membutuhkan model lengkap, coba yang berikut ini (berdasarkan jawaban @ newUserNameHere's).

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

Ini akan mengembalikan baris di mana alamat email baris tersebut tidak unik.

Saya tidak mengetahui cara untuk melakukan ini pada beberapa atribut.

Ben Aubin
sumber
`` `` User.where (email: User.select (: email) .group (: email) .having ("count (*)> 1")) ``
chet corey
Terima kasih yang berfungsi dengan baik :) Juga sepertinya yang terakhir .select(:email)ini berlebihan. Saya pikir ini sedikit lebih bersih, tetapi saya bisa saja salah. User.where(email: User.select(:email).group(:email).having("count(*) > 1"))
chet corey
2

Dapatkan semua duplikat dengan satu kueri jika Anda menggunakan 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
itsnikolay
sumber
-1

Berdasarkan jawaban di atas oleh @newUserNameDi sini saya yakin cara yang tepat untuk menunjukkan hitungan masing-masing adalah

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

res.each {|r| puts r.attributes } ; nil
Nuno Costa
sumber