【问题标题】:How to sort Rails AR.find by number of objects in a has_many relationship如何通过 has_many 关系中的对象数量对 Rails AR.find 进行排序
【发布时间】:2010-11-02 03:16:08
【问题描述】:

如何编写 AR 查找查询以使结果按 has_many 关联中的记录数排序?

class User < ActiveRecord::Base
  has_many :photos
end

我想做一些类似...的事情

User.find(:all, :order => photos.count)

我意识到我的发现不是有效代码。假设我有以下数据。

User 1, which has 3 photos 
User 2, which has 5 photos 
User 3, which has 2 photos 

我希望我的发现按...的顺序将我带回用户。

User 2, 
User 1, 
User 3 

基于用户照片的数量

【问题讨论】:

    标签: ruby-on-rails activerecord


    【解决方案1】:

    实现这一点的最简单方法可能是向该模型添加一个计数器缓存,然后按该列排序。

    class Photo < ActiveRecord::Base
      belongs_to :user, :counter_cache => true
    end
    

    并确保在您的 users 表中添加一个名为 photos_count 的列。

    然后你就可以...

    User.find(:all, :order => 'photos_count')
    

    【讨论】:

      【解决方案2】:

      如果您不想要一个额外的列,您可以随时在返回的结果集中请求一个额外的列:

      User.all(:select => "#{User.table_name}.*, COUNT(#{Photo.table_name}.id) number_of_photos",
               :joins => :photos,
               :order => "number_of_photos")
      

      这会生成以下 SQL:

      SELECT users.*, COUNT(photos.id) number_of_photos
      FROM `users` INNER JOIN `photos` ON photos.user_id = users.id
      ORDER BY number_of_photos
      

      【讨论】:

      • 这真的是我要找的东西,我只是没有解释清楚。
      • +1 用于使用 Model.table_name,-1 用于不使用 :joins => "#{Photo.table_name.pluralize}" !
      • -1 用于预先加载所有内容,-1 用于编写您自己的 SQL 计数每个人都使用 MySQL。
      • @ChuckE -1 因为没有意识到发布日期。 -1 表示没有意识到 COUNT 至少是 SQL99 的一部分,而 column alias 也是 SQL99 的一部分。 -1 表示没有意识到查询不会预先加载任何内容。 -1 表示非建设性评论。
      • 它会进行急切加载,这是一个 .all 调用。 -1 对我来说确实没有意识到日期,+1 用于提出不再有最新答案的问题,这不是你的问题,而是堆栈溢出的问题。您可以在答案中添加一个额外的空格,以便我可以删除我的 -1 吗?
      【解决方案3】:

      如果您不想添加计数器缓存列,则唯一的选择是在查找后进行排序。如果您在查找中:include 关联,则不会产生任何额外的数据库工作。

      users = User.find(:all, :include => :photos).sort_by { |u| -u.photos.size }
      

      注意sort_by 块中的负号从高到低排序。

      【讨论】:

      • 我相信,检索并仅排序 ruby​​ 对象比在数据库级别执行排序要慢得多。
      • 哪个快要视具体情况而定。在非常繁忙的数据库服务器上,将排序卸载到应用程序服务器(假设不同的硬件)可能会更快。在这种情况下,您是对的,因为我的解决方案引入了照片记录。避免:include 并且仍然在Ruby 中进行排序会导致额外的查询,这甚至更糟。 François 接受的答案显示了如何在查询中进行排序,而无需拉入照片,这几乎可以肯定是这种情况下的最佳解决方案。
      【解决方案4】:

      我建议您不要编写直接 SQL,因为它的实现可能因商店而异。幸运的是,你有:

      User.joins(:photos).group(Photo.arel_table[:user_id]).
           order(Photo.arel_table[:user_id].count)
      

      【讨论】:

      • 我们为什么不资助这个?
      • 你知道一种方法来添加排序方向,使其升序或降序吗?
      【解决方案5】:

      Counter cache 会有所帮助,但您需要在数据库中添加一个额外的列。

      【讨论】:

        【解决方案6】:

        我会将此添加为对最佳答案的评论,但由于某种原因我不能。根据这篇文章:

        http://m.onkey.org/active-record-query-interface

        User.all(options) 方法将在 Rails 3.0.3 之后被弃用,取而代之的是一堆其他(方便的、可链接的)活动记录类型的东西,但是很难弄清楚如何将相同的类型放在一起的查询。

        因此,我继续实施了计数器缓存方法。除了您需要记住更新迁移中的列信息之外,这非常简单且轻松,否则所有现有记录都将具有“0”。

        这是我在迁移中使用的:

        class AddUserCountToCollections < ActiveRecord::Migration
          def self.up
            add_column :collections, :collectionusers_count, :integer, :default => 0
            Collection.reset_column_information
            Collection.all.each do |c| 
              Collection.update_counters c.id, :collectionusers_count => c.collectionusers.count
            end
          end
        
          def self.down
            remove_column :collections, :collectionusers_count
          end
        end
        

        理论上这也应该更快。希望这对未来有所帮助。

        【讨论】:

          【解决方案7】:

          你的问题没有意义。 :order 参数指定列名和可选的排序方向,即 asc(ending) 或 desc(ending)。

          你想要达到的结果是什么?

          【讨论】:

          • 对,我意识到我的发现不是有效代码。假设我有以下数据。用户 1,有 3 张照片 用户 2,有 5 张照片 用户 3,有 2 张照片 我希望我找到按以下顺序带回用户... 用户 2、用户 1、用户 3 基于计数的用户照片。
          • 他试图按用户照片的总和对用户进行排序
          猜你喜欢
          • 2010-10-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-12-20
          • 1970-01-01
          • 1970-01-01
          • 2011-04-14
          • 1970-01-01
          相关资源
          最近更新 更多