web-dev-qa-db-fra.com

Requête ActiveRecord via plusieurs jointures

J'ai un schéma comme celui-ci.

database schema

managers  
    has_many :emails  
    has_many :stores

emails  
    belongs_to :manager

stores  
    belongs_to :manager  
    belongs_to :region

regions  
    has_many :stores  
    has_many :readings

readings  
    belongs_to :regions

Je veux obtenir des lectures pour un manager. En SQL, je ferais quelque chose comme ça.

SELECT * FROM managers  
    JOIN stores ON stores.manager_id = managers.id  
    JOIN regions ON stores.region_id = regions.id  
    JOIN readings ON readings.region_number = regions.number  
WHERE  
    manager.name = 'John Smith'  
AND  
    regions.number = '1234567'
LIMIT 100

Je ne peux pas comprendre comment faire cela dans activerecord. J'ai essayé de donner un sens à http://guides.rubyonrails.org/active_record_querying.html et http://guides.rubyonrails.org/association_basics.html mais il ne s'enfonce pas. Je pense que j'ai juste besoin de le voir d'un point de vue différent.

Je pensais que j'allais accéder aux données comme ça, mais je pense que je ne comprends tout simplement pas comment cela fonctionne.

managers.name  
managers.stores.name  
managers.stores.regions.readings.limit(10)

J'ai dû faire quelque chose comme ça qui est beaucoup plus laid.

managers.first.stores.first.regions.first.readings.limit(10)
23
user1757006

Considérez les modèles suivants (et utilisez has_many through):

class Reading < ActiveRecord::Base
  belongs_to :region,
    inverse_of: :readings
end

class Region < ActiveRecord::Base
  has_many :readings,
    inverse_of: :region

  has_many :stores,
    inverse_of: :region    
end

class Store < ActiveRecord::Base
  belongs_to :region,
    inverse_of: :stores

  belongs_to :manager,
    inverse_of: :stores
end

class Manager < ActiveRecord::Base
  has_many :stores,
    inverse_of: :region

  has_many :emails,
    inverse_of: :manager

  has_many :regions,
    through: :stores

  has_many :readings,
    through: :regions
end

class Email < ActiveRecord::Base
  belongs_to :manager,
    inverse_of: :emails
end

Maintenant, votre question est un peu ambiguë parce que vous dites que vous voulez obtenir des lectures pour un gestionnaire, mais votre SQL ne sélectionne pas du tout les lectures et prescrit également une région.

En supposant que vous vouliez que toutes les lectures correspondent à un gestionnaire et une région donnés:

@readings = Reading.joins(region: { stores: :manager }).where(
  manager: { name: 'John Smith' },
  region: { id: 1234567 })

En supposant que vous souhaitiez également charger les régions, les magasins et les gestionnaires pour éviter les requêtes 1 + N:

@readings = Reading.includes(region: { stores: :manager }).where(
  manager: { name: 'John Smith' },
  region: { id: 1234567 })

En supposant que vous avez un nom de gestionnaire et que vous souhaitez à la fois ses détails et ses lectures:

@manager = Manager.where(name: 'John Smith').first!
@readings = manager.readings

Tous les exemples de requête ci-dessus renvoient ActiveRecord::Relation qui peuvent être enchaînés avec les conditions where, ou joins, limit, group, having et order etc

Vous devez également tenir compte des différences entre joins, includes, preload, eager_load et references méthodes. Il y a un résumé à leur sujet ici Je vous encourage également à lire les documents, guides et blogs sur Arel car il prend également en charge les jointures et les alias.

Après avoir utilisé ActiveRecord en colère pendant un certain temps maintenant, je suis arrivé à la conclusion que Sequel/SequelModel est une bien meilleure DB/ORM qu'ActiveRecord. Aucun manque de respect pour les développeurs mais j'ai trouvé que Sequel est un meilleur outil. Arel a une documentation fine depuis des années maintenant et ActiveRecord/Arel ont des échecs dans un certain nombre de domaines tels que les conditions de jointure, le contrôle des types de jointure et le chargement, les unions, les intersections, les requêtes récursives, les arborescences/listes de contiguïté et de nombreuses autres fonctionnalités SQL que Sequel couvertures.

Comme vous semblez commencer avec AR, vous pouvez plutôt commencer avec Sequel que lutter contre les faiblesses et les frustrations des requêtes ActiveRecord, y compris l'utilisation décousue de AR et Arel, Relations vs Associations et bizarreries de composition de requête, cela continue et sur. Il n'y a rien de plus frustrant que de connaître le SQL que vous voulez, mais ActiveRecord/Arel conspirent pour vous arrêter, vous êtes donc obligé d'utiliser la voie d'échappement annoncée et `` utilisez simplement un fragment de chaîne SQL '' et vous obtenez un résultat qui ne peut pas être enchaîné , mais le reste de votre code attend une relation! (par exemple, les résultats paginés)

45
Andrew Hacking

Je voulais commenter le commentaire de ser1757006 4 octobre 13 à 14:39, mais je n'ai pas assez de points .. Quoi qu'il en soit, cela concerne des scénarios d'imbrication plus profonds. J'ai ajouté des modèles de planète et de pays pour montrer comment fonctionne la syntaxe lorsque vous devez chaîner des modèles supplémentaires.

@readings = Reading.includes(planet: [ country: [ region: [ {stores:
:manager}]]]).where(
manager: { name: 'John Smith' },   
region: {id: 1234567 })
8
Aaron Taddiken

essayez quelque chose comme ceci:

managers.joins(stores: {regions: :readings}).where('managers.name = ? AND regions.number = ?', 'John Smith', '1234567').limit(100) 
5

En supposant que managers est un nom de modèle

 managers.find(:all,
               :joins=>" JOIN stores ON stores.manager_id = managers.id  
                         JOIN regions ON stores.region_id = regions.id  
                         JOIN readings ON readings.region_number = regions.number"
               :conditions=>"manager.name = 'John Smith' AND regions.number = '1234567'"
               :limit=>100)
5
shiva