L'enregistrement actif de Rails peut-il gérer les requêtes d'agrégat SQL?
-
10-07-2019 - |
Question
Je viens tout juste d'apprendre l'apprentissage actif et je me demande comment récupérer au mieux les données de plusieurs tables dans lesquelles une requête d'agrégat SQL est impliquée.
Dans l'exemple suivant (tiré d'une application médicale), je recherche les événements les plus récents de différents types pour chaque patient (par exemple, la dernière visite, le dernier test de laboratoire, etc.). Comme vous pouvez le voir à partir de la requête SQL ci-dessous, je recherche la valeur max (date) d'une requête groupée. J'ai eu recours à find_by_sql pour le faire. Toutefois, j'aimerais savoir comment faire cela sans utiliser find_by_sql.
IOW - comment obtiendriez-vous les données requises ici en utilisant une approche purement ActiveRecord? Vous trouverez ci-dessous les définitions de table et de classe que je teste avec:
Rechercher par sql pour récupérer les entrées les plus récentes pour chaque type - notez le 'max (date_événement)' 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 résultat de la requête SQL:
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
La solution
Comme l'a souligné Pallan, l'option : select
ne peut pas être utilisée avec l'option : include
. Cependant, l'option : join
peut. Et c'est ce que vous voulez ici. En fait, il peut prendre les mêmes arguments que : include
ou utiliser votre propre code SQL. Voici un code approximatif, non testé, qui peut nécessiter quelques manipulations mineures.
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")
Notez que j'ai légèrement modifié les choses. J'ai renommé l'alias event_date
en max_date
afin d'éviter toute confusion quant à 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, vous pouvez appeler date_événement.max_date
. J'ai également ajouté la colonne id
de l'événement, car vous pouvez parfois obtenir quelques erreurs désagréables sans attribut id
(selon l'utilisation des modèles renvoyés).
La principale différence entre : include
et : joint
est que l'ancien effectue un chargement rapide des modèles associés. En d'autres termes, il va automatiquement chercher l'objet patient associé pour chaque événement. Cela nécessite le contrôle de l'instruction select
car il doit sélectionner les attributs du patient en même temps. Avec : join
, les objets patient ne sont pas instanciés.
Autres conseils
Une inclusion est juste une jointure gauche intelligente, vous pouvez l'utiliser conjointement avec select et group by dans votre .find
Dans la version actuelle d’AR (2.3), je pense que votre seule option est d’utiliser find_by_sql. Pourquoi? Vos attributs SELECT personnalisés. ActiveRecord permet: de sélectionner et d'inclure des arguments à l'appel de recherche. Malheureusement, ils ne peuvent pas être utilisés ensemble. Si vous spécifiez les deux: la sélection sera ignorée. Dans votre exemple, vous devez sélectionner pour limiter vos colonnes et obtenir votre fonction MAX.
J'ai entendu dire qu'il existe un correctif / hack pour les amener à travailler ensemble, mais je ne sais pas trop où il se trouve.
Peer