Rails has_many关联计算子行数

29
什么是高效地获取父表所有行以及每行子表数量的“Rails Way”?
我不想使用 `counter_cache`,因为我想基于某些时间条件运行这些计数。
典型的博客示例:文章表。每篇文章有0个或多个评论。
我想能够获取每篇文章在过去一小时、一天、一周内有多少评论。
理想情况下,我不想遍历列表并为每篇文章做单独的SQL查询,也不想使用 `:include` 预取所有数据并在应用程序服务器上处理它们。
我想运行一个SQL语句并获得包含所有信息的一个结果集。
我知道我可以硬编码完整的SQL,也许可以使用 `.find` 并设置 `:joins`、`:group`和 `:conditions` 参数... 但我想知道是否有更好的方法... 即“Rails Way”。

这里是另一个答案。 - Arup Rakshit
5个回答

29

这个 activerecord 调用应该可以满足您的需求:

Article.find(:all, :select => 'articles.*, count(posts.id) as post_count',
             :joins => 'left outer join posts on posts.article_id = articles.id',
             :group => 'articles.id'
            )
这将返回一个文章对象列表,每个对象都包含方法post_count,其中包含文章上的帖子数量字符串。
该方法执行类似于以下的 SQL 语句:
SELECT articles.*, count(posts.id) AS post_count
FROM `articles`
LEFT OUTER JOIN posts ON posts.article_id = articles.id
GROUP BY articles.id

如果你感到好奇,这是运行此类查询可能看到的MySQL结果的示例:

+----+----------------+------------+
| id | text           | post_count |
+----+----------------+------------+
|  1 | TEXT TEXT TEXT |          1 |
|  2 | TEXT TEXT TEXT |          3 |
|  3 | TEXT TEXT TEXT |          0 |
+----+----------------+------------+

6
针对那些使用PostgreSQL的用户,需要在select和group语句中提供所有检索到的列。 - Kevin Ansfield
2
在Rails 3中,这将是Article.select('foo').joins('bar').group('baz')等。 - Jared Beck
1
@JaredBeck,你的代码会导致Rails在表上执行INNER JOIN操作,这将不会返回任何具有0个关联Post行的Article行。 - user1158559
也许我应该说 select('foo').joins('left join bar')。我只是想展示一下新的 arel 特性。 - Jared Beck

10

Rails 3 版本

对于 Rails 3,你需要看一下类似这样的内容:

Article.select("articles.*, count(comments.id) AS comments_count")
  .joins("LEFT OUTER JOIN comments ON comments.article_id = articles.id")
  .group("articles.id")

感谢 Gdeglin 提供 Rails 2 版本的代码。

Rails 5 版本

自从 Rails 5 开始,出现了left_outer_joins方法,您可以简化为:

Article.select("articles.*, count(comments.id) AS comments_count")
  .left_outer_joins(:comments)
  .group("articles.id")

因为你询问了Rails的方法: 使用ActiveRecord不能更简化/使其符合Rails风格。


4

我用来解决这个问题的简单方法是:

在我的模型中,我做了以下操作:

class Article < ActiveRecord::Base
  has_many :posts

  def count_posts
    Post.where(:article_id => self.id).count
  end
end

现在,您可以使用如下代码示例:
Articles.first.count_posts

我不确定是否有更高效的方法,但这是一个解决方案。在我看来,它比其他方案更加优雅。


3
从 articles/index 路由检索文章时,每篇文章返回一个计数查询。如果每个计数查询需要0.25毫秒,而且你要求100篇文章,那么这将为请求添加25毫秒。如果在添加此功能之前请求时间为5毫秒,现在请求时间将变成30毫秒,慢了6倍。虽然这种方法优雅,但不够高效。 - rmcsharry
就像我所说的一样! - JonatasTeixeira
你确实这样做了 - 但我只是通过粗略的计算来说明它有多么低效,以便后续用户可以做出知情选择 :) - rmcsharry

4

从SQL的角度来看,这似乎很简单 - 只需编写一个新查询。

从Rails的角度来看,您提到的值是计算出来的值。因此,如果使用find_by_sql,模型类将不知道计算字段,即使您成功将查询转换为Rails语言,它也会返回计算值作为字符串。请参见下面链接的问题。
(从我得到的回复中)总体趋势是让一个单独的类负责汇总/计算所需的值。

如何让Rails返回正确数据类型的SUM(columnName)属性,而不是字符串?


0

我是这样完成这项工作的:

def show
  section = Section.find(params[:id])
  students = Student.where( :section_id => section.id ).count
  render json: {status: 'SUCCESS', section: students},status: :ok
end

在这个项目中,我有两个模型:Section和Student。因此,我需要计算与特定部分ID匹配的学生数量。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接