Ответ 1
Ба, нашел его здесь: fooobar.com/questions/46556/...
City.includes(:photos).where(photos: { city_id: nil })
Это кажется довольно простым, но я не могу заставить его появляться в Google.
Если у меня есть:
class City < ActiveRecord::Base
has_many :photos
end
class Photo < ActiveRecord::Base
belongs_to :city
end
Я хочу найти все города, у которых нет фотографий. Мне бы очень хотелось называть что-то вроде...
City.where( photos.empty? )
... но этого не существует. Итак, как вы делаете такой запрос?
Update: Теперь, найдя ответ на исходный вопрос, мне любопытно, как вы создаете обратный?
IE: если бы я хотел создать их как области видимости:
scope :without_photos, includes(:photos).where( :photos => {:city_id=>nil} )
scope :with_photos, ???
Ба, нашел его здесь: fooobar.com/questions/46556/...
City.includes(:photos).where(photos: { city_id: nil })
В 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
При попытке найти записи без соответствующих записей из объединенной таблицы вам нужно использовать 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')
Я использовал соединение, чтобы получить все фотографии:
scope :with_photos, -> { joins(:photos).distinct }
Легче писать и понимать для этого конкретного случая. Я не уверен, что эффективность заключается в том, чтобы выполнить соединение и делать, включая
Я не верю, что принятый ответ даст вам именно то, что вы ищете, так как вы хотите сделать 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) )
Если вы не используете Rails 5+ и производительность необходима, избегайте бесполезного создания ActiveRecord и получите именно то, что вам нужно:
City.where("NOT EXISTS(SELECT 1 FROM photos WHERE photos.city_id = cities.id LIMIT 1)")