如何让Rails获得急切的负载计数?

这与一年前的一个问题有关。

我提出了一个开箱即用的问题示例,只要你有sqlite3可用: https : //github.com/cairo140/rails-eager-loading-counts-demo

安装说明(主分支)

git clone git://github.com/cairo140/rails-eager-loading-counts-demo.git cd rails-eager-loading-counts-demo rails s 

我在存储库中有更全面的文章,但我的一般问题是这个。

如何以最小化数据库查询的方式使Rails急切加载计数?

只要在关联上使用#count ,就会出现n+1问题,尽管在ActiveRelation中通过#includes(:associated)包含了该关联。 一个解决方法是使用#length ,但只有当它被调用的对象已被加载时才能正常工作,更不用说我怀疑它复制了Rails内部已经完成的东西。 此外,使用#length一个问题是,当开始时没有加载关联并且只需要计数时,它会导致不幸的过载。

从自述文件:

我们可以通过在posts数组上运行#length来避免这个问题(参见附录),这已经加载了,但是也可以随时获得数量。 它不仅更加一致; 它提供了一种访问路径,不一定需要加载post。 例如,如果你有一个部分显示计数,无论什么,但有一半的时间,部分是在加载post和一半时间没有调用,你面临以下情况:

  • 使用#count
    • n已加载post时的COUNT样式查询
    • n尚未加载post时的COUNT样式查询
  • 使用#length
    • 在已加载post时将其他查询归零
    • 当post尚未加载时,n *样式查询

在这两种选择之间,没有主导选择。 但是修改#count来推迟#length或访问存储在幕后的其他方式的长度会很好,这样我们就可以得到以下场景:

  • 使用修改后的#count
    • 在已加载post时将其他查询归零
    • n尚未加载post时的COUNT样式查询

那么这里的正确方法是什么? 有没有我忽略的东西(非常非常可能)?

看来,实现这种设施的最佳方式可能是为您想要的单独的模型和子计数对象创建SQL视图(参考: 此处和此处 ); 及其相关的ActiveRecord模型。

您可能非常聪明,并且在原始模型上结合set_table_name :sql_view_name使用子类来保留对象上的所有原始方法,甚至可能保留一些关联。

例如,假设我们要在你的例子中添加“Post.has_many:comments”,就像上面的@Zubin的回答一样; 然后人们可以做到:

  class CreatePostsWithCommentsCountsView < ActiveRecord::Migration def self.up #Create SQL View called posts_with_comments_counts which maps over # select posts.*, count(comments.id) as comments_count from posts # left outer join comments on comments.post_id = posts.id # group by posts.id # (As zubin pointed out above.) #*Except* this is in SQL so perhaps we'll be able to do further # reducing queries against it *as though it were any other table.* end end class PostWithCommentsCount < Post #Here there be cleverness. #The class definition sets up PWCC # with all the regular methods of # Post (pointing to the posts table # due to Rails' STI facility.) set_table_name :posts_with_comment_counts #But then we point it to the # SQL view instead. #If you don't really care about # the methods of Post being in PWCC # then you could just make it a # normal subclass of AR::Base. end PostWithCommentsCount.all(:include => :user) #Obviously, this sort of "upward # looking" include is best used in big lists like "latest posts" rather than # "These posts for this user." But hopefully it illustrates the improved # activerecordiness of this style of solution. PostWithCommentsCount.all(:include => :comments) #And I'm pretty sure you # should be able to do this without issue as well. And it _should_ only be # the two queries. 

正如@apneadiving建议的那样,counter_cache运行良好,因为在添加或删除记录时,计数器列会自动更新。 因此,当您加载父对象时,计数将包含在对象中,而无需访问其他表。

但是,如果由于某种原因你不喜欢这种方法,你可以这样做:

 Post.find(:all, :select => "posts.*, count(comments.id) `comments_count`", :joins => "left join comments on comments.post_id = posts.id") 

我已经设置了一个小的gem,它将一个includes_count方法添加到ActiveRecord,它使用SELECT COUNT来获取关联中的记录数,而不需要使用可能很昂贵的JOIN(取决于具体情况)。

请参阅https://github.com/manastech/includes-count

希望能帮助到你!

Zubin的另一种方法:

 Post.select('posts.*, count(comments.id) `comments_count`').joins(:comments).group('posts.id')