【问题标题】:Howto optimize an ActiveRecord query with sums on several categories如何使用多个类别的总和优化 ActiveRecord 查询
【发布时间】:2011-04-08 14:50:17
【问题描述】:

型号:

  • 用户 has_many 类别,has_many 条目
  • 类别 has_many Entries
  • 条目属于_类别和用户

让我们假设,条目上有一个名称和一个数量。 如果我有一个视图需要为特定用户显示特定月份(让我们暂时使用 created_at),并希望显示一个表格,其中包含按类别分组的特定月份中的所有条目(不显示空类别)和总和和类别中的条目数。

我的问题是: 查询数据库(并充分利用缓存)的最有效方法是什么?此视图会经常呈现,并且每次用户创建新条目时,自然会更改一个类别的总和,但不会更改其他类别的总和。

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-3 activerecord activemodel


    【解决方案1】:

    查询部分可以很简单地用范围来完成:

    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 和条目 created_at 月份创建新条目时简单地将其过期。我想说您应该先使用这种方法,然后再尝试单独缓存每个类别的条目。此查询应该执行得很快,因此您不必深入研究单独缓存每一行。它还将执行单个查询,而不是为每个类别执行一个查询。

    这就是我不单独缓存每一行的原因:

    • 您仍然需要查询数据库以获取用户的类别列表或类别 ID,因此无论如何您都必须执行一次查询。
    • 缓存过期更加复杂,因为在更多情况下您必须使两个缓存过期,例如,当条目的类别发生更改时,您必须使旧类别缓存和新类别缓存过期。
    • 您最终可能会针对您的数据库运行更多查询以获取过期的缓存信息,并且数据库的延迟最终可能会比实际查询花费的时间更长。
    • 您不需要缓存每一行,因为查询很简单并且使用索引。您应该有一个关于 user_id 和 category_id 条目的索引。

    【讨论】:

    • 伟大的职位!非常感谢你!我遇到了一个小问题:在 SQLite 中进行开发,在 PostgreSQL 上进行生产。在 SQLite 上,没有 MONTH 函数。没有这一行,输出很好(并返回所有条目),如果我将其更改为where('strftime(%m, created_at)= ?', date.month),则结果中也包含零条目的类别。是否有适用于 SQLite 和 Postgre(Heroku 部署)的版本?
    • 是的,您可以使用某种 between 查询。例如:'first_day_of_the_month
    【解决方案2】:

    潘的回答很完美,很有帮助。对于档案:这是我在我的应用程序中使用的查询:

    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')
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-07-10
      • 2016-08-06
      • 2011-09-02
      • 1970-01-01
      • 2012-02-20
      • 1970-01-01
      • 2013-06-28
      相关资源
      最近更新 更多