Может ли активная запись Rails обрабатывать SQL-запросы SQL?
Просто начал изучать активную запись и задаюсь вопросом, как наилучшим образом извлекать данные из нескольких таблиц, в которых участвует SQL-агрегатный запрос.
В следующем примере (из медицинского приложения) я ищу самые последние события различных типов для каждого пациента (например, последний визит, последний лабораторный тест и т.д.). Как вы можете видеть из sql-запроса ниже, я ищу значение max (date) из сгруппированного запроса. Я обратился к find_by_sql, чтобы сделать это, но я хотел бы посмотреть, как это сделать, не используя find_by_sql.
IOW - как бы вы получили требуемые данные здесь, используя чистый подход ActiveRecord. Ниже приведены таблицы и классы, которые я тестирую с помощью:
Найти по Sql для получения последних записей для каждого типа - обратите внимание на "max (event_date)" здесь
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"
Здесь пример результата запроса 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
Классы
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
Ответы
Ответ 1
Как отметил Паллан, опция :select
не может использоваться с опцией :include
. Однако опция :joins
может. И это то, что вы хотите здесь. Фактически, он может принимать те же аргументы, что и :include
, или использовать собственный SQL. Здесь некоторый грубый, непроверенный код, возможно, нуждается в некоторой незначительной игре.
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")
Примечание. Я немного изменил ситуацию. Я переименовал псевдоним event_date
в max_date
, поэтому нет путаницы в отношении того, к какому атрибуту вы обращаетесь. Атрибуты, используемые в вашем запросе :select
, доступны в возвращенных моделях. Например, вы можете вызвать event.max_date
. Я также добавил столбец id
, потому что иногда вы можете получить некоторые неприятные ошибки без атрибута id
(в зависимости от того, как вы используете возвращенные модели).
Основное различие между :include
и :joins
заключается в том, что первая выполняет активную загрузку связанных моделей. Другими словами, он автоматически получает связанный объект пациента для каждого события. Для этого требуется контроль над оператором select
, поскольку он должен одновременно выбирать атрибуты пациента. При :joins
объекты пациента не создаются.
Ответ 2
Включение - это просто умное левое соединение, вы можете использовать это в сочетании с select и group_by в своем .find
Ответ 3
В текущей версии AR (2.3) я думаю, что ваш единственный вариант - использовать find_by_sql. Зачем? Ваши пользовательские атрибуты SELECT. ActiveRecord позволяет: выбирать и: включать аргументы в вызов find. К сожалению, они не могут использоваться вместе. Если вы укажете, что: select будет проигнорирован. В вашем примере вам нужно выбрать, чтобы ограничить свои столбцы и получить свою MAX-функцию.
Я слышал, что есть патч/хак, чтобы заставить их работать вместе, но я не уверен, где он находится.
Peer