【问题标题】:Rails 3: .group() ordered by created_atRails 3:.group() 由 created_at 排序
【发布时间】:2011-12-22 03:18:35
【问题描述】:

Rails 3 对 Activerecord 中的 .group() 结果进行排序的方式是什么(此处为“created_at”)?

@messages = Message.group(:foo)

只会显示最旧的消息。我需要显示最新的。

我试过了

@messages = Message.group(:foo).having("created_at = MAX(created_at)")

没有成功。任何提示表示赞赏!

澄清一下:我希望该组在其内部进行排序,而不是正常的messages.order("...")。 如果没有简单的 Activerecord 语法,我也会对原始 SQL 感到满意


更新:尝试 SQL 方式,这应该可以工作:

@messages = Message.find_by_sql("
  SELECT messages.* 
  FROM messages 
  GROUP BY messages.foo 
  HAVING messages.created_at = MAX(messages.created_at) 
  ORDER BY messages.created_at DESC")

但这仅检索单个记录(未分组的记录)。据说分组的被省略了。不知道为什么,所有记录都有 :created_at 和 :foo 值

【问题讨论】:

    标签: ruby-on-rails-3 activerecord group-by sql-order-by


    【解决方案1】:

    在某些数据库下,你不能在同一个查询中使用 order then group,所以你需要使用两个查询:

    message_ids = Message.select("MAX(id) AS id").group(:foo).collect(&:id)
    @messages = Message.order("created_at DESC").where(:id => message_ids)
    

    注意:这假设您有一个自动递增的 id 列,大多数 Rails 表都这样做。

    【讨论】:

      【解决方案2】:

      你可以使用 SQL,但你也可以大部分时间留在 activerecord 领域

      @messages = Message.select('DISTINCT ON (foo) *').order(:created_at).reverse_order
      

      DISTINCT ON () 语法是 postgres。 MYSQL 中有类似但略有不同的语法。

      我认为这个查询最终也会比接受的答案更有效,因为它只是一次排序和扫描,而不是 N 次排序。

      【讨论】:

      • 谢谢你。我试图获得一个有序且唯一的结果列表,并且不得不更改 Postgresql 上的顺序: Deadline.select('DISTINCT ON (client_id) *').order(:client_id, :reminder_date)
      【解决方案3】:

      通过使用更专用的子查询,无需 GROUP BY 调用即可完成所有工作:

      SELECT *
         FROM `messages`
         WHERE `id` = (
            SELECT `id`
            FROM `messages` as `alt`
            WHERE `alt`.`foo` = `messages`.`foo`
            ORDER BY `created_at` DESC
            LIMIT 1
         )
         ORDER BY `created_at` DESC
      

      感谢 Chaos 在此线程中的回答:Select the 3 most recent records where the values of one column are distinct

      虽然还不知道任何速度影响。如果有人想介入,请随时这样做。

      【讨论】:

      • 我建议您现在考虑使用 :conditions 等将其转换为“rails”查询。这是“按预期”使用 rails 的一部分,并通过一个像这样的相当简单的示例来掌握它做一个很棒的锻炼。编写 find_by_sql (我做了很长一段时间,因为我的背景是 sql 数据库)是不使用 rails 结构的一部分,往往会导致快速结果,但它们是硬编码的并且通常是特定于供应商的(这也意味着特定于开发人员..)和不易维护。
      • 谢谢迈克尔,我同意你的看法。你手头有这个查询的例子吗?
      【解决方案4】:

      我刚刚遇到了同样的问题。以下 Rails 查询对我有用:

      Message.where("id IN (SELECT MAX(id) FROM messages GROUP BY id) AND state = 'unread'")
      

      在这里,我们使用子查询来获取每个组中最大(因此也是最新的)ID,然后过滤这些以仅显示 state == 'unread' 的 ID。

      提示:我在 Message 模型类中创建了一个 self.latest 方法,其中包括:

      def self.latest
        where("id IN (SELECT MAX(id) FROM messages GROUP BY id)
      end
      

      这意味着我可以在这样的控制器中使用它:

      Message.latest.where(state: 'unread')
      

      【讨论】:

        【解决方案5】:

        我不知道如何轻松地使用 Arel 完成此操作 - 但如果您只是像在您的问题中那样构建自己的 SQL,那么将其修改如下:

        @messages = Message.find_by_sql("
          SELECT messages.* 
          FROM messages 
          GROUP BY messages.foo 
          HAVING messages.created_at = (SELECT max(created_at) FROM messages AS sub_messages WHERE sub_messages.foo = messages.foo)
        

        注意 HAVING 子句中的子查询。您的 HAVING 子句计算了所有消息的 MAX - 而不是仅 foo 中的消息。子查询应该可以解决这个问题。

        我将消息表名称别名为 sub_messages - 但这不是必需的。我只是为了“展示”父查询和子查询如何协同工作。

        【讨论】:

        • 谢谢乔尔格。尝试了您的建议,但查询没有为我返回任何记录。我认为它看起来非常合理的查询!所以我想弄清楚为什么会这样?
        • 刚刚用一条消息对此进行了测试,显示正确。但是一旦有多个(所以 GROUP 有效?),都找不到。有什么想法吗?
        猜你喜欢
        • 1970-01-01
        • 2014-07-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-24
        相关资源
        最近更新 更多