SELECT 子句中不存在聚合函数时的 GROUP BY 行为

Posted

技术标签:

【中文标题】SELECT 子句中不存在聚合函数时的 GROUP BY 行为【英文标题】:GROUP BY behavior when no aggregate functions are present in the SELECT clause 【发布时间】:2010-12-08 04:39:04 【问题描述】:

我有一个表emp,其结构和数据如下:

name   dept    salary
-----  -----   -----
Jack   a       2
Jill   a       1
Tom    b       2
Fred   b       1

当我执行以下 SQL 时:

SELECT * FROM emp GROUP BY dept

我得到以下结果:

name   dept    salary
-----  -----   -----
Jill   a       1
Fred   b       1

服务器根据什么决定返回 Jill 和 Fred 并排除 Jack 和 Tom?

我正在 mysql 中运行此查询。

注意 1:我知道查询本身没有意义。我正在尝试调试“GROUP BY”场景的问题。我正在尝试了解此目的的默认行为。

注意 2:我习惯于编写与 GROUP BY 子句相同的 SELECT 子句(减去聚合字段)。当我遇到上述行为时,我开始想知道是否可以在以下情况下依赖它: 从 emp 表中选择工资在部门中最低/最高的行。 例如:这样的 SQL 语句适用于 MySQL:

SELECT A.*, MIN(A.salary) AS min_salary FROM emp AS A GROUP BY A.dept

我没有找到任何描述这种 SQL 为何有效的材料,更重要的是,如果我能始终如一地依赖这种行为。如果这是一个可靠的行为,那么我可以避免这样的查询:

SELECT A.* FROM emp AS A WHERE A.salary = ( 
            SELECT MAX(B.salary) FROM emp B WHERE B.dept = A.dept)

【问题讨论】:

能否请您发布您希望的结果。 GROUP BY 为 GROUP BY 列的每个唯一组合返回一行。由于您仅指定了一列 dept,因此每个部门将仅返回一行。如果您从查询中列出您需要的内容,人们可能会提供更多帮助。 告诉我们您的期望,我们将能够为您提供帮助。 我的猜测是每组的最后一行 请注意,在基于 MVCC 的数据库上,行顺序会重新洗牌,如果您更新第一行,第一行可以成为最后一行 我在问题的“备注”部分提供了其他信息。 【参考方案1】:

请阅读MySQL documentation 了解这一点。

简而言之,MySQL 允许从 GROUP BY 中省略一些列,以提高性能,但是这仅在 如果省略的列都具有相同的值(在分组),否则,查询返回的值确实是不确定的,正如本文中其他人正确猜测的那样。为确保添加 ORDER BY 子句不会重新引入任何形式的确定性行为。

虽然不是问题的核心,但此示例显示了使用 * 而不是显式枚举所需列通常是一个坏主意。

摘自 MySQL 5.0 文档:

使用此功能时,每组中的所有行都应具有相同的值 对于 GROUP BY 部分中省略的列。服务器是免费的 从组中返回任何值,因此结果是不确定的,除非 所有值都相同。

【讨论】:

@mjv,我正在考虑用户声明select user_id, order_id from (select user_id, order_id from orders order by user_id, order_id desc) a group by user_id 来选择每个user_id 的最新订单,这不会达到我的目的吗?【参考方案2】:

这有点晚了,但我会把它放上来供以后参考。

GROUP BY 获取第一行重复的行,并丢弃结果集中在它之后匹配的所有行。因此,如果 Jack 和 Tom 拥有相同的部门,那么在普通 SELECT 中首先出现的将是 GROUP BY 中的结果行。

如果您想控制列表中最先出现的内容,您需要执行 ORDER BY。但是,SQL 不允许 ORDER BY 出现在 GROUP BY 之前,因为它会抛出异常。此问题的最佳解决方法是在子查询中执行 ORDER BY,然后在外部查询中执行 GROUP BY。这是一个例子:

SELECT * FROM (SELECT * FROM emp ORDER BY name) as foo GROUP BY dept

这是我发现的表现最好的技术。我希望这对某人有所帮助。

【讨论】:

感谢您的帮助 - 非常有帮助。昂贵的子选择,但它似乎是做我希望“HAVING”做的唯一方法。 我找不到任何引用来支持您的断言,即“GROUP BY 采用具有重复项的第一行并丢弃结果集中在它之后匹配的任何行。”相反,MySQL 特别明确指出非聚合列的值是从组中的任何行中任意获取的。 没有有用。我认为这会使情况恶化。代替一项非标准功能,您现在使用两项非标准功能。结果可能是错误的,并且不能保证您将获得每个部门的第一个结果,按名称排序。 小心这个答案是不正确。它看起来应该可以工作,如果它可以工作,那就太好了,但它不起作用...... 我同意,这不起作用。它有时会起作用,但它们的值是不确定的。我的测试随机失败,问题在于使用了上述技术。【参考方案3】:

据我所知,出于您的目的,返回的特定行可以被认为是随机的。

只有在GROUP BY 完成后才能订购

【讨论】:

【参考方案4】:

你可以放一个:

SET GLOBAL sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));

在查询之前强制执行 SQL 标准 GROUP BY 行为

【讨论】:

【参考方案5】:

我发现最好的办法是考虑不支持这种类型的查询。在大多数其他数据库系统中,不能包含不在 GROUP BY 子句或 HAVING、SELECT 或 ORDER BY 子句中的聚合函数中的列。

相反,请考虑您的查询内容为:

SELECT ANY(name), dept, ANY(salary)
FROM emp 
GROUP BY dept;

...因为这是正在发生的事情。

希望这会有所帮助....

【讨论】:

【参考方案6】:

我认为 ANSI SQL 要求选择只包括来自 GROUP BY 子句的字段,以及聚合函数。 MySQL 的这种行为看起来像是返回了一些行,可能是服务器读取的最后一行,或者它手头的任何行,但不要依赖它。

【讨论】:

关于 Marius 评论:(由于评分低,我无法评论)正如其他人所说,Order By 作用于 Grouping 的结果,对将折叠的行进行排序没有意义通过一个分组。相反,您可以选择 MAX(name),如果行按字母升序排列,它实际上会返回姓氏。【参考方案7】:

这将为每个人选择最近的一行:

SELECT * FROM emp
WHERE ID IN
(
    SELECT
        MAX(ID) AS ID
    FROM
        emp
    GROUP BY
        name
)

【讨论】:

【参考方案8】:

如果您按部门分组,其他数据是否重要?我知道 Sql Server 甚至不允许这个查询。如果有这种可能性,听起来可能还有其他问题。

【讨论】:

我知道这条 SQL 在 Oracle 和少数其他数据库中无效。【参考方案9】:

尝试使用 ORDER BY 来选择您想要的行。

SELECT * FROM emp GROUP BY dept ORDER BY name ASC;

将返回以下内容:

name   dept    salary
-----  -----   -----
jack   a       2
fred   b       1

【讨论】:

在我的情况下 ORDER BY 没有区别。我预计这种行为是因为 ORDER BY 在 GROUP BY 之后应用。

以上是关于SELECT 子句中不存在聚合函数时的 GROUP BY 行为的主要内容,如果未能解决你的问题,请参考以下文章

在 SELECT 中不使用聚合函数时用 SELECT DISTINCT 替换 GROUP BY

google bigquery SQL group by 聚合函数

GROUP BY 子句必须与聚合函数一起使用?

GROUP BY子句

GROUP BY子句

分组函数 ----group by 使用总结