2009-08-01 9 views
9

J'ai commencé à apprendre l'enregistrement actif et je me demande comment récupérer au mieux les données de plusieurs tables où une requête d'agrégat SQL est impliquée.L'enregistrement actif de Can Rails gère les requêtes d'agrégat SQL?

Dans l'exemple suivant (d'une application médicale), je suis à la recherche des événements les plus récents de divers types pour chaque patient (par exemple, dernière visite, dernier test, etc.). Comme vous pouvez le voir dans la requête sql ci-dessous, je suis à la recherche de la valeur max (date) d'une requête groupée. J'ai eu recours à find_by_sql pour le faire - mais j'aimerais voir comment faire ceci sans utiliser find_by_sql. IOW - comment obtiendriez-vous les données requises ici en utilisant une approche ActiveRecord pure. Ci-dessous sont la table et defs classe je teste avec:

Recherche par Sql pour récupérer les plus récentes entrées pour chaque type - notez le « max (EVENT_DATE) » ici

strsql = "select p.lname, e.patient_id, e.event_type, max(e.event_date) as event_date 
    from events e 
     inner join patients p on e.patient_id = p.id 
     group by p.lname, e.patient_id, e.event_type" 

Voici l'exemple de requête SQL résultat:

 
lname, patient_id, event_type, latest 
'Hunt', 3, 'Labtest', '2003-05-01 00:00:00' 
'Hunt', 3, 'Visit', '2003-03-01 00:00:00' 
'Seifer', 2, 'Labtest', '2002-05-01 00:00:00' 
'Seifer', 2, 'Visit', '2002-03-01 00:00:00' 

Table Relationships are: 
Tables ---> Patients --> Events 
           --> visits 
           --> labtests 
           --> ... other 
patients 
     t.string :lname 
     t.date :dob 

events 
     t.column :patient_id, :integer 
     t.column :event_date, :datetime 
     t.column :event_type, :string 

visits 
     t.column :event_id, :integer 
     t.column :visittype, :string 

labtests 
     t.column :event_id, :integer 
     t.column :testtype, :string 
     t.column :testvalue, :string 

Classes

class Patient < ActiveRecord::Base 
    has_many :events 
    has_many :visits, :through =>:events 
    has_many :labtests, :through => :events 
end 

class Event < ActiveRecord::Base 
    has_many :visits 
    has_many :labtests 
    belongs_to :patient 
end 

class Visit < ActiveRecord::Base 
    belongs_to :event 
end 

class Labtest < ActiveRecord::Base 
    belongs_to :event 
end 

Répondre

14

Comme Pallan a souligné, l'option :select ne peut pas être utilisé avec le :include option. Cependant, l'option :joins peut. Et c'est ce que vous voulez ici. En fait, il peut prendre les mêmes arguments que :include ou utiliser votre propre SQL. Voici un code rugueux, non testé, peut-être besoin d'un peu de violon.

Event.all(:select => "events.id, patients.lname, events.patient_id, events.event_type, max(events.event_date) as max_date", :joins => :patient, :group => "patients.lname, events.patient_id, events.event_type") 

Remarque J'ai légèrement modifié les choses. J'ai renommé l'alias event_date en max_date, donc il n'y a pas de confusion sur l'attribut auquel vous faites référence. Les attributs utilisés dans votre requête :select sont disponibles dans les modèles renvoyés. Par exemple, dans cela, vous pouvez appeler event.max_date. J'ai également ajouté l'événement colonne id car vous pouvez parfois obtenir des erreurs désagréables sans attribut id (selon la façon dont vous utilisez les modèles retournés).

La principale différence entre :include et :joins est que le premier effectue un chargement rapide des modèles associés. En d'autres termes, il va chercher automatiquement l'objet patient associé pour chaque événement. Cela nécessite le contrôle de l'instruction select car elle doit sélectionner les attributs du patient en même temps. Avec :joins, les objets patients ne sont pas instanciés.

+0

Suivi rapide ici d'OP.Avec 1 tweak mineur: joint (vs: join) Le code de Ryan fonctionne et remplace la requête Find_By_Sql dans mon post original. Très sympa. Cependant, en passant, je noterai simplement (AFAIK) que toutes les requêtes SQL ne peuvent pas être implémentées via des appels ORM (par exemple Union Query qui combine les valeurs Min et Max d'une table). Je suppose que cela nécessitera 2 appels distincts via l'ORM, tandis qu'un Find_By_Sql peut le faire dans 1 appel par exemple. find_by_sql ("Select Min (t.Event_Date) de la table t Union Select Max (t.Event_Date) de la table t") – BrendanC

+0

Mise à jour du message d'origine à utiliser: jointures. Vous avez raison cependant, toutes les requêtes ne peuvent pas être effectuées avec l'ORM, et ce n'est pas l'objectif de Active Record. Il a été conçu pour répondre à la majorité des besoins et laisser le reste possible avec find_by_sql. Il n'essaie pas de tout faire, alors n'hésitez pas à utiliser find_by_sql si cela correspond mieux au travail. – ryanb

0

Dans la version actuelle, je pense que l'AR (2.3) ans Notre seule option est d'utiliser find_by_sql. Pourquoi? Vos attributs SELECT personnalisés. ActiveRecord permet: select et: d'inclure des arguments à l'appel find. Malheureusement, ils ne peuvent pas être utilisés ensemble. Si vous spécifiez les deux: select sera ignoré. Dans votre exemple, vous avez besoin de sélectionner pour limiter vos colonnes et pour obtenir votre fonction MAX. J'ai entendu dire qu'il y avait un patch/hack pour les amener à travailler ensemble, mais je ne suis pas sûr de l'endroit où il se trouve.

Peer

+0

Existe-t-il un moyen d'utiliser explicit: join au lieu de et include pour contourner cette limitation? Serait-ce une option ici? – BrendanC

2

Un include est juste une gauche intelligente rejoindre, vous pouvez l'utiliser conjointement avec sélection et group_by dans votre .Find

Questions connexes