web-dev-qa-db-ja.com

Rails has_manyレコードが関連付けられていないレコードを検索

これはかなり簡単に思えますが、Googleに掲載することはできません。

私が持っている場合:

class City < ActiveRecord::Base
  has_many :photos
end

class Photo < ActiveRecord::Base
  belongs_to :city
end

写真のないすべての都市を探したいです。のようなものを呼び出すことができるようにしたい...

City.where( photos.empty? )

...しかし、それは存在しません。それでは、この種のクエリをどのように行うのでしょうか?


更新:元の質問に対する答えが見つかったので、興味がありますが、どのように逆を構築しますか?

IE:これらをスコープとして作成したい場合:

scope :without_photos, includes(:photos).where( :photos => {:city_id=>nil} )
scope :with_photos, ???
87
Andrew

Bah、ここで見つけました: https://stackoverflow.com/a/5570221/417872

City.includes(:photos).where(photos: { city_id: nil })
121
Andrew

Rails 5で、写真のないすべての都市を検索するには、 left_outer_joins

City.left_outer_joins(:photos).where(photos: {id: nil})

次のようなSQLが生成されます。

SELECT cities.*
FROM cities LEFT OUTER JOIN photos ON photos.city_id = city.id
WHERE photos.id IS NULL

includes を使用:

City.includes(:photos).where(photos: {id: nil})

同じ結果になりますが、次のようなはるかにいSQLになります。

SELECT cities.id AS t0_r0, cities.attr1 AS t0_r1, cities.attr2 AS t0_r2, cities.created_at AS t0_r3, cities.updated_at AS t0_r4, photos.id AS t1_r0, photos.city_id AS t1_r1, photos.attr1 AS t1_r2, photos.attr2 AS t1_r3, photos.created_at AS t1_r4, photos.updated_at AS t1_r5
FROM cities LEFT OUTER JOIN photos ON photos.city_id = cities.id
WHERE photos.id IS NULL
45
TeWu

結合されたテーブルから一致するレコードのないレコードを検索しようとすると、LEFT OUTER JOINを使用する必要があります

scope :with_photos, joins('LEFT OUTER JOIN photos ON cities.id = photos.city_id').group('cities.id').having('count(photos.id) > 0')
scope :without_photos, joins('LEFT OUTER JOIN photos ON cities.id = photos.city_id').group('cities.id').having('count(photos.id) = 0')
22
Yossi Shasho

結合を使用してすべてのものを取得しましたwith photos:

scope :with_photos, -> { joins(:photos).distinct }

その特定のケースについて、書きやすく理解しやすい。結合を実行することとインクルードを実行することの効率が何であるかはわかりませんが、

6
Onikoroshi

Rails 5+を実行しておらず、パフォーマンスが必須の場合、無駄なActiveRecordの作成を避け、必要なものだけを取得します。

City.where("NOT EXISTS(SELECT 1 FROM photos WHERE photos.city_id = cities.id LIMIT 1)")
0
RaphaMex

LEFT OUTER JOINを実行したいので、受け入れられた答えがまさにあなたが探しているものを与えるとは思わず、その答えはINNER JOINを与えます。少なくともRails 5では次を使用できます:

scope :without_photos, left_joins(:photos).where( photos: {id: nil} )

または、名前空間によってmerge句が扱いにくい場合にwhereを使用できます。

scope :without_photos, left_joins(:photos).merge( Photos.where(id: nil) )
0
skepticscript