¿Puede Active Record de Rails manejar consultas agregadas de SQL?
-
10-07-2019 - |
Pregunta
Recién comencé a aprender el registro activo y me pregunto cómo recuperar mejor los datos de varias tablas donde está involucrada una consulta agregada SQL.
En el siguiente ejemplo (de una aplicación médica), estoy buscando los eventos más recientes de varios tipos para cada paciente (por ejemplo, última visita, última prueba de laboratorio, etc.). Como puede ver en la consulta sql a continuación, estoy buscando el valor máximo (fecha) de una consulta agrupada. Recurrí a find_by_sql para hacer esto; sin embargo, me gustaría ver cómo hacerlo sin usar find_by_sql.
IOW: ¿cómo obtendría los datos requeridos aquí utilizando un enfoque ActiveRecord puro? A continuación se muestran las definiciones de tabla y clase con las que estoy probando:
Buscar por SQL para recuperar las entradas más recientes para cada tipo: tenga en cuenta el 'max (event_date)' aquí
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"
Aquí está el resultado de la consulta SQL de muestra:
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
Clases
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
Solución
Como señaló Pallan, la opción : select
no se puede usar con la opción : include
. Sin embargo, la opción : une
puede. Y esto es lo que quieres aquí. De hecho, puede tomar los mismos argumentos que : include
o usar su propio SQL. Aquí hay un código aproximado, no probado, que puede necesitar algunos pequeños retoques.
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")
Nota: modifiqué un poco las cosas. Cambié el nombre del alias event_date
a max_date
para que no haya confusión sobre a qué atributo se refiere. Los atributos utilizados en su consulta : select
están disponibles en los modelos devueltos. Por ejemplo, en esto puede llamar a event.max_date
. También agregué la columna de evento id
porque a veces puedes obtener algunos errores desagradables sin un atributo id
(dependiendo de cómo uses los modelos devueltos).
La principal diferencia entre : include
y : une
es que el primero realiza una carga entusiasta de los modelos asociados. En otras palabras, buscará automáticamente el objeto de paciente asociado para cada evento. Esto requiere el control de la declaración select
porque necesita seleccionar los atributos del paciente al mismo tiempo. Con : une
los objetos del paciente no se instancian.
Otros consejos
Una inclusión es solo una combinación izquierda inteligente, puede usarla junto con select y group by en su .find
En la versión actual de AR (2.3) creo que su única opción es usar find_by_sql. ¿Por qué? Tus atributos SELECT personalizados. ActiveRecord permite: seleccionar e: incluir argumentos para la llamada de búsqueda. Lamentablemente no se pueden usar juntos. Si especifica ambos, se ignorará: select. En su ejemplo, necesita seleccionar para limitar sus columnas y obtener su función MAX.
Escuché que hay un parche / pirateo para que trabajen juntos, pero no estoy seguro de dónde está.
Par