【问题标题】:What is the best way to store a multi-dimensional counter_cache?存储多维 counter_cache 的最佳方法是什么?
【发布时间】:2020-04-28 10:44:28
【问题描述】:

在我的应用程序中,我有一个如下所示的 search_volume.rb 模型:

search_volume.rb

class SearchVolume < ApplicationRecord
   # t.integer "keyword_id"
   # t.integer "search_engine_id"
   # t.date "date"
   # t.integer "volume"
  belongs_to :keyword
  belongs_to :search_engine
end

keyword.rb

class Keyword < ApplicationRecord
 has_and_belongs_to_many :labels
 has_many :search_volumes

end

search_engine.rb

class SearchEngine < ApplicationRecord
  belongs_to :country
  belongs_to :language
end

label.rb

class Label < ApplicationRecord
 has_and_belongs_to_many :keywords
 has_many :search_volumes, through: :keywords

end

在label#index 页面上,我试图显示用户cookie 的search_engine 上个月每个标签中关键字的search_volumes 总和。我可以通过以下方式做到这一点:

<% @labels.each do |label| %>
  <%= number_with_delimiter(label.search_volumes.where(search_engine_id: cookies[:search_engine_id]).where(date: 1.month.ago.beginning_of_month..1.month.ago.end_of_month).sum(:volume)) %>
<% end %>

这很好,但我觉得上面的效率很低。使用目前的方法,我也觉得很难对搜索量进行操作。大多数时候我只想知道上个月的搜索量。

通常我会在关键字模型上创建一个 counter_cache 来跟踪最新的 search_volume,但由于有几十个 search_engine 我必须为每个创建一个,这也是低效的。

分别存储所有不同搜索引擎上个月搜索量的最有效方法是什么?

【问题讨论】:

    标签: ruby-on-rails counter-cache


    【解决方案1】:

    首先,您可以通过对所有涉及的标签执行一个请求来优化当前的实现,如下所示:

    # models
    class SearchVolume < ApplicationRecord
      # ...
    
      # the best place for your filters!
      scope :last_month, -> { where(date: 1.month.ago.beginning_of_month..1.month.ago.end_of_month) }
      scope :search_engine, ->(search_engine_id) { where(search_engine_id: search_engine_id) }
    end
    
    class Label < ApplicationRecord
      # ...
    
      # returns { label_id1 => search_volumn_sum1, label_id2 => search_volumn_sum2, ... }
      def self.last_month_search_volumes_per_label_report(labels, search_engine_id:)
        labels.
          group(:id).
          left_outer_joins(:search_volumes).
          merge(SearchVolume.last_month.search_engine(search_engine_id)).
          pluck(:id, 'SUM(search_volumes.volume)').
          to_h
      end
    end
    
    # controller
    class LabelsController < ApplicationController
      def index
        @labels = Label.all
    
        @search_volumes_report = 
          Label.last_month_search_volumes_per_label_report(
            @labels, search_engine_id: cookies[:search_engine_id]
          )
      end
    end
    
    # view
    <% @labels.each do |label| %>
      <%= number_with_delimiter(@search_volumes_report[label.id]) %>
    <% end %>
    

    请注意,我没有使用相同的架构对其进行测试,但我在本地机器上使用了类似的模型。它可以通过调整一些东西来工作。

    我提出的方法仍然是实时请求数据库。如果您确实需要在某处存储值,因为您有非常大的数据集,我建议两种解决方案:
    - 使用物化视图,您可以每月刷新(scenic gem 提供了一种在 Rails 应用程序中处理视图的好方法:https://github.com/scenic-views/scenic
    - 实现一个具有模型之间标准关系的新表,它可以按 id 和月份存储您的计算,并且您可以使用 rake 任务每个月填充它,然后您只需急切地加载您的计算

    请让我知道您的反馈!

    【讨论】:

    • 谢谢!您拥有的代码确实有效。检索记录确实比我想要的要长一些,所以我将研究物化视图和表
    猜你喜欢
    • 1970-01-01
    • 2016-04-26
    • 2013-05-23
    • 1970-01-01
    • 1970-01-01
    • 2021-05-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多