2009-08-01 7 views
9

Acabo de empezar a aprender el registro activo y me pregunto cómo recuperar mejor los datos de varias tablas donde se trata de una consulta agregada de SQL.¿El registro activo de Can Rails maneja las consultas SQL agregadas?

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, último 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 de ActiveRecord puro. A continuación se presentan la tabla y defs Clase Estoy probando con:

Búsqueda por SQL para recuperar las entradas más recientes de 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" 

Ésta es la consulta de ejemplo sql resultado:

 
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 

Respuesta

14

Como Pallan señaló, la opción :select no se puede utilizar con el :include opción. Sin embargo, la opción :joins puede. Y esto es lo que quieres aquí. De hecho, puede tomar los mismos argumentos que :include o usar su propio SQL. Aquí hay algunos códigos difíciles de probar que pueden necesitar un poco de manipulación.

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é las cosas ligeramente. Cambié el nombre del alias event_date al max_date, por lo que no hay confusión sobre el atributo al que se refiere. Los atributos utilizados en su consulta :select están disponibles en los modelos devueltos. Por ejemplo, en esto puede llamar al event.max_date. También agregué la columna del evento id porque a veces puede obtener algunos errores desagradables sin un atributo id (dependiendo de cómo use los modelos devueltos).

La principal diferencia entre :include y :joins es que la primera realiza una carga ansiosa 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 :joins los objetos del paciente no se crean instancias.

+0

Seguimiento rápido aquí desde OP.Con 1 pequeño ajuste: uniones (vs: unir) el código de Ryan funciona y reemplaza la consulta Find_By_Sql en mi publicación original. Muy genial. Sin embargo, al pasar, me limitaré a señalar que (AFAIK) no todas las consultas SQL se pueden implementar a través de llamadas ORM (por ejemplo, Union Query que combina los valores Min y Max de una tabla). Sospecho que esto requerirá 2 llamadas separadas a través del ORM, mientras que un Find_By_Sql puede hacerlo en 1 llamada, p. find_by_sql ("Seleccione Min (t.Event_Date) de la tabla t Union Select Max (t.Event_Date) de la tabla t") – BrendanC

+0

Actualizó la publicación original para usar: une. Sin embargo, está en lo correcto, no todas las consultas se pueden hacer con el ORM, y ese no es el objetivo de Active Record. Fue diseñado a propósito para satisfacer la mayoría de las necesidades y dejar el resto posible con find_by_sql. No intenta hacer todo, así que no dude en usar find_by_sql si se adapta mejor al trabajo. – ryanb

0

En la versión actual de AR (2.3) Creo que yo Tu única opción es usar find_by_sql. ¿Por qué? Tus atributos SELECCIONADOS personalizados. ActiveRecord permite: seleccionar y: incluir argumentos a la llamada de búsqueda. Desafortunadamente no se pueden usar juntos. Si especifica tanto el: select se ignorará. En su ejemplo, necesita seleccionar para limitar sus columnas y obtener su función MAX.

He oído que hay un parche/truco para que funcionen juntos, pero no estoy seguro de dónde está.

Peer

+0

¿Hay alguna manera de utilizar un explícito: join en lugar de e include para evitar esta limitación? ¿Sería eso una opción aquí? – BrendanC

2

un include es sólo una izquierda inteligente se unen, se puede usar que, en conjunción con seleccionar y group_by en su .find

Cuestiones relacionadas