単純な関連付けを検討してください...
class Person
has_many :friends
end
class Friend
belongs_to :person
end
ARelやmeta_whereに友達がいないすべての人を取得する最もクリーンな方法は何ですか?
そして、has_many:throughバージョンについてはどうですか
class Person
has_many :contacts
has_many :friends, :through => :contacts, :uniq => true
end
class Friend
has_many :contacts
has_many :people, :through => :contacts, :uniq => true
end
class Contact
belongs_to :friend
belongs_to :person
end
私は実際にcounter_cacheを使いたくありません-そして私が読んだものからはhas_manyで動作しません:through
すべてのperson.friendsレコードをプルしてRubyでループしたくありません-meta_search gemで使用できるクエリ/スコープが必要です
クエリのパフォーマンスコストを気にしません
そして、実際のSQLから離れるほど良い...
これはまだSQLにかなり近いですが、最初のケースでは友人のいない全員を取得する必要があります。
Person.where('id NOT IN (SELECT DISTINCT(person_id) FROM friends)')
より良い:
Person.includes(:friends).where( :friends => { :person_id => nil } )
Hmtの場合、基本的に同じことです。友達のいない人にも連絡先がないという事実に依存します。
Person.includes(:contacts).where( :contacts => { :person_id => nil } )
コメントにhas_one
に関する質問があるので、更新してください。ここでのコツは、includes()
はアソシエーションの名前を期待しますが、where
はテーブルの名前を期待するということです。 has_one
の場合、関連は通常単数形で表現されるため、変更されますが、where()
部分はそのままです。したがって、Person
のみhas_one :contact
の場合、ステートメントは次のようになります。
Person.includes(:contact).where( :contacts => { :person_id => nil } )
誰かが逆の、人のいない友人について尋ねました。以下にコメントしたように、これは実際に、最後のフィールド(上記::person_id
)が実際に返すモデルに関連する必要はなく、結合のフィールドでなければならないことを実感させました。テーブル。それらはすべてnil
になるので、どれでもかまいません。これにより、上記の簡単なソリューションが得られます。
Person.includes(:contacts).where( :contacts => { :id => nil } )
そして、これを切り替えて、人のいない友達を返すことがさらに簡単になり、前のクラスのみを変更します:
Friend.includes(:contacts).where( :contacts => { :id => nil } )
優れたRails 5ソリューション(@Ansonに以下の回答に+1を与えてください)のおかげで、left_outer_joins
を使用して関連付けのロードを回避できます。
Person.left_outer_joins(:contacts).where( contacts: { id: nil } )
人々がそれを見つけることができるように私はそれをここに含めましたが、彼はこのための+1に値します。素晴らしい追加!
友達がいない人
Person.includes(:friends).where("friends.person_id IS NULL")
または、少なくとも1人の友人がいる
Person.includes(:friends).where("friends.person_id IS NOT NULL")
Friend
にスコープを設定して、Arelでこれを行うことができます
class Friend
belongs_to :person
scope :to_somebody, ->{ where arel_table[:person_id].not_eq(nil) }
scope :to_nobody, ->{ where arel_table[:person_id].eq(nil) }
end
そして、少なくとも1人の友人がいる人:
Person.includes(:friends).merge(Friend.to_somebody)
友だち:
Person.includes(:friends).merge(Friend.to_nobody)
DmarkowとUnixmonkeyの両方の答えから、必要なものが得られます-ありがとうございます!
私は実際のアプリで両方を試し、それらのタイミングを取得しました-2つのスコープは次のとおりです:
class Person
has_many :contacts
has_many :friends, :through => :contacts, :uniq => true
scope :without_friends_v1, -> { where("(select count(*) from contacts where person_id=people.id) = 0") }
scope :without_friends_v2, -> { where("id NOT IN (SELECT DISTINCT(person_id) FROM contacts)") }
end
これを実際のアプリで実行しました-700個の 'Person'レコードを持つ小さなテーブル-平均5回の実行
Unixmonkeyのアプローチ(:without_friends_v1
)813ms /クエリ
dmarkowのアプローチ(:without_friends_v2
)891ms /クエリ(〜10%遅い)
しかし、その後、私はDISTINCT()...
への呼び出しを必要としないことに気付きました。NOPerson
のないContacts
レコードを探しているので、NOT IN
連絡先のリストperson_ids
。だから私はこのスコープを試しました:
scope :without_friends_v3, -> { where("id NOT IN (SELECT person_id FROM contacts)") }
同じ結果が得られますが、平均425ミリ秒/コール-ほぼ半分の時間で...
今、あなたは他の同様のクエリでDISTINCT
を必要とするかもしれません-しかし、私の場合、これはうまくいくようです。
ご協力いただきありがとうございます
残念ながら、SQLを含むソリューションを検討している可能性がありますが、スコープに設定して、そのスコープを使用することはできます。
class Person
has_many :contacts
has_many :friends, :through => :contacts, :uniq => true
scope :without_friends, where("(select count(*) from contacts where person_id=people.id) = 0")
end
次に、それらを取得するには、Person.without_friends
を実行します。また、これを他のArelメソッドとチェーンすることもできます:Person.without_friends.order("name").limit(10)
NOT EXISTS相関サブクエリは、特に行レコード数と親レコードに対する子レコードの比率が増加するにつれて高速になります。
scope :without_friends, where("NOT EXISTS (SELECT null FROM contacts where contacts.person_id = people.id)")
また、たとえば1人の友人によって除外するには:
Friend.where.not(id: other_friend.friends.pluck(:id))