SQL vs MySQL:有关聚合操作和GROUP BY的规则

pha*_*haz 10 mysql sql group-by aggregate-functions

本书中我正在阅读关于数据库的课程,下面给出了使用聚合运算符的非法查询示例:

找出最老的水手的名字和年龄.

请考虑以下尝试回答此查询:

SELECT S.sname, MAX(S.age)
FROM Sailors S
Run Code Online (Sandbox Code Playgroud)

目的是使该查询不仅返回最大年龄,还返回具有该年龄的水手的姓名.但是,此查询在SQL中是非法的 - 如果SELECT子句使用聚合操作,则它必须使用聚合操作,除非查询包含GROUP BY子句!

一段时间后,在使用MySQL进行练习时,我遇到了类似的问题,并犯了类似于上面提到的错误.然而,MySQL没有抱怨,只是吐了一些表,后来证明不是我需要的.

以上查询在SQL中是否真的非法,但在MySQL中是合法的,如果是,那为什么呢?在什么情况下需要进行这样的查询?

进一步阐述问题:

问题不在于是否也应在GROUP BY中提及SELECT中提到的所有属性.这就是为什么上面的查询,使用属性和属性上的聚合操作,没有任何GROUP BY在MySQL中是合法的.

让我们说Sailors表看起来像这样:

+----------+------+
| sname    | age  |
+----------+------+
| John Doe |   30 |
| Jane Doe |   50 |
+----------+------+
Run Code Online (Sandbox Code Playgroud)

然后查询将返回:

+----------+------------+
| sname    | MAX(S.age) |
+----------+------------+
| John Doe |         50 |
+----------+------------+
Run Code Online (Sandbox Code Playgroud)

现在谁需要那个?John Doe不是50岁,他是30岁!正如本书引用中所述,这是第一次尝试获得最年长水手的名字和年龄,在这个例子中,Jane Doe在50岁时.

SQL会说这个查询是非法的,但是MySQL只是进行并吐出"垃圾".谁需要这种结果?为什么MySQL会为新手提供这个小陷阱?

Dev*_*art 11

顺便说一句,它是默认的MySQL行为.但是可以通过在my.ini文件或会话中设置ONLY_FULL_GROUP_BY服务器模式来更改它-

SET sql_mode = 'ONLY_FULL_GROUP_BY';
SELECT * FROM sakila.film_actor GROUP BY actor_id;

Error: 'sakila.film_actor.film_id' isn't in GROUP BY
Run Code Online (Sandbox Code Playgroud)

ONLY_FULL_GROUP_BY - 不允许选择列表引用未在GROUP BY子句中命名的非聚合列的查询.


Red*_*ter 6

上面的查询在SQL中是否真的非法,但在MySQL中是合法的

如果是这样,那为什么呢

我不知道在MySQL中做出设计决策的原因,但考虑到你可以从聚合来自(例如或者)的同一行获得实际的相关数据而只需稍微多一些工作,我不会从任意行返回额外的列数据没有任何优势.MAXMIN

我非常不喜欢MySQL中的这个"功能",它让很多人学习MySQL的聚合,然后转移到不同的dbms,突然意识到他们从来都不知道他们在做什么.


pha*_*haz 5

基于链接a_horse_with_no_name在评论规定,我已经到达我自己的答案:

似乎MySQL使用GROUP BY的方式与SQL方式不同,为了允许从GROUP BY子句中省略列,当它们在功能上依赖于其他包含的列时.

假设我们有一个显示银行账户活动的表格.这不是一张经过深思熟虑的表,但它是我们唯一拥有的表,而且必须这样做.我们假设一个帐户从'0'开始,而不是跟踪金额,而是记录所有交易,因此金额是交易的总和.该表可能如下所示:

+------------+----------+-------------+
| costumerID | name     | transaction |
+------------+----------+-------------+
|       1337 | h4x0r    |         101 |
|         42 | John Doe |         500 |
|       1337 | h4x0r    |        -101 |
|         42 | John Doe |        -200 |
|         42 | John Doe |         500 |
|         42 | John Doe |        -200 |
+------------+----------+-------------+
Run Code Online (Sandbox Code Playgroud)

很明显,'name'在功能上依赖于'costumerID'.(在这个例子中,另一种方式也是可能的.)

如果我们想知道每个客户的costumerID,名称和当前金额怎么办?

在这种情况下,两个非常相似的查询将返回以下正确的结果:

+------------+----------+--------+
| costumerID | name     | amount |
+------------+----------+--------+
|         42 | John Doe |    600 |
|       1337 | h4x0r    |      0 |
+------------+----------+--------+
Run Code Online (Sandbox Code Playgroud)

此查询可以在MySQL中执行,并且根据SQL是合法的.

SELECT costumerID, name, SUM(transaction) AS amount
FROM Activity
GROUP BY costumerID, name
Run Code Online (Sandbox Code Playgroud)

此查询可以在MySQL中执行,根据SQL 合法.

SELECT costumerID, name, SUM(transaction) AS amount
FROM Activity
GROUP BY costumerID
Run Code Online (Sandbox Code Playgroud)

以下行将使查询返回并返回错误,因为它现在必须遵循使用聚合操作和GROUP BY的SQL方式:

SET sql_mode = 'ONLY_FULL_GROUP_BY';
Run Code Online (Sandbox Code Playgroud)

在MySQL中允许第二个查询的论据似乎是假设SELECT中提到但GROUP BY中没有提到的所有列都在聚合操作中使用("事务"的情况),或者是功能上依赖于其他包含的列("名称"的情况).在'name'的情况下,我们可以确保为所有组条目选择了正确的'name',因为它在功能上依赖于'costumerID',因此每个costumerID组只有一个可能的名称.

这种使用GROUP BY的方式看起来很难,因为它不会对GROUP BY子句中遗漏的内容进行任何进一步的检查.人们可以从他们的SELECT语句中选择列,以便在他们认为合适时放入他们的GROUP BY子句,即使包含或遗漏任何特定列没有意义.

水手的例子很好地说明了这个缺陷.使用聚合运算符(可能与GROUP BY结合使用)时,返回集中的每个组条目对其每个列只有一个值.在Sailors的情况下,由于省略了GROUP BY子句,整个表被放入一个单独的组条目中.此条目需要名称和最大年龄.选择此条目的最大年龄是明智的,因为MAX(S.age)只返回一个值.但是在S.sname的情况下,只在SELECT中提到过,现在有很多选择,因为整个Sailor表中都有独特的sname,(在本例中是两个,John和Jane Doe).MySQL没有任何线索可供选择,我们没有给它任何,它没有及时踩刹车,所以它必须先挑选任何先到先得的(Jane Doe).如果两行被切换,它实际上会偶然给出"正确答案".在MySQL中允许这样的事情似乎很明显,如果在GROUP BY子句中遗漏了某些内容,那么使用GROUP BY的查询结果可能会依赖于表的顺序.显然,这就是MySQL如何滚动.但是,当它因为"有缺陷"的查询而不知道它在做什么时,至少还是不能保证警告我们吗?我的意思是,当然,如果你给一个程序提供错误的指令,它可能不会(或不应该)按你的意愿做,但如果你给出不明确的指示,我当然不希望它只是开始猜测或挑选最先出的东西... -_-'