106 votes

Rails recherche un enregistrement avec zéro enregistrement has_many associé

Cela semble assez simple, mais je n'arrive pas à le faire apparaître sur Google.

Si je l'ai fait :

class City < ActiveRecord::Base
  has_many :photos
end

class Photo < ActiveRecord::Base
  belongs_to :city
end

Je veux trouver toutes les villes qui n'ont pas de photos. J'aimerais pouvoir appeler quelque chose comme...

City.where( photos.empty? )

... mais cela n'existe pas. Alors, comment faire ce genre de requête ?


Mise à jour : Ayant trouvé une réponse à la question initiale, je suis curieux de savoir comment on construit l'inverse.

IE : si je voulais les créer en tant que champs d'application :

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

139voto

Andrew Points 14110

Bah, je l'ai trouvé ici : https://stackoverflow.com/a/5570221/417872

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

62voto

TeWu Points 1814

En Versions de Rails >= 5 Pour trouver toutes les villes qui n'ont pas de photos, vous pouvez utiliser left_outer_joins :

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

ce qui se traduira par un code SQL du type :

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

Utilisation includes :

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

aura le même résultat, mais donnera lieu à un SQL beaucoup plus laid :

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

24voto

Yossi Shasho Points 1209

Lorsque vous essayez de trouver des enregistrements qui ne correspondent pas à ceux de la table jointe, vous devez utiliser une jointure externe gauche (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')

8voto

Onikoroshi Points 29

J'ai utilisé une jointure pour obtenir tous les éléments avec photos :

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

Plus facile à écrire et à comprendre, dans ce cas précis. Je ne suis pas sûr de l'efficacité d'une jointure par rapport à une inclusion, cependant

1voto

RaphaMex Points 2056

Si vous n'utilisez pas Rails 5+ et que la performance est un impératif, évitez la création inutile d'ActiveRecord et obtenez juste ce dont vous avez besoin :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X