【问题标题】:Rails 3.1 with PostgreSQL: GROUP BY must be used in an aggregate function带有 PostgreSQL 的 Rails 3.1:GROUP BY 必须在聚合函数中使用
【发布时间】:2011-10-20 16:15:19
【问题描述】:

我正在尝试加载按 user_id 分组并按 created_at 排序的最新 10 个艺术。这适用于 SqlLite 和 MySQL,但在我的新 PostgreSQL 数据库上出现错误。

Art.all(:order => "created_at desc", :limit => 10, :group => "user_id")

ActiveRecord 错误:

Art Load (18.4ms)  SELECT "arts".* FROM "arts" GROUP BY user_id ORDER BY created_at desc LIMIT 10
ActiveRecord::StatementInvalid: PGError: ERROR:  column "arts.id" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: SELECT  "arts".* FROM "arts"  GROUP BY user_id ORDER BY crea...

有什么想法吗?

【问题讨论】:

    标签: ruby-on-rails ruby postgresql


    【解决方案1】:

    表达式生成的 sql 不是有效查询,您按 user_id 分组并基于此选择许多其他字段,但没有告诉数据库它应该如何聚合其他字段。例如,如果您的数据如下所示:

    a  | b
    ---|---
    1  | 1
    1  | 2
    2  | 3
    

    现在,当您要求 db 按a 分组并返回 b 时,它不知道如何聚合值 1,2。您需要判断它是否需要选择最小值、最大值、平均值、总和或其他内容。正如我正在写答案一样,有两个答案可能会更好地解释这一切。

    不过,在您的用例中,我认为您不希望在数据库级别上进行分组。由于只有 10 种艺术,您可以将它们分组到您的应用程序中。但是,请勿将此方法用于数千种艺术:

     arts = Art.all(:order => "created_at desc", :limit => 10)
     grouped_arts = arts.group_by {|art| art.user_id}
     # now you have a hash with following structure in grouped_arts
     # { 
     #    user_id1 => [art1, art4],
     #    user_id2 => [art3],
     #    user_id3 => [art5],
     #    ....
     # }
    

    编辑:选择 latest_arts,但每个用户只能选择一种艺术

    只是给你sql的想法(没有测试过,因为我的系统上没有安装RDBMS)

    SELECT arts.* FROM arts
    WHERE (arts.user_id, arts.created_at) IN 
      (SELECT user_id, MAX(created_at) FROM arts
         GROUP BY user_id
         ORDER BY MAX(created_at) DESC
         LIMIT 10)
    ORDER BY created_at DESC
    LIMIT 10
    

    此解决方案基于实际假设,即同一用户的两个艺术不能具有相同的最高 created_at,但如果您正在导入或以编程方式创建大量艺术,则很可能是错误的。如果假设不成立,则 sql 可能会变得更加人为。

    编辑:尝试将查询更改为 Arel:

    Art.where("(arts.user_id, arts.created_at) IN 
                 (SELECT user_id, MAX(created_at) FROM arts
                    GROUP BY user_id
                    ORDER BY MAX(created_at) DESC
                    LIMIT 10)").
        order("created_at DESC").
        page(params[:page]).
        per(params[:per])
    

    【讨论】:

    • 好吧,我有 6000 条记录,所以这可能会成为性能问题。我看到您有两个 user_id1 记录。我正在尝试加载最新的 10 个艺术作品 - 只有一个 pr。用户。
    • 这不会是性能问题,直到您的限制子句为 10,但第二个问题仍然存在。我想可以有原始 sql 来处理你想要的,但是将它转换为 Arel 可能很困难。
    • 更新了答案,但不确定它的语法是否正确。让我知道这是否有效。
    • 您的示例有效。我在让它与分页一起玩时遇到了很多麻烦。这可以用 Active Record 来完成,这样你就可以调用 .page(params[:page]).per(20) 等方法。
    • 已尝试将其转换为 arel 语法,但此查询很难转换。
    【解决方案2】:

    您需要选择您需要的特定列

    Art.select(:user_id).group(:user_id).limit(10)

    例如在查询中选择标题时会报错

    Art.select(:user_id, :title).group(:user_id).limit(10)

    列“arts.title”必须出现在 GROUP BY 子句中或用于聚合函数中

    那是因为当你尝试按 user_id 分组时,查询不知道如何处理组中的标题,因为组包含多个标题。

    所以例外已经提到你需要出现在分组中

    Art.select(:user_id, :title).group(:user_id, :title).limit(10)

    或用于聚合函数

    Art.select("user_id, array_agg(title) 作为标题").group(:user_id).limit(10)

    【讨论】:

      【解决方案3】:

      看看这个帖子SQLite to Postgres (Heroku) GROUP BY

      PostGres 在这里实际上遵循 SQL 标准,而 sqlite 和 mysql 打破了标准。

      【讨论】:

        【解决方案4】:

        看看这个问题 - Converting MySQL select to PostgreSQL。 Postgres 不允许在 select 语句中列出不在 group by 子句中的列。

        【讨论】:

          猜你喜欢
          • 2013-08-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-05-19
          • 2020-11-04
          相关资源
          最近更新 更多