Ruby Rails Complex SQL с агрегатной функцией и DayOfWeek

Рейлс 2.3.4

Я искал Google, и не нашел ответа на мою дилемму.

Для этого обсуждения у меня есть две модели. Пользователи и записи. Пользователи могут иметь много записей (по одной на каждый день).

Записи имеют значения и даты send_at.

Я хочу запросить и отобразить среднее значение записей для пользователя ПО ДНЯМ НЕДЕЛИ. Итак, если пользователь ввел значения, скажем, за последние 3 недели, я хочу показать среднее значение для воскресенья, понедельника и т. д. В MySQL это просто:

SELECT DAYOFWEEK(sent_at) as day, AVG(value) as average FROM entries WHERE user_id = ? GROUP BY 1

Этот запрос вернет от 0 до 7 записей, в зависимости от того, сколько дней у пользователя была хотя бы одна запись.

Я просмотрел find_by_sql, но пока я ищу Entry, я не хочу возвращать объект Entry; вместо этого мне нужен массив до 7 дней и средние значения...

Кроме того, меня немного беспокоит производительность этого, так как мы хотели бы загрузить это в пользовательскую модель, когда пользователь входит в систему, чтобы ее можно было отобразить на их панели инструментов. Любые советы / указатели приветствуются. Я относительно новичок в Rails.


person devGuy    schedule 03.02.2011    source источник
comment
После небольшого дальнейшего исследования и настройки я обнаружил, что это будет работать: @ee=Entry.all(:select =› dayofweek(entries.sent_at) as Day,avg(entries.value) as Value, :group =› dayofweek(sent_at), :conditions =› ['user_id = ?', self.id]) Однако это выглядит некрасиво и может быть не очень эффективным. Я рассмотрю полученные ответы и посмотрю, стали ли они лучше.   -  person devGuy    schedule 03.02.2011


Ответы (2)


Вы можете запросить базу данных напрямую, нет необходимости использовать фактический объект ActiveRecord. Например:

ActiveRecord::Base.connection.execute "SELECT DAYOFWEEK(sent_at) as day, AVG(value) as average FROM entries WHERE user_id = #{user.id} GROUP BY DAYOFWEEK(sent_at);"

Это даст вам MySql::Result или MySql2::Result, которые вы можете затем использовать каждый или все в этом перечислимом, чтобы просмотреть свои результаты.

Что касается кэширования, я бы рекомендовал использовать memcached, но подойдет и любая другая стратегия кэширования rails. Приятным преимуществом memcached является то, что срок действия вашего кеша может истечь через определенное время. Например:

result = Rails.cache.fetch('user/#{user.id}/averages', :expires_in => 1.day) do
  # Your sql query and results go here
end

Это поместит ваши результаты в memcached на один день под ключом «user//averages». Например, если бы вы были пользователем с идентификатором 10, ваши средние значения были бы в memcached в разделе «user/10/average», и в следующий раз, когда вы отправитесь выполнять этот запрос (в течение того же дня), будет использоваться кешированная версия вместо фактического попадания в база данных.

person Pan Thomakos    schedule 03.02.2011
comment
Прямой запрос ActiveRecord к базе данных действительно работает. Есть ли преимущество (в накладных расходах) использования прямого запроса к базе данных по сравнению с запросом Entry.all, который я использовал выше (в комментарии к моему исходному вопросу)? - person devGuy; 03.02.2011
comment
Это действительно зависит от того, как вы хотите справиться со своими результатами. Это только предпочтения и разница в формате. На мой взгляд, мне не нравится смешивать один объект с данными из совокупного запроса. Нет причин создавать экземпляр объекта Entry с несвязанными данными, когда вместо этого я могу использовать массив строк и чисел. - person Pan Thomakos; 03.02.2011
comment
Я согласен (насчет того, чтобы не создавать экземпляр объекта с несвязанными данными). Я думаю, что буду использовать метод прямого запроса. Спасибо за помощь. - person devGuy; 03.02.2011

Не проверено, но что-то вроде этого должно работать:

@user.entries.select('DAYOFWEEK(sent_at) as day, AVG(value) as average').group('1').all

ПРИМЕЧАНИЕ. При использовании select для явного указания столбцов возвращаемые объекты только для чтения. Rails не может надежно определить, какие столбцы можно и нельзя изменять. В этом случае вы, вероятно, не будете пытаться изменить выбранные столбцы, но вы также можете изменить свои столбцы sent_at или value с помощью результирующих объектов.

Ознакомьтесь с Руководством по созданию запросов ActiveRecord, чтобы узнать, что здесь происходит, в удобном для новичков формате. . О, и если этот запрос не работает, отправьте сообщение, чтобы другие, которые могут наткнуться на это, могли это увидеть (и я, возможно, смогу обновить).


Поскольку это не сработает из-за того, что entries возвращает массив, мы можем попробовать вместо этого использовать join:

User.where(:user_id => params[:id]).joins(:entries).select('...').group('1').all

Опять же, я не знаю, сработает ли это. Обычно вы можете указать where после joins, но я не видел, чтобы select сочеталось там. Хитрость здесь заключается в том, что select, вероятно, вообще исключит возврат каких-либо данных о user. Возможно, было бы разумнее просто отказаться от методов find_by_* в пользу написания метода в модели Entry, который просто вызывает ваш запрос с помощью select_all (docs) и пропускает сопоставление ассоциаций.

person coreyward    schedule 03.02.2011
comment
Мне очень нравится, как это выглядит, но выдает ошибку: ArgumentError: неправильное количество аргументов (1 вместо 0) - person devGuy; 03.02.2011
comment
Ах, да, я думаю, что @user.entries вернет массив, а не ActiveRecord::Relation. Обновление с другим возможным вариантом... - person coreyward; 03.02.2011
comment
Использование Rails 2.3.4. Является ли .where частью Rails 3? NoMethodError: неопределенный метод «где» для #‹Class:0x1033aeb08› - person devGuy; 03.02.2011
comment
Что ж, у меня все получилось с другим ответом, но я очень ценю вашу помощь! - person devGuy; 03.02.2011