MySQL的SQL语句 - 数据操作语句(13)- 子查询(9)

派生表

派生表是一个表达式,在查询 FROM 子句范围内生成一个表。例如,SELECT 语句 FROM 子句中的子查询是派生表:

1. SELECT ... FROM (subquery) [AS] tbl_name ...

JSON_TABLE() 函数生成一个表,并提供另一种创建派生表的方法:

1. SELECT * FROM JSON_TABLE(arg_list) [AS] tbl_name ...

[AS] tbl_name 子句是必需的,因为 FROM 子句中的每个表都必须有一个名称。派生表中的任何列都必须具有唯一的名称。或者,tbl_name 后面可以跟一个用括号括起来的派生表的一系列列名:

1. SELECT ... FROM (subquery) [AS] tbl_name (col_list) ...

列名的数目必须与表的列数相同。

为了便于说明,假设有以下表:

1. CREATE TABLE t1 (s1 INT, s2 CHAR(5), s3 FLOAT);

下面是如何在 FROM 子句中使用子查询,使用示例表:

1. INSERT INTO t1 VALUES (1,‘1‘,1.0);
2. INSERT INTO t1 VALUES (2,‘2‘,2.0);
3. SELECT sb1,sb2,sb3
4.   FROM (SELECT s1 AS sb1, s2 AS sb2, s3*2 AS sb3 FROM t1) AS sb
5.   WHERE sb1 > 1;

结果:

1. +------+------+------+
2. | sb1  | sb2  | sb3  |
3. +------+------+------+
4. |    2 | 2    |    4 |
5. +------+------+------+

下面是另一个例子:假设想知道分组表的一组和的平均值。下面的语句不起作用:

1. SELECT AVG(SUM(column1)) FROM t1 GROUP BY column1;

但是,此查询会得到所需的信息:

1. SELECT AVG(sum_column1)
2.   FROM (SELECT SUM(column1) AS sum_column1
3.         FROM t1 GROUP BY column1) AS t1;

请注意,子查询中使用的列名(sum_column1)在外部查询中可以被识别。

派生表的列名来自其选择列表:

1. mysql> SELECT * FROM (SELECT 1, 2, 3, 4) AS dt;
2. +---+---+---+---+
3. | 1 | 2 | 3 | 4 |
4. +---+---+---+---+
5. | 1 | 2 | 3 | 4 |
6. +---+---+---+---+

若要显式提供列名,请在派生表名后面加上一个带圆括号的列名列表:

1. mysql> SELECT * FROM (SELECT 1, 2, 3, 4) AS dt (a, b, c, d);
2. +---+---+---+---+
3. | a | b | c | d |
4. +---+---+---+---+
5. | 1 | 2 | 3 | 4 |
6. +---+---+---+---+

派生表可以返回标量、列、行或表。

派生表受以下限制:

● 派生表不能包含对同一 SELECT 的其他表的引用(这种情况需要使用 LATERAL 派生表)

● 在 MySQL 8.0.14 之前,派生表不能包含外部引用。这个限制在 MySQL 8.0.14 中取消了,不是 SQL 标准的限制。例如,以下查询中的派生表 dt 包含外部查询中表 t1 的引用 t1.b:

1. SELECT * FROM t1
2. WHERE t1.d > (SELECT AVG(dt.a)
3.                 FROM (SELECT SUM(t2.a) AS a
4.                       FROM t2
5.                       WHERE t2.b = t1.b GROUP BY t2.c) dt
6.               WHERE dt.a > 10);

查询在 MySQL 8.0.14 及更高版本中有效。在 8.0.14 之前,它生成一个错误:Unknown column ‘t1.b‘ in ‘where clause‘

优化器确定有关派生表的信息时,EXPLAIN 不需要具体化它们。

在某些情况下,使用 EXPLAIN SELECT 可能会修改表数据。如果外部查询访问某些表,而内部查询调用存储函数,更改了表中的一行或多行,则会发生这种情况。假设数据库 d1 中有两个表 t1 和 t2,以及修改 t2 的存储函数 f1,创建如下:

