Как оптимизировать запрос ActiveRecord с помощью сумм по нескольким категориям - PullRequest
1 голос
/ 08 апреля 2011

Модель:

  • Пользователь has_many Категории, Записи has_many
  • Категория has_many Записи
  • Запись принадлежит_ как категории, так и пользователю

Предположим, на записях есть имя и сумма. Если у меня есть представление, которое должно отображать определенный месяц (давайте возьмем созданный сейчас) для конкретного пользователя и хочу отобразить таблицу со всеми записями в этом конкретном месяце, сгруппированными по категориям (пустые категории не отображаются) и с суммой и количество записей в категории.

Мой вопрос: Каков наиболее эффективный способ сделать запрос к базе данных (и наилучшим образом использовать кэширование)? Это представление будет отображаться довольно часто, и каждый раз, когда пользователь создает новую запись, естественно, изменяется сумма одной категории, но не других.

Ответы [ 2 ]

2 голосов
/ 08 апреля 2011

Часть запроса может быть выполнена довольно просто с помощью области:

class Category
  scope :grouped_entries, lambda { |user, date|
    select(['categories.*', 'COUNT(entries.id) as count_entries'])
      .joins(:entries)
      .where('entries.user_id = ?', user.id)
      .where('MONTH(entries.created_at) = ?', date.month)
      .group('categories.id')
  }
end

Который затем может быть зациклен:

<% Category.grouped_entries(current_user, Date.today).each do |category| %>
  <%= category.name %> with <%= category.count_entries %> entries this month.
<% end %>

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

@categories = Rails.cache.fetch("/grouped_entries/#{current_user.id}/#{Date.today.month}") do
  Category.grouped_entries(current_user, Date.today).all
end

А затем просто истекает срок его действия, когда создается новая запись с использованием user_id и записи creation_at month. Я бы сказал, что вы должны использовать этот подход, прежде чем пытаться кэшировать записи каждой отдельной категории в отдельности. Этот запрос должен выполняться довольно быстро, поэтому вам не нужно углубляться в кеширование каждой строки в отдельности. Он также будет выполнять один запрос, а не один для каждой категории.

Вот почему я бы не кэшировал каждую строку отдельно:

  • Вам все еще нужно запросить базу данных, чтобы получить список категорий или идентификаторов категорий для пользователя, поэтому вам все равно придется выполнить один запрос.
  • Истечение срока действия Cachine более сложное, потому что есть больше случаев, когда вам нужно завершить срок действия двух кешей, например, когда изменяется категория для записи, вы должны истечь старый кеш категории и новый кеш категории.
  • Вы можете выполнить больше запросов к вашей базе данных, чтобы получить информацию о кеше с истекшим сроком действия, и задержка для базы данных, вероятно, займет больше времени, чем фактический запрос.
  • Вам не нужно кэшировать каждую строку, потому что запрос прост и использует индексы. У вас должен быть индекс user_id и category_id для записей.
1 голос
/ 10 апреля 2011

Ответ Пэна был безупречен и очень помог. Для архивов: это запрос, который я использовал в своем приложении:

scope :grouped_entries, lambda { |user, date|
  select(['categories.*', 'COUNT(entries.id) as count_entries']).
    joins(:entries).
    where('entries.user_id = ?', user.id).
    where(':first_day <= entries.created_at AND entries.created_at <= :last_day', { 
       :first_day => date.at_beginning_of_month,
       :last_day => date.at_end_of_month
    } ).
    group('categories.id')
}
...