我的Rails 4应用拥有User
型号,Link
型号和Hit
型号。每个User
都有很多Link
个,每个Link
都有很多Hit
个。有时,我希望显示User
的{{1}}个列表,其中包含Link
个。{/ p>
显而易见的方法是循环链接并在每个链接上调用Hit
,但这会产生N + 1个查询。相反,我编写了一个连接link.hits.count
表的范围:
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")
}
添加了一个虚拟hit_count
属性,该属性在单个查询中计算。奇怪的是,它似乎是一个单独的查询加载链接,而不是实际在同一个查询中完成:
Link
不幸的是,随着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 ;
表的增长,这已成为一个缓慢的查询。 hits
表示查询正在使用索引连接所有匹配及其匹配链接,然后通过顺序扫描将链接缩小到具有正确EXPLAIN
的链接;这似乎是它缓慢的原因。但是,如果我们已经单独加载链接列表 - 我们就是 - 根本没有实际需要加入链接表。我们可以获取用户的链接ID列表,然后仅使用user_id
在hits表上进行查询。
将此作为单独的查询编写很容易,并且它可以快速运行:
hits.link_id IN (list of IDs)
问题是,我无法弄清楚如何让ActiveRecord作为Hit.where(link_id: @user.links.ids).group(:link_id).count
模型上的范围执行此操作,以便每个Link
都具有Link
属性,我可以使用,以便我可以使用生成的返回值作为将其他查询链接到它的能力的关系。有什么想法吗?
(我知道ActiveRecord的hit_count
功能,但我不想在这里使用它 - counter_cache
由一个单独的非Ruby系统插入,并修改该系统以更新反缓存会有点痛苦。)
答案 0 :(得分:0)
正如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
然后在模型中
class Hit < ActiveRecord::Base
belongs_to :link, :counter_cache => true #this will trigger the rails magic
如果你有更复杂的东西,你可以写自己的,这是微不足道的。