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


276

我有一张桌子,看起来像这个呼叫者“ makerar”

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

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

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

Answers:


226

是的,这是一个常见的聚合问题。在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在服务器配置中手动启用此选项(),以支持此功能...


1
感谢语法是corect,但是加入时必须比较mx和avg的值
RandomGuy 2013年

1
是的,您的语法是正确的,并且消除了重复项,但是最终(在您编写JOING之后)需要m.avg = t.mx才能获得预期的结果
RandomGuy 2013年

1
@Sebas可以不加入而完成MAX(请参阅@ypercube的答案,我的答案中还有另一种解决方案),但不是您的方式。检查预期输出。
2013年

1
@Sebas您的解决方案只增加了一个列(MAX avgcname),但它不限制结果行(如OP希望)。请参阅“ 实际结果”中的问题段落。
ypercubeᵀᴹ

1
谈到关闭 ONLY_FULL_GROUP_BY在MySQL 5.7不激活的方式,SQL标准规定当列可以从被忽略group by(或使MySQL的行为很像Postgres的)。它只是恢复到原来的行为,即MySQL返回随机(=“ indeterminate”)结果。
a_horse_with_no_name

126

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

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

5
如果要对像avg这样的列进行排序,它将无法正常工作
amenzhinsky 2014年

@amenzhinsky是什么意思?如果要让结果集以不同于BY cname?的顺序排序?
ypercubeᵀᴹ

@ypercube,实际上psql首先排序,然后应用DISTINCT。如果按avg进行排序,则根据排序方向,每一行的最小值和最大值将得到不同的结果
amenzhinsky 2014年

3
当然。如果您不运行我发布的查询,则会得到不同的结果!这与“它将无法按预期运行”不同...
ypercubeᵀᴹ

1
@Batfan thnx。请注意,尽管这非常酷,紧凑且易于编写,但对于这种查询而言,这通常不是最有效的方法。
ypercubeᵀᴹ

27

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

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

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

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


好点子。尽管DB似乎可以进行外部联接,以将每行中的非聚合字段链接到该行所贡献的聚合结果。我经常好奇为什么他们没有选择的余地。尽管我可能根本不知道该选项:)
本·西蒙斯

16
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() 窗口功能

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的答案。


16

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

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

结果将是:

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

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

  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具有相同的“得分”。


这个解决方案也对我有用。我遇到了查询问题,因为我的ORM还包括了相关的主键,导致了以下错误查询:SELECT cname, id, MAX(avg) FROM makerar GROUP BY cname;,确实出现了这种误导性错误。
罗伯托

1

这似乎也很好

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

0

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

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

1
which声明?
希拉里·桑德斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.