1. CREATE DATABASE d1;
2. USE d1;
3. CREATE TABLE t1 (c1 INT);
4. CREATE TABLE t2 (c1 INT);
5. CREATE FUNCTION f1(p1 INT) RETURNS INT
6.   BEGIN
7.     INSERT INTO t2 VALUES (p1);
8.     RETURN p1;
9.   END;

在 EXPLAIN SELECT 中直接引用函数对 t2 没有影响,如下所示:

1. mysql> SELECT * FROM t2;
2. Empty set (0.02 sec)
3. 
4. mysql> EXPLAIN SELECT f1(5)\G
5. *************************** 1. row ***************************
6.            id: 1
7.   select_type: SIMPLE
8.         table: NULL
9.    partitions: NULL
10.          type: NULL
11. possible_keys: NULL
12.           key: NULL
13.       key_len: NULL
14.           ref: NULL
15.          rows: NULL
16.      filtered: NULL
17.         Extra: No tables used
18. 1 row in set (0.01 sec)
19. 
20. mysql> SELECT * FROM t2;
21. Empty set (0.01 sec)

这是因为 SELECT 语句没有引用任何表,可以在输出的 table 和 Extra 列中看到。以下嵌套 SELECT 也是如此:

1. mysql> EXPLAIN SELECT NOW() AS a1, (SELECT f1(5)) AS a2\G
2. *************************** 1. row ***************************
3.            id: 1
4.   select_type: PRIMARY
5.         table: NULL
6.          type: NULL
7. possible_keys: NULL
8.           key: NULL
9.       key_len: NULL
10.           ref: NULL
11.          rows: NULL
12.      filtered: NULL
13.         Extra: No tables used
14. 1 row in set, 1 warning (0.00 sec)
15. 
16. mysql> SHOW WARNINGS;
17. +-------+------+------------------------------------------+
18. | Level | Code | Message                                  |
19. +-------+------+------------------------------------------+
20. | Note  | 1249 | Select 2 was reduced during optimization |
21. +-------+------+------------------------------------------+
22. 1 row in set (0.00 sec)
23. 
24. mysql> SELECT * FROM t2;
25. Empty set (0.00 sec)

但是,如果外部 SELECT 引用某些表,优化器也会执行子查询中的语句,结果是 t2 被修改:

1. mysql> EXPLAIN SELECT * FROM t1 AS a1, (SELECT f1(5)) AS a2\G
2. *************************** 1. row ***************************
3.            id: 1
4.   select_type: PRIMARY
5.         table: <derived2>
6.    partitions: NULL
7.          type: system
8. possible_keys: NULL
9.           key: NULL
10.       key_len: NULL
11.           ref: NULL
12.          rows: 1
13.      filtered: 100.00
14.         Extra: NULL
15. *************************** 2. row ***************************
16.            id: 1
17.   select_type: PRIMARY
18.         table: a1
19.    partitions: NULL
20.          type: ALL
21. possible_keys: NULL
22.           key: NULL
23.       key_len: NULL
24.           ref: NULL
25.          rows: 1
26.      filtered: 100.00
27.         Extra: NULL
28. *************************** 3. row ***************************
29.            id: 2
30.   select_type: DERIVED
31.         table: NULL
32.    partitions: NULL
33.          type: NULL
34. possible_keys: NULL
35.           key: NULL
36.       key_len: NULL
37.           ref: NULL
38.          rows: NULL
39.      filtered: NULL
40.         Extra: No tables used
41. 3 rows in set (0.00 sec)
42. 
43. mysql> SELECT * FROM t2;
44. +------+
45. | c1   |
46. +------+
47. |    5 |
48. +------+
49. 1 row in set (0.00 sec)

这也意味着像这里所示的 EXPLAIN SELECT 语句可能需要执行很长时间,因为在 t1 中,BENCHMARK() 函数对每一行执行一次:

1. EXPLAIN SELECT * FROM t1 AS a1, (SELECT BENCHMARK(1000000, MD5(NOW())));

官方网址:
https://dev.mysql.com/doc/refman/8.0/en/derived-tables.html

MySQL的SQL语句 - 数据操作语句(13)- 子查询(9)

上一篇:MySQL的SQL语句 - 数据操作语句(13)- 子查询(8)


下一篇:MySQL binlog_ignore_db 参数最全解析