【问题标题】:must appear in the GROUP BY clause or be used in an aggregate function必须出现在 GROUP BY 子句中或在聚合函数中使用
【发布时间】:2013-11-05 06:52:23
【问题描述】:

我有一张看起来像这个调用者“makerar”的表

 cname  | wmname |          avg           
--------+-------------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | luffy  | 1.00000000000000000000
 spain  | usopp  |     5.0000000000000000

我想为每个 cname 选择最大 avg。

SELECT cname, wmname, MAX(avg)  FROM makerar GROUP BY cname;

但我会得到一个错误,

ERROR:  column "makerar.wmname" must appear in the GROUP BY clause or be used in an   aggregate function 
LINE 1: SELECT cname, wmname, MAX(avg)  FROM makerar GROUP BY cname;

所以我这样做

SELECT cname, wmname, MAX(avg)  FROM makerar GROUP BY cname, wmname;

但是这不会给出预期的结果,并且会显示下面的错误输出。

 cname  | wmname |          max           
--------+--------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | luffy  | 1.00000000000000000000
 spain  | usopp  |     5.0000000000000000

实际结果应该是

 cname  | wmname |          max           
--------+--------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | usopp  |     5.0000000000000000

我该如何解决这个问题?

注意:此表是根据先前操作创建的 VIEW。

【问题讨论】:

  • 我不明白。为什么是 wmname="usopp" 而不是 wmname="luffy"
  • @AndreKR 因为 (1, 5) 的最大值是 5,并且 5 与“usopp”相关联,而不是与“luffy”相关联。这是预期/期望的结果。

标签: sql group-by aggregate-functions postgresql-9.1


【解决方案1】:

是的,这是一个常见的聚合问题。在SQL3 (1999) 之前,所选字段必须出现在GROUP BY 子句[*] 中。

要解决此问题,您必须在子查询中计算聚合,然后将其与自身连接以获得您需要显示的其他列:

SELECT m.cname, m.wmname, t.mx
FROM (
    SELECT cname, MAX(avg) AS mx
    FROM makerar
    GROUP BY cname
    ) t JOIN makerar m ON m.cname = t.cname AND t.mx = m.avg
;

 cname  | wmname |          mx           
--------+--------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | usopp  |     5.0000000000000000

但你也可以使用看起来更简单的窗口函数:

SELECT cname, wmname, MAX(avg) OVER (PARTITION BY cname) AS mx
FROM makerar
;

这个方法唯一的好处是它会显示所有记录(窗口函数不分组)。但它会在每一行显示国家/地区的正确(即最大cname 级别)MAX,所以这取决于你:

 cname  | wmname |          mx           
--------+--------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | luffy  |     5.0000000000000000
 spain  | usopp  |     5.0000000000000000

显示唯一匹配最大值的 (cname, wmname) 元组的解决方案可能不太优雅,是:

SELECT DISTINCT /* distinct here matters, because maybe there are various tuples for the same max value */
    m.cname, m.wmname, t.avg AS mx
FROM (
    SELECT cname, wmname, avg, ROW_NUMBER() OVER (PARTITION BY avg DESC) AS rn 
    FROM makerar
) t JOIN makerar m ON m.cname = t.cname AND m.wmname = t.wmname AND t.rn = 1
;


 cname  | wmname |          mx           
--------+--------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | usopp  |     5.0000000000000000

[*]:有趣的是,尽管规范允许选择非分组字段,但主要引擎似乎并不喜欢它。 Oracle 和 SQLServer 根本不允许这样做。 Mysql 过去默认允许,但现在从 5.7 开始管理员需要在服务器配置中手动启用此选项 (ONLY_FULL_GROUP_BY) 才能支持此功能...

【讨论】:

  • 谢谢语法正确,但是加入时必须比较mx和avg的值
  • 是的,你的语法是正确的并且消除了重复,但是你需要 m.avg=t.mx 最后(在你写 JOING 之后)来获得预期的结果
  • @Sebas 它可以在不加入MAX 的情况下完成(请参阅@ypercube 的回答,我的回答中还有另一个解决方案)但不是你这样做的方式。检查预期输出。
  • @Sebas 您的解决方案仅添加一列(MAX avg per cname),但它不限制结果的行(如 OP 所愿)。请参阅问题中的实际结果应为段。
  • 在 MySQL 5.7 中关闭 off ONLY_FULL_GROUP_BY 不会激活 SQL 标准指定何时可以从 group by 中省略列的方式(或使 MySQL 的行为类似于 Postgres) .它只是恢复到 MySQL 返回随机(=“不确定”)结果的旧行为。
【解决方案2】:

在 Postgres 中,您还可以使用特殊的 DISTINCT ON (expression) 语法:

SELECT DISTINCT ON (cname) 
    cname, wmname, avg
FROM 
    makerar 
ORDER BY 
    cname, avg DESC ;

