SQL-92 及更早版本不允许查询,其中选择列表、HAVING 条件或 ORDER BY 列表引用非聚合列,而这些列不在 GROUP BY 子句中命名。例如,这个查询在标准 SQL-92 中是非法的,因为选择列表中的非聚合 name 列不在 GROUP BY 子句中:
SELECT o.custid, c.name, MAX(o.payment)
FROM orders AS o, customers AS c
WHERE o.custid = c.custid
GROUP BY o.custid;
为了使查询在 SQL-92 中合法,name 列必须从选择列表中省略或在 GROUP BY 子句中命名。
SQL:1999 及更高版本允许这些非聚合列,如果它们在 GROUP BY 列上是函数依赖的:如果 name 和 custid 之间存在这种关系,那么查询就是合法的。这将是这种情况,例如,如果 custid 是 customers 的主键。
MySQL 实现了函数依赖检测。如果启用了 ONLY_FULL_GROUP_BY SQL 模式(默认情况下启用),MySQL 将拒绝查询,其中选择列表、HAVING 条件或 ORDER BY 列表引用非聚合列,而这些列既不在 GROUP BY 子句中命名,也不在函数依赖于它们。
MySQL 也允许在 GROUP BY 子句中未命名的非聚合列,当 SQL ONLY_FULL_GROUP_BY 模式启用时,只要该列在单个值上有限制,如下所示:
mysql> CREATE TABLE mytable (
-> id INT UNSIGNED NOT NULL PRIMARY KEY,
-> a VARCHAR(10),
-> b INT
-> );
mysql> INSERT INTO mytable
-> VALUES (1, 'abc', 1000),
-> (2, 'abc', 2000),
-> (3, 'def', 4000);
mysql> SET SESSION sql_mode = sys.list_add(@@session.sql_mode, 'ONLY_FULL_GROUP_BY');
mysql> SELECT a, SUM(b) FROM mytable WHERE a = 'abc';
+------+--------+
| a | SUM(b) |
+------+--------+
| abc | 3000 |
+------+--------+
也可以在 SELECT 列表中拥有多个非聚合列,当使用 ONLY_FULL_GROUP_BY 模式时。在这种情况下,每个这样的列必须在 WHERE 子句中有限制,并且所有这些限制条件必须用逻辑 AND 连接,如下所示:
mysql> DROP TABLE IF EXISTS mytable;
mysql> CREATE TABLE mytable (
-> id INT UNSIGNED NOT NULL PRIMARY KEY,
-> a VARCHAR(10),
-> b VARCHAR(10),
-> c INT
-> );
mysql> INSERT INTO mytable
-> VALUES (1, 'abc', 'qrs', 1000),
-> (2, 'abc', 'tuv', 2000),
-> (3, 'def', 'qrs', 4000),
-> (4, 'def', 'tuv', 8000),
-> (5, 'abc', 'qrs', 16000),
-> (6, 'def', 'tuv', 32000);
mysql> SELECT @@session.sql_mode;
+---------------------------------------------------------------+
| @@session.sql_mode |
+---------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION |
+---------------------------------------------------------------+
mysql> SELECT a, b, SUM(c) FROM mytable
-> WHERE a = 'abc' AND b = 'qrs';
+------+------+--------+
| a | b | SUM(c) |
+------+------+--------+
| abc | qrs | 17000 |
+------+------+--------+
如果禁用 ONLY_FULL_GROUP_BY,MySQL 对标准 SQL 使用 GROUP BY 的扩展允许选择列表、HAVING 条件或 ORDER BY 列表引用非聚合列,即使这些列不在 GROUP BY 子句中命名。在这种情况下,服务器可以从每个组中选择任何值,因此除非它们相同,选择的值将是非确定性的,这可能不是您想要的。此外,添加 ORDER BY 子句也不能影响服务器从每个组中选择的值。结果集排序发生在值被选择后,ORDER BY 不会影响服务器从每个组中选择的值。禁用 ONLY_FULL_GROUP_BY 主要是当您知道由于某些数据属性,所有非聚合列的值在每个组中都是相同的时有用。
您可以通过使用 ANY_VALUE() 引用非聚合列来达到相同的效果,而不需要禁用 ONLY_FULL_GROUP_BY。
以下讨论演示了函数依赖关系,MySQL 在缺乏函数依赖关系时产生的错误消息,以及在缺乏函数依赖关系时使 MySQL 接受查询的方法。
这个查询可能在 ONLY_FULL_GROUP_BY 启用时无效,因为选择列表中的非聚合 address 列不在 GROUP BY 子句中命名:
SELECT name, address, MAX(age) FROM t GROUP BY name;
如果 name 是 t 的主键或唯一的 NOT NULL 列,那么查询是合法的。在这种情况下,MySQL 认识到所选列是分组列的函数依赖项。例如,如果 name 是主键,那么其值将确定 address 的值,因为每个组只有一个主键值,因此只有一个行。因此,在组中选择 address 值时没有随机性,无需拒绝查询。
如果 name 不是 t 的主键或唯一的 NOT NULL 列,那么查询是无效的。在这种情况下,无法推断函数依赖关系,错误将发生:
mysql> SELECT name, address, MAX(age) FROM t GROUP BY name;
ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP
BY clause and contains nonaggregated column 'mydb.t.address' which
is not functionally dependent on columns in GROUP BY clause; this
is incompatible with sql_mode=only_full_group_by
如果您知道,对于给定的数据集,每个 name 值实际上唯一确定了 address 值,那么 address 就是函数依赖于 name 的。要告诉 MySQL 接受该查询,可以使用 ANY_VALUE() 函数:
SELECT name, ANY_VALUE(address), MAX(age) FROM t GROUP BY name;
或者,禁用 ONLY_FULL_GROUP_BY。
前面的示例非常简单。特别是,您不太可能在单个主键列上分组,因为每个组将只包含一行。有关功能依赖项在更复杂查询中的示例,请参阅 第 14.19.4 节,“检测功能依赖项”。
如果查询具有聚合函数且没有 GROUP BY 子句,则不能在选择列表、HAVING 条件或 ORDER BY 列表中具有非聚合列,ONLY_FULL_GROUP_BY 启用时:
mysql> SELECT name, MAX(age) FROM t;
ERROR 1140 (42000): In aggregated query without GROUP BY, expression
#1 of SELECT list contains nonaggregated column 'mydb.t.name'; this
is incompatible with sql_mode=only_full_group_by
没有 GROUP BY,只有一个组,它是非确定性的选择哪个 name 值用于该组。这里也可以使用 ANY_VALUE(),如果选择哪个 name 值无关紧要:
SELECT ANY_VALUE(name), MAX(age) FROM t;
ONLY_FULL_GROUP_BY 也影响使用 DISTINCT 和 ORDER BY 的查询处理。考虑表 t 的情况,该表具有三列 c1、c2 和 c3,其中包含这些行:
c1 c2 c3
1 2 A
3 4 B
1 2 C
假设我们执行以下查询,期望结果按 c3 排序:
SELECT DISTINCT c1, c2 FROM t ORDER BY c3;
要排序结果,必须首先消除重复项。但是,要做到这一点,应该保留第一行还是第三行?这个任意选择会影响保留的 c3 值,从而影响排序并使其任意。为了防止这个问题,具有 DISTINCT 和 ORDER BY 的查询将被视为无效,除非任何 ORDER BY 表达式满足以下条件之一:
-
表达式等于选择列表中的一个
-
所有由表达式引用的列都属于选择列表
另一个 MySQL 扩展到标准 SQL 的地方是,允许在 HAVING 子句中引用选择列表中的别名。例如,以下查询返回表 orders 中只出现一次的 name 值:
SELECT name, COUNT(name) FROM orders
GROUP BY name
HAVING COUNT(name) = 1;
MySQL 扩展允许在 HAVING 子句中使用别名来引用聚合列:
SELECT name, COUNT(name) AS c FROM orders
GROUP BY name
HAVING c = 1;
标准 SQL 只允许在 GROUP BY 子句中使用列表达式,因此以下语句无效,因为 FLOOR(value/100) 是一个非列表达式:
SELECT id, FLOOR(value/100)
FROM tbl_name
GROUP BY id, FLOOR(value/100);
MySQL 扩展标准 SQL,以便在 GROUP BY 子句中使用非列表达式,因此认为前面的语句有效。
标准 SQL 也不允许在 GROUP BY 子句中使用别名。MySQL 扩展标准 SQL,以便在 GROUP BY 子句中使用别名,因此可以使用以下方式编写查询:
SELECT id, FLOOR(value/100) AS val
FROM tbl_name
GROUP BY id, val;
别名 val 被认为是 GROUP BY 子句中的列表达式。
在 GROUP BY 子句中存在非列表达式时,MySQL 认识到该表达式与选择列表中的表达式之间的相等关系。这意味着启用 ONLY_FULL_GROUP_BY SQL 模式时,包含 GROUP BY id, FLOOR(value/100) 的查询是有效的,因为同一个 FLOOR() 表达式出现在选择列表中。然而,MySQL 不会尝试识别 GROUP BY 非列表达式的功能依赖项,因此以下查询在启用 ONLY_FULL_GROUP_BY 时无效,即使第三个选择表达式是 id 列和 FLOOR() 表达式的简单公式:
SELECT id, FLOOR(value/100), id+FLOOR(value/100)
FROM tbl_name
GROUP BY id, FLOOR(value/100);
解决方法是使用派生表:
SELECT id, F, id+F
FROM
(SELECT id, FLOOR(value/100) AS F
FROM tbl_name
GROUP BY id, FLOOR(value/100)) AS dt;