2009-08-01 10 views
9

È appena iniziato l'apprendimento della registrazione attiva e mi sto chiedendo come recuperare i dati da più tabelle in cui è coinvolta una query SQL aggregata.Il record attivo di Rails può gestire query di aggregazione SQL?

Nell'esempio seguente (da un'app medica) cerco gli eventi più recenti di vario tipo per ciascun paziente (ad esempio, l'ultima visita, l'ultimo labtest ecc.). Come puoi vedere dalla query sql qui sotto cerco il valore massimo (data) da una query raggruppata. Ho fatto ricorso a find_by_sql per farlo - tuttavia mi piacerebbe vedere come farlo senza usare find_by_sql.

IOW: come ottenere qui i dati richiesti utilizzando un approccio ActiveRecord puro. Di seguito sono la Tavola e defs Classe sto testando con:

Trova per SQL per recuperare le voci più recenti per ogni tipo - notare la 'max (EVENT_DATE)' qui

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" 

Ecco la query di esempio SQL risultato:

 
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 

Classi

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 

risposta

14

Come Pallan sottolineato, l'opzione :select non possono essere utilizzati con il :include opzione. Tuttavia l'opzione :joins può. E questo è quello che vuoi qui. In effetti, può prendere gli stessi argomenti di :include o utilizzare il proprio SQL. Ecco alcuni codici grezzi e non testati, potrebbero aver bisogno di qualche piccolo giochetto.

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 Ho modificato leggermente le cose. Ho cambiato il nome dell'alias event_date in max_date, quindi non c'è confusione su quale attributo ci si riferisce. Gli attributi utilizzati nella tua query :select sono disponibili nei modelli restituiti. Ad esempio, in questo è possibile chiamare event.max_date. Ho anche aggiunto la colonna dell'evento id perché a volte puoi ottenere alcuni brutti errori senza un attributo id (a seconda di come usi i modelli restituiti).

La differenza principale tra :include e :joins è che il primo carico esegue ansiosi dei modelli associati. In altre parole, recupera automaticamente l'oggetto paziente associato per ogni evento. Ciò richiede il controllo dell'istruzione select perché è necessario selezionare gli attributi del paziente contemporaneamente. Con :joins gli oggetti paziente non vengono istanziati.

+0

Seguito rapido qui da OP.Con 1 modifica minore: join (vs: join) Il codice di Ryan funziona e sostituisce la query Find_By_Sql nel mio post originale. Molto bello Tuttavia di passaggio noterò solo che (AFAIK) non tutte le query SQL possono essere implementate tramite chiamate ORM (ad esempio Union Query che combina i valori Min e Max da una tabella). Sospetto che ciò richiederà 2 chiamate separate tramite l'ORM, mentre un Find_By_Sql può farlo in una chiamata, ad es. find_by_sql ("Seleziona Min (t.Event_Date) dalla tabella t Unione Seleziona Max (t.Event_Date) dalla tabella t") – BrendanC

+0

Aggiornato il post originale da utilizzare: join. Sei corretto, tuttavia, non tutte le query possono essere eseguite con ORM, e questo non è l'obiettivo di Active Record. È stato progettato appositamente per soddisfare la maggior parte dei bisogni e lasciare il resto possibile con find_by_sql. Non cerca di fare tutto, quindi non esitate a usare find_by_sql se si adatta meglio al lavoro. – ryanb

0

Nella versione corrente di AR (2.3) penso yo la tua unica opzione è usare find_by_sql. Perché? I tuoi attributi SELECT personalizzati. ActiveRecord consente: selezionare e: includere argomenti per la chiamata di ricerca. Sfortunatamente non possono essere usati insieme. Se specifichi entrambi: select verrà ignorato. Nel tuo esempio hai bisogno della selezione per limitare le colonne e ottenere la tua funzione MAX.

Ho sentito che c'è una patch/hack per farli lavorare insieme, ma non sono sicuro di dove sia.

Peer

+0

C'è un modo per utilizzare un esplicito: join anziché e include per aggirare questa limitazione? Sarebbe un'opzione qui? – BrendanC

2

un include è solo un intelligente sinistra join, è possibile utilizzare tale in combinazione con selezionare e group_by nella vostra .find

Problemi correlati