在GROUP BY和HAVING与隐藏列,MySQL手册文件(强调):
MySQL 扩展了 的使用,
GROUP BY以便选择列表可以引用GROUP BY子句中未命名的非聚合列。这意味着前面的查询在 MySQL 中是合法的。您可以使用此功能通过避免不必要的列排序和分组来获得更好的性能。但是,这主要在GROUP BY每个组中未命名的每个非聚合列中的所有值都相同时很有用。服务器可以自由地从每个组中选择任何值,因此除非它们相同,否则选择的值是不确定的。
尽管开发人员发出了这个明确的警告,但有些人继续依赖未记录的行为来确定将从隐藏列中选择的值。
特别是,MySQL似乎经常从每个组中选择“第一”记录(其中“第一”的概念本身没有记录,例如某些存储引擎上的最旧记录或根据应用于物化表的某种排序顺序从子查询)。我已经看到这个被利用来检索,例如,分组最大值:
SELECT * FROM (
SELECT * FROM my_table ORDER BY sort_col DESC
) t GROUP BY group_col
Run Code Online (Sandbox Code Playgroud)
为了完整起见,可以通过简单的连接以标准和文档化的方式完成相同的操作:
SELECT * FROM my_table NATURAL JOIN (
SELECT group_col, MAX(sort_col) sort_col
FROM my_table
GROUP BY group_col
) t
Run Code Online (Sandbox Code Playgroud)
我相信人们永远不应该依赖无证行为,因为可能存在无法预见的极端情况导致该行为中断。例如,在GROUP BY用索引满足操作时,MySQL 对结果进行排序,从而可能选择一个意外的值。
还有哪些极端情况可以打破这种行为?或者它对于生产系统是否足够可靠?
我在想NATURAL JOIN你刚才使用的例子
SELECT * FROM my_table NATURAL JOIN (
SELECT group_col, MAX(sort_col) sort_col
FROM my_table
GROUP BY group_col
) t
Run Code Online (Sandbox Code Playgroud)
如果你转向另一种类型JOIN并强加WHERE,尽管不明智地依赖无证行为,但订单可能会在没有警告的情况下来来去去。GROUP BY。
对于这个例子,我将
LEFT JOIN不带WHERE子句的LEFT JOIN带有 WHERE 子句的对于数据库环境
mysql> select version();
+------------+
| version() |
+------------+
| 5.5.12-log |
+------------+
1 row in set (0.00 sec)
mysql> show variables like '%version_co%';
+-------------------------+------------------------------+
| Variable_name | Value |
+-------------------------+------------------------------+
| version_comment | MySQL Community Server (GPL) |
| version_compile_machine | x86 |
| version_compile_os | Win64 |
+-------------------------+------------------------------+
3 rows in set (0.00 sec)
mysql>
Run Code Online (Sandbox Code Playgroud)
使用此脚本生成示例数据
DROP DATABASE IF EXISTS eggyal;
CREATE DATABASE eggyal;
USE eggyal
CREATE TABLE groupby
(
id int not null auto_increment,
num int,
primary key (id)
);
INSERT INTO groupby (num) VALUES
(floor(rand() * unix_timestamp())),(floor(rand() * unix_timestamp())),
(floor(rand() * unix_timestamp())),(floor(rand() * unix_timestamp())),
(floor(rand() * unix_timestamp())),(floor(rand() * unix_timestamp())),
(floor(rand() * unix_timestamp())),(floor(rand() * unix_timestamp()));
INSERT INTO groupby (num) SELECT num FROM groupby;
SELECT * FROM groupby;
Run Code Online (Sandbox Code Playgroud)
以及这两个查询用于测试GROUP BY后续使用;
SELECT * FROM groupby A LEFT JOIN
(
SELECT num, MAX(id) id
FROM groupby
GROUP BY num
) B USING (id);
SELECT * FROM groupby A LEFT JOIN
(
SELECT num, MAX(id) id
FROM groupby
GROUP BY num
) B USING (id) WHERE B.num IS NOT NULL;
Run Code Online (Sandbox Code Playgroud)
让我们测试一下GROUP BY结果的持久性;
mysql> DROP DATABASE IF EXISTS eggyal;
Query OK, 1 row affected (0.09 sec)
mysql> CREATE DATABASE eggyal;
Query OK, 1 row affected (0.00 sec)
mysql> USE eggyal
Database changed
mysql> CREATE TABLE groupby
-> (
-> id int not null auto_increment,
-> num int,
-> primary key (id)
-> );
Query OK, 0 rows affected (0.07 sec)
mysql> INSERT INTO groupby (num) VALUES
-> (floor(rand() * unix_timestamp())),(floor(rand() * unix_timestamp())),
-> (floor(rand() * unix_timestamp())),(floor(rand() * unix_timestamp())),
-> (floor(rand() * unix_timestamp())),(floor(rand() * unix_timestamp())),
-> (floor(rand() * unix_timestamp())),(floor(rand() * unix_timestamp()));
Query OK, 8 rows affected (0.06 sec)
Records: 8 Duplicates: 0 Warnings: 0
mysql> INSERT INTO groupby (num) SELECT num FROM groupby;
Query OK, 8 rows affected (0.05 sec)
Records: 8 Duplicates: 0 Warnings: 0
mysql> SELECT * FROM groupby;
+----+------------+
| id | num |
+----+------------+
| 1 | 269529129 |
| 2 | 387090406 |
| 3 | 1126864683 |
| 4 | 411160755 |
| 5 | 29173595 |
| 6 | 266349579 |
| 7 | 1244227156 |
| 8 | 6231766 |
| 9 | 269529129 |
| 10 | 387090406 |
| 11 | 1126864683 |
| 12 | 411160755 |
| 13 | 29173595 |
| 14 | 266349579 |
| 15 | 1244227156 |
| 16 | 6231766 |
+----+------------+
16 rows in set (0.00 sec)
Run Code Online (Sandbox Code Playgroud)
LEFT JOIN不带WHERE子句使用mysql> SELECT * FROM groupby A LEFT JOIN
-> (
-> SELECT num, MAX(id) id
-> FROM groupby
-> GROUP BY num
-> ) B USING (id);
+----+------------+------------+
| id | num | num |
+----+------------+------------+
| 1 | 269529129 | NULL |
| 2 | 387090406 | NULL |
| 3 | 1126864683 | NULL |
| 4 | 411160755 | NULL |
| 5 | 29173595 | NULL |
| 6 | 266349579 | NULL |
| 7 | 1244227156 | NULL |
| 8 | 6231766 | NULL |
| 9 | 269529129 | 269529129 |
| 10 | 387090406 | 387090406 |
| 11 | 1126864683 | 1126864683 |
| 12 | 411160755 | 411160755 |
| 13 | 29173595 | 29173595 |
| 14 | 266349579 | 266349579 |
| 15 | 1244227156 | 1244227156 |
| 16 | 6231766 | 6231766 |
+----+------------+------------+
16 rows in set (0.00 sec)
mysql>
Run Code Online (Sandbox Code Playgroud)
LEFT JOIN与WHERE子句一起使用mysql> SELECT * FROM groupby A LEFT JOIN
-> (
-> SELECT num, MAX(id) id
-> FROM groupby
-> GROUP BY num
-> ) B USING (id) WHERE B.num IS NOT NULL;
+----+------------+------------+
| id | num | num |
+----+------------+------------+
| 16 | 6231766 | 6231766 |
| 13 | 29173595 | 29173595 |
| 14 | 266349579 | 266349579 |
| 9 | 269529129 | 269529129 |
| 10 | 387090406 | 387090406 |
| 12 | 411160755 | 411160755 |
| 11 | 1126864683 | 1126864683 |
| 15 | 1244227156 | 1244227156 |
+----+------------+------------+
8 rows in set (0.00 sec)
mysql>
Run Code Online (Sandbox Code Playgroud)
看了上面的结果,这里有两个问题:
LEFT JOIN保留排序方式id?WHERE强制重新排序?
没有人预见到任何这些影响,因为显式子句的行为是由查询优化器的隐式行为所依赖的。
从我的角度来看,极端情况只能是外部性质的。有鉴于此,开发人员必须愿意GROUP BY结合以下十二(12)个方面全面评估结果:
JOINs条款WHERE条款ORDER BY子句的结果排序顺序my.cnf这里是要记住的关键点:任何适用于特定环境中的查询的 MySQL 实例本身就是一个特殊情况。一旦更改十二 (12) 个评估方面中的一个或多个,极端情况就会崩溃,特别是考虑到前九 (9) 个方面。