Ruby on rails 在不使用联接的情况下向ActiveRecords添加相关数据

Ruby on rails 在不使用联接的情况下向ActiveRecords添加相关数据,ruby-on-rails,join,scope,rails-activerecord,Ruby On Rails,Join,Scope,Rails Activerecord,我的Rails 4应用程序有一个用户模型、一个链接模型和一个点击模型。每个用户都有许多链接s,每个链接都有许多点击s。有时,我想显示用户的链接的列表,以及它的点击次数 显而易见的方法是在链接上循环并对每个链接调用link.hits.count,但这会产生N+1个查询。因此,我编写了一个作用域,它连接hits表: scope :with_hit_counts, -> { joins("LEFT OUTER JOIN hits ON hits.link_id = links.id").se

我的Rails 4应用程序有一个
用户
模型、一个
链接
模型和一个
点击
模型。每个
用户
都有许多
链接
s,每个
链接
都有许多
点击
s。有时,我想显示
用户的
链接的列表,以及它的
点击次数

显而易见的方法是在链接上循环并对每个链接调用
link.hits.count
,但这会产生N+1个查询。因此,我编写了一个作用域,它连接
hits
表:

scope :with_hit_counts, -> {
  joins("LEFT OUTER JOIN hits ON hits.link_id = links.id").select('links.*', 'count(hits.link_id) AS hit_count').group("links.id")
}
这有效地为每个
链接添加了一个虚拟的
点击次数
属性,该属性在单个查询中计算得到。奇怪的是,它似乎是加载链接的一个单独查询,而不是在同一个查询中实际执行:

SELECT COUNT(*) AS count_all, links.id AS links_id
  FROM "links" LEFT OUTER JOIN hits ON hits.link_id = links.id
 WHERE "links"."user_id" = $1
 GROUP BY links.id
 ORDER BY "links"."domain_id" ASC, "links"."custom_slug" ASC, "links"."id" ASC ;
不幸的是,随着
点击
表的增长,这已成为一个缓慢的查询
EXPLAIN
表示查询使用索引将所有点击与其匹配的链接连接起来,然后通过顺序扫描将链接缩小到只有正确的
user\u id
的链接;这似乎就是它慢的原因。但是,如果我们已经单独加载了链接列表,并且已经加载了,那么实际上根本不需要加入links表。我们可以为用户获取链接id列表,然后使用(id列表)
中的
hits.link\u id在hits表上进行纯粹的查询

将其作为单独的查询编写很容易,而且运行速度极快:

Hit.where(link_id: @user.links.ids).group(:link_id).count
问题是,我不知道如何让ActiveRecord在
链接
模型上作为一个作用域来做这件事,这样每个
链接
都有一个我可以使用的
命中计数
属性,这样我就可以使用得到的返回值作为一个与链接其他查询的能力相关的关系。有什么想法吗


(我确实知道ActiveRecord的
计数器缓存
功能,但我不想在这里使用它-
命中
是由一个单独的非Ruby系统插入的,修改该系统以更新计数器缓存会有一定的痛苦。)

如trh所述,通常的做法是在link.rb中添加一个hits_count,然后您可以快速查询和排序。你只需要让它们保持同步。如果它确实是一个基本计数,那么您可以使用基本rails计数器缓存。这将在创建和销毁时递增和递减

class AddCounterCacheToLink < ActiveRecord::Migration
  def up
    add_column :links, :hits_count, :integer, :default => 0  

    Link.reset_column_information  
    Link.all.each do |l|  
      l.update_attribute :hits_count, l.hits.size
    end 
  end
end
类AddCounterCacheToLink0 Link.reset\u列\u信息 Link.all.each do|l| l、 更新属性:点击次数,l.hits.size 结束 结束 结束
然后在模型中

class Hit < ActiveRecord::Base

  belongs_to :link, :counter_cache => true   #this will trigger the rails magic
类命中true#这将触发rails魔法

如果您有更复杂的内容,您可以编写自己的,这很简单。

正如trh所提到的,通常的方法是将点击数添加到link.rb,然后您可以快速查询和排序。你只需要让它们保持同步。如果它确实是一个基本计数,那么您可以使用基本rails计数器缓存。这将在创建和销毁时递增和递减

class AddCounterCacheToLink < ActiveRecord::Migration
  def up
    add_column :links, :hits_count, :integer, :default => 0  

    Link.reset_column_information  
    Link.all.each do |l|  
      l.update_attribute :hits_count, l.hits.size
    end 
  end
end
类AddCounterCacheToLink0 Link.reset\u列\u信息 Link.all.each do|l| l、 更新属性:点击次数,l.hits.size 结束 结束 结束
然后在模型中

class Hit < ActiveRecord::Base

  belongs_to :link, :counter_cache => true   #this will trigger the rails magic
类命中true#这将触发rails魔法

如果您有更复杂的内容,您可以编写自己的,这很简单。

您是否考虑过使用数据库视图,并向其添加hit\u count属性?在处理速度比activerecord快得多的数据库级别上。您是否考虑过使用数据库视图,并向其添加hit_count属性?在数据库级别,处理速度比activerecord级别快得多。