Railsに関連するレコードがないレコードを見つけたい
-
24-10-2019 - |
質問
簡単な関連性を考えてみましょう...
class Person
has_many :friends
end
class Friend
belongs_to :person
end
ArelやMeta_に友人がいないすべての人を獲得するための最もクリーンな方法は何ですか?
そして、has_many:spullバージョンについてはどうですか
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で動作しません:スルー
私はすべての人を引っ張りたくありません。レコードと友達とルビーでループをループしてください - 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()
一部はそのままとなります。したがって、a Person
それだけ has_one :contact
その後、あなたの声明は次のとおりです。
Person.includes(:contact).where( :contacts => { :person_id => nil } )
更新2
誰かが逆、人がいない友人について尋ねました。以下でコメントしたように、これは実際に私に最後のフィールドを実現させました(上記: :person_id
)実際には、返品しているモデルに関連する必要はありません。結合テーブルのフィールドでなければなりません。彼らはすべてそうなるだろう nil
だから、それはそれらのいずれかになることができます。これは、上記のより単純なソリューションにつながります。
Person.includes(:contacts).where( :contacts => { :id => nil } )
そして、これを切り替えて、人がいない友人を返すようになります。フロントのクラスのみを変更します。
Friend.includes(:contacts).where( :contacts => { :id => nil } )
更新3-レール5
優れたRails 5ソリューションを提供してくれた@Ansonに感謝します(以下の彼の回答のために彼にいくつかの +1を与えてください)、あなたは使用できます left_outer_joins
協会の読み込みを避けるため:
Person.left_outer_joins(:contacts).where( contacts: { id: nil } )
私はここにそれを含めたので、人々はそれを見つけるでしょうが、彼はこれに +1Sに値します。素晴らしい追加!
友達がいない人
Person.includes(:friends).where("friends.person_id IS NULL")
または、少なくとも1人の友達がいます
Person.includes(:friends).where("friends.person_id IS NOT NULL")
スコープをセットアップすることで、アレルでこれを行うことができます Friend
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人の人 'レコードを備えた小さなテーブル - 平均5回のラン
UnixMonkeyのアプローチ(:without_friends_v1
)813ms /クエリ
Dmarkowのアプローチ(:without_friends_v2
)891ms / Query(〜10%遅い)
しかし、その後、私は電話が必要ではないことがわかりました DISTINCT()...
を探しています Person
なしのレコード 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)
特に子と親の記録が増加するため、存在しない相関サブクエリは速くなるはずです。
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))