【讨论】:

  • 如果想要对 avg 之类的列进行排序,它将无法按预期工作
  • @amenzhinsky 你是什么意思?如果希望以不同于BY cname 的顺序对结果集进行排序?
  • @ypercube,实际上 psql 先排序,然后再应用 DISTINCT。在按 avg 排序的情况下,我们将根据排序方向为每一行的最小值和最大值得到不同的结果
  • 当然。如果你不运行我发布的查询,你会得到不同的结果!这与“它不会按预期工作”不同......
  • @Batfan thnx。请注意,虽然这非常酷、紧凑且易于编写,但它通常不是此类查询的最有效方式。
【解决方案3】:

group by 选择中指定非分组和非聚合字段的问题是引擎无法知道在这种情况下它应该返回哪个记录的字段。是第一吗?是最后吗?通常没有与聚合结果自然对应的记录(minmax 除外)。

但是,有一种解决方法:将必填字段也进行聚合。 在 postgres 中,这应该可以工作:

SELECT cname, (array_agg(wmname ORDER BY avg DESC))[1], MAX(avg)
FROM makerar GROUP BY cname;

请注意,这会创建一个包含所有 wname 的数组,按 avg 排序,并返回第一个元素(postgres 中的数组是从 1 开始的)。

【讨论】:

  • 好点。尽管数据库似乎有可能进行外连接,以将每行的非聚合字段链接到该行贡献的聚合结果。我经常很好奇为什么他们没有选择。虽然我可能根本不知道这个选项:)
  • 这是 pgsql 的不错解决方案,但是任何 mysql 等价物?我不喜欢多选方式。
  • 很好地使用array_agg 我不知道你可以在参数中直接排序
【解决方案4】:

对我来说,这不是一个“常见的聚合问题”,而是一个不正确的 SQL 查询。 “选择每个 cname 的最大平均值...”的唯一正确答案是

SELECT cname, MAX(avg) FROM makerar GROUP BY cname;

结果将是:

 cname  |      MAX(avg)
--------+---------------------
 canada | 2.0000000000000000
 spain  | 5.0000000000000000

这个结果通常回答了问题“每个组的最佳结果是什么?”。我们看到西班牙的最佳结果是 5,而加拿大的最佳结果是 2。这是真的,而且没有错误。 如果我们还需要显示 wmname,我们必须回答以下问题:“从结果集中选择 wmname 的 RULE 是什么?”让我们稍微改变一下输入数据以澄清错误:

  cname | wmname |        avg           
--------+--------+-----------------------
 spain  | zoro   |  1.0000000000000000
 spain  | luffy  |  5.0000000000000000
 spain  | usopp  |  5.0000000000000000

在运行此查询时您期望得到什么结果:SELECT cname, wmname, MAX(avg) FROM makerar GROUP BY cname;?应该是spain+luffy 还是spain+usopp?为什么?查询中没有确定如果有几个合适的话,如何选择“更好的”wmname,所以结果也没有确定。这就是 SQL 解释器返回错误的原因 - 查询不正确。

换句话说,对于spain 组中谁是最好的?”这个问题没有正确答案。路飞不比乌索普强,因为乌索普的“分数”是一样的。

【讨论】:

  • 这个解决方案也对我有用。我遇到了查询问题,因为我的 ORM 还包含关联的主键,导致以下 incorrect 查询:SELECT cname, id, MAX(avg) FROM makerar GROUP BY cname;,它确实给出了这个误导性错误。
【解决方案5】:
SELECT t1.cname, t1.wmname, t2.max
FROM makerar t1 JOIN (
    SELECT cname, MAX(avg) max
    FROM makerar
    GROUP BY cname ) t2
ON t1.cname = t2.cname AND t1.avg = t2.max;

使用rank()window function

SELECT cname, wmname, avg
FROM (
    SELECT cname, wmname, avg, rank() 
    OVER (PARTITION BY cname ORDER BY avg DESC)
    FROM makerar) t
WHERE rank = 1;

注意

任何一个都会为每组保留多个最大值。如果您希望每组只有一条记录,即使有不止一条平均等于最大值的记录,您也应该检查@ypercube 的答案。

【讨论】:

    【解决方案6】:

    这似乎也有效

    SELECT *
    FROM makerar m1
    WHERE m1.avg = (SELECT MAX(avg)
                    FROM makerar m2
                    WHERE m1.cname = m2.cname
                   )
    

    【讨论】:

      【解决方案7】:

      我最近在尝试使用case when 计数时遇到了这个问题,发现更改whichcount 语句的顺序可以解决问题:

      SELECT date(dateday) as pick_day,
      COUNT(CASE WHEN (apples = 'TRUE' OR oranges 'TRUE') THEN fruit END)  AS fruit_counter
      
      FROM pickings
      
      GROUP BY 1
      

      而不是在后者中使用 -,我得到了苹果和橙子应该出现在聚合函数中的错误

      CASE WHEN ((apples = 'TRUE' OR oranges 'TRUE') THEN COUNT(*) END) END AS fruit_counter
      

      【讨论】:

      • which 声明?
      猜你喜欢
      • 2013-08-06
      • 2015-08-14
      • 2018-05-16
      • 1970-01-01
      • 1970-01-01
      • 2021-06-25
      • 2013-04-24
      相关资源
      最近更新 更多