209

単純な関連付けを考えてみましょう...

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_searchgemで使用できるクエリ/スコープが必要です

クエリのパフォーマンスコストは気にしません

そして、実際のSQLから離れるほど良い...

4

9 に答える 9

522

アップデート4-Rails6.1

今後の6.1でこれを実行できることを指摘してくれたTimParkに感謝します。

Person.where.missing(:contacts)

彼もリンクした投稿に感謝します。

アップデート3-Rails5

優れたRails5ソリューションを提供してくれた@Ansonに感謝します(以下の回答に+1を付けてください)。これを使用left_outer_joinsして、関連付けの読み込みを回避できます。

Person.left_outer_joins(:contacts).where(contacts: { id: nil })

人々がそれを見つけることができるように私はそれをここに含めました、しかし彼はこれのために+1に値します。素晴らしい追加です!

アップデート2

誰かが逆に、人のいない友達について尋ねました。以下でコメントしたように、これにより、最後のフィールド(上記:person_id:)は実際には返されるモデルに関連している必要はなく、結合テーブルのフィールドである必要があることに気づきました。それらはすべてそうなるnilので、それらのどれでもかまいません。これにより、上記のより簡単な解決策が得られます。

Person.includes(:contacts).where(contacts: { id: nil })

そして、これを切り替えて人のいない友達を返すのはさらに簡単になり、前のクラスだけを変更します。

Friend.includes(:contacts).where(contacts: { id: nil })

アップデート

コメントで質問がありhas_oneましたので、更新してください。ここでの秘訣はincludes()、関連付けの名前をwhere期待しますが、テーブルの名前を期待することです。アソシエーションの場合has_one、関連付けは通常、単数で表されるため、変更されますが、where()パーツはそのまま残ります。したがって、その場合Personのみhas_one :contact、ステートメントは次のようになります。

Person.includes(:contact).where(contacts: { person_id: nil })

オリジナル

より良い:

Person.includes(:friends).where(friends: { person_id: nil })

hmtの場合、基本的に同じことです。友達がいない人にも連絡先がないという事実に依存します。

Person.includes(:contacts).where(contacts: { person_id: nil })
于 2011-04-06T17:05:10.873 に答える
186

smathyにはRails3の良い答えがあります。

Rails 5left_outer_joinsの場合、関連付けの読み込みを回避するために使用できます。

Person.left_outer_joins(:contacts).where( contacts: { id: nil } )

APIドキュメントを確認してください。プルリクエスト#12071で紹介されました。

于 2016-11-09T16:19:01.390 に答える
124

これはまだSQLにかなり近いですが、最初のケースでは友達がいない全員を取得する必要があります。

Person.where('id NOT IN (SELECT DISTINCT(person_id) FROM friends)')
于 2011-03-16T00:33:29.780 に答える
14

友達がいない人

Person.includes(:friends).where("friends.person_id IS NULL")

または、少なくとも1人の友達がいます

Person.includes(:friends).where("friends.person_id IS NOT NULL")

スコープを設定することにより、Arelでこれを行うことができます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)
于 2013-09-29T16:05:09.353 に答える
12

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 /クエリ(約10%遅い)

しかし、その後、NOのレコードをDISTINCT()...探しているので、連絡先のリストである必要があるだけで、電話をかける必要がないことに気づきました。だから私はこのスコープを試しました:PersonContactsNOT INperson_ids

  scope :without_friends_v3, -> { where("id NOT IN (SELECT person_id FROM contacts)") }

同じ結果が得られますが、平均425ミリ秒/呼び出しで-ほぼ半分の時間...

今、あなたは他の同様のクエリで必要かもしれませんDISTINCT-しかし私の場合、これはうまくいくようです。

ご協力いただきありがとうございます

于 2011-03-16T15:17:21.440 に答える
6

残念ながら、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)

于 2011-03-16T00:29:54.490 に答える
1

NOT EXISTS相関サブクエリは、特に行数と子レコードと親レコードの比率が増加するため、高速である必要があります。

scope :without_friends, where("NOT EXISTS (SELECT null FROM contacts where contacts.person_id = people.id)")
于 2013-09-16T08:40:28.590 に答える
1

また、たとえば1人の友人で除外するには:

Friend.where.not(id: other_friend.friends.pluck(:id))
于 2017-06-01T23:53:13.993 に答える
1

サブクエリを使用するオプションは次のとおりです。

# Scenario #1 - person <-> friend
people = Person.where.not(id: Friend.select(:person_id))

# Scenario #2 - person <-> contact <-> friend
people = Person.where.not(id: Contact.select(:person_id))

上記の式は、次のSQLを生成する必要があります。

-- Scenario #1 - person <-> friend
SELECT people.*
FROM people 
WHERE people.id NOT IN (
  SELECT friends.person_id
  FROM friends
)

-- Scenario #2 - person <-> contact <-> friend
SELECT people.*
FROM people 
WHERE people.id NOT IN (
  SELECT contacts.person_id
  FROM contacts
)
于 2020-10-16T14:07:02.330 に答える