Active Record、Rails、Postgresで複数の重複フィールドを持つ行を検索する


103

PostgresとActiverecordを使用して複数の列にわたって重複する値を持つレコードを見つけるための最良の方法は何ですか?

私はこの解決策をここで見つけまし

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

しかし、それはpostgresでは動作しないようです。私はこのエラーを受け取ります:

PG :: GroupingError:エラー:列 "parts.id"はGROUP BY句に出現するか、集計関数で使用する必要があります


3
通常のSQLでは、のような自己結合を使用しますselect a.id, b.id, name, email FROM user a INNER JOIN user b USING (name, email) WHERE a.id > b.id。ActiveRecord-speakでそれを表現する方法はわかりません。
クレイグリンガー、

回答:


221

テスト済みおよび動作中のバージョン

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

また、これは少し無関係ですが便利です。各組み合わせが見つかった回数を確認する場合は、最後に.sizeを付けます。

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

次のような結果セットが返されます。

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

それはかなりクールで、以前にそれを見たことがなかったと思いました。

Taryn氏の功績です。これは彼女の回答の微調整されたバージョンです。


7
機能するためには、次のselect()ように明示的な配列を渡すUser.select([:first,:email]).group(:first,:email).having("count(*) > 1").count必要がありました。
Rafael Oliveira

4
追加.countできますPG::UndefinedFunction: ERROR: function count
マグネ

1
User.select([:first、:email])。group(:first、:email).having( "count(*)> 1")。map.countを試すことができます
Serhii Nadolynskyi

3
私は同じメソッドを試していますが、User.idも取得しようとしています。それをselectおよびgroupに追加すると、空の配列が返されます。Userモデル全体を返す方法、または少なくとも:idを含める方法はありますか?
アッシュベリー、2015年

5
.size代わりに使用してください.count
Charles Hamel

32

そのエラーは、POSTGRESがグループ化列をSELECT句に配置する必要があるために発生します。

試してください:

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

(注:テストされていません。調整する必要がある場合があります)

ID列を削除するように編集されました


7
それはうまくいきません。id列には、あなたがそれを集約しない限り、あなたはそれを参照することはできませんので(例えば、グループの一部ではないarray_agg(id)json_agg(id)
クレイグリンガー

9

完全なモデルが必要な場合は、(@ newUserNameHereの回答に基づいて)以下を試してください。

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

これにより、行の電子メールアドレスが一意でない行が返されます。

複数の属性に対してこれを行う方法を知りません。


`` `User.where(email:User.select(:email).group(:email).having(" count(*)> 1 "))` ``
chet corey 2018

素晴らしい作品をありがとうございます:)また、最後.select(:email)は冗長です。これはもう少しきれいだと思いますが、私は間違っている可能性があります。 User.where(email: User.select(:email).group(:email).having("count(*) > 1"))
chet corey

2

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

上記の @newUserNameHere の回答に基づいて、私はそれぞれの数を表示する正しい方法は

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

res.each {|r| puts r.attributes } ; nil
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.