关于sql:必须出现在GROUP BY子句中或在聚合函数中使用

must appear in the GROUP BY clause or be used in an aggregate function

我有一张桌子,看起来像这个调用方" makerar"

1
2
3
4
5
 cname  | wmname |          avg          
--------+-------------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | luffy  | 1.00000000000000000000
 spain  | usopp  |     5.0000000000000000

我想为每个cname选择最大平均。

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

但是我会出错

1
2
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;

所以我这样做

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

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

1
2
3
4
5
 cname  | wmname |          MAX          
--------+--------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | luffy  | 1.00000000000000000000
 spain  | usopp  |     5.0000000000000000

实际结果应为

1
2
3
4
 cname  | wmname |          MAX          
--------+--------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | usopp  |     5.0000000000000000

我该如何解决这个问题?

注意:此表是根据上一个操作创建的VIEW。


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

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

1
2
3
4
5
6
7
8
9
10
11
12
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

但是您也可以使用窗口函数,它看起来更简单:

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

此方法唯一的作用是它将显示所有记录(窗口功能不分组)。但是它会在每行中显示正确的国家(即在cname级别上最高)在MAX,因此由您自己决定:

1
2
3
4
5
 cname  | wmname |          mx          
--------+--------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | luffy  |     5.0000000000000000
 spain  | usopp  |     5.0000000000000000

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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),以便支持此功能...


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

1
2
3
4
5
6
SELECT DISTINCT ON (cname)
    cname, wmname, avg
FROM
    makerar
ORDER BY
    cname, avg DESC ;


GROUP BY selects中指定非分组字段和非聚合字段的问题在于,在这种情况下,引擎无法知道应返回哪个记录的字段。首先吗最后吗?通常没有自然与聚合结果相对应的记录(minMAX是例外)。

但是,有一种解决方法:也将必填字段汇总在一起。
在posgres中,这应该起作用:

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

请注意,这会创建一个由wg排序的所有wname的数组,并返回第一个元素(postgres中的数组基于1)。


1
2
3
4
5
6
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()窗口功能:

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

注意

任一将在每个组中保留多个最大值。如果即使每条记录中只有多条记录且平均avg等于max,则应检查@ypercube的答案。


对我而言,这与"常见的聚合问题"无关,而仅与错误的SQL查询有关。唯一正确的答案是"为每个名称选择最大平均..."

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

结果将是:

1
2
3
4
 cname  |      MAX(avg)
--------+---------------------
 canada | 2.0000000000000000
 spain  | 5.0000000000000000

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

1
2
3
4
5
  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组中最好?"这个问题没有正确答案。路飞并不比usopp好,因为usopp具有相同的"得分"。


这似乎也很好

1
2
3
4
5
6
SELECT *
FROM makerar m1
WHERE m1.avg = (SELECT MAX(avg)
                FROM makerar m2
                WHERE m1.cname = m2.cname
               )


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

1
2
3
4
5
6
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

而不是使用-在后者中,我遇到了错误:苹果和橘子应该出现在集合函数中

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