【问题标题】:Rails has_many association count child rowsRails has_many 关联计数子行
【发布时间】:2010-10-22 05:19:33
【问题描述】:

什么是“rails 方式”可以有效地获取父表的所有行以及每行具有的子表的数量?

我不想使用counter_cache,因为我想根据某些时间条件运行这些计数。

陈词滥调的博客示例: 文章表。每篇文章有 0 个或多个 cmets。

我希望能够提取过去一小时、一天、一周内每篇文章有多少个 cmets。

但是,理想情况下,我不想遍历列表并对每篇文章进行单独的 sql 调用,也不想使用 :include 预取所有数据并在应用服务器上处理。

我想运行一条 SQL 语句并获得一个包含所有信息的结果集。

我知道我可以硬编码出完整的 SQL,也许可以使用 .find 并设置 :joins:group:conditions 参数...但我想知道是否有“更好”的方式...又名“Rails 方式”

【问题讨论】:

标签: ruby-on-rails grouping has-many


【解决方案1】:

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

从 Rails 的角度来看,您提到的值是计算值。因此,如果您使用find_by_sql,Model 类将不知道计算字段,因此即使您设法将查询转换为 Rails 语言,也会将计算值作为字符串返回。请参阅下面的链接问题。
一般的偏差(根据我对那个问题的回答)是让一个单独的类负责汇总/计算所需的值。

How to get rails to return SUM(columnName) attributes with right datatype instead of a string?

【讨论】:

    【解决方案2】:

    这个 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 |
    +----+----------------+------------+
    

    【讨论】:

    • 对于那些使用 PostgreSQL 的用户的快速说明,您需要为选择和组提供所有检索到的列。
    • 在 Rails 3 中,这将是 Article.select('foo').joins('bar').group('baz') 等。
    • @JaredBeck,您的行导致 Rails 对表执行 INNER JOIN,这不会返回任何具有 0 个关联 Post 行的文章行。
    • 也许我应该说select('foo').joins('left join bar')。我只是想展示新的arel 优点。
    【解决方案3】:

    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 来简化/更进一步地简化/railsify。

    【讨论】:

      【解决方案4】:

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

      在我的模型中我做了:

      class Article < ActiveRecord::Base
        has_many :posts
      
        def count_posts
          Post.where(:article_id => self.id).count
        end
      end
      

      现在,您可以使用例如:

      Articles.first.count_posts
      

      我不确定它是否可以更有效,但它是一种解决方案,在我看来比其他解决方案更优雅。

      【讨论】:

      • 当从文章/索引路由中检索文章时,这将为返回的每篇文章触发一个计数查询。如果每个计数保守地说是 0.25 毫秒,而您要求 100 篇文章,那么请求会增加 25 毫秒。因此,如果在您添加它之前请求是 5 毫秒,那么现在它是 30 毫秒 - 慢了 6 倍。优雅但不高效。
      • 正如我所说!
      • 你确实做到了——但我只是通过粗略的计算来说明它是多么低效,因此后续用户可以做出明智的选择:)
      【解决方案5】:

      我是这样完成的:

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

      在此我有 2 个模型部分和学生。所以我必须计算与特定部分 id 匹配的学生人数。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-04-29
        相关资源
        最近更新 更多