子查询就是指在一个 SELECT 语句中嵌套另一个 SELECT 语句。同时,子查询必须包含括号。
:::info MySQL 5.6.x 版本之前,MySQL 的子查询性能较差,但是从 5.6 开始,不存在性能差的问题。 :::
1.select a from t1 where a > any(select a from t2);
- select a from t1 是外部查询(outer query)。
- (select a from t2) 是子查询。
一般说来,子查询嵌套于外部查询中,可以将两个或两个以上的子查询进行嵌套。
子查询的使用
ANY & SOME
如果外部查询的列的结果和子查询的列的结果比较得到为 True 的话,则返回比较值为 True 的(外查询)的记录。
mysql> create table t1 (a int);
Query OK, 0 rows affected (0.15 sec)
mysql> create table t2 (a int);
Query OK, 0 rows affected (0.14 sec)
mysql> insert into t1 values(10),(4);
Query OK, 2 rows affected (0.02 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> insert into t2 values(12),(13),(5);
Query OK, 3 rows affected (0.03 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql> select a from t1;
+------+
| a |
+------+
| 10 |
| 4 |
+------+
2 rows in set (0.00 sec)
mysql> select * from t2;
+------+
| a |
+------+
| 12 | -- t1中10,4比12小
| 13 | -- t1中10,4比13小
| 5 | -- t1中10比5大,4比5小
+------+
3 rows in set (0.00 sec)
mysql> select a from t1
-> where a > any
-> (select a from t2); -- 返回(12,13,4)
-- t1中a列的值,只要大于(12,13,4)中任意一值
-- 即t1.a > t2.a为True,则返回对应的t1.a
+------+
| a |
+------+
| 10 | -- 10比5大为True,则返回该值,4比t2中所有的a值小,为False
+------+
1 row in set (0.00 sec)
-- 这个查询可以解释为,t1表内a列的值大于t2表中a列的任意(any)一个值(t1.a > any(t2.a) == true),则返回t1.a的记录
- ANY 关键词必须与一个比较操作符一起使用: =, >, <, >=, <=, <>(这个是 != 的意思)。
- 子查询中 SOME 和 ANY 是同一个意思。
IN
IN 是 ANY 的一种特殊情况:”in” equals “= any”。
mysql> insert into t1 values(5); -- 向t1中插入一个t2中存在的值5
Query OK, 1 row affected (0.03 sec)
mysql> select a from t1 where a = any(select a from t2); -- t1.a==t2.a 的只有5
+------+
| a |
+------+
| 5 |
+------+
1 row in set (0.00 sec)
mysql> select a from t1 where a in (select a from t2); -- in的结果等同于 =any 的结果
+------+
| a |
+------+
| 5 |
+------+
1 row in set (0.00 sec)
ALL
如果外部查询的列的结果和子查询的列的所有结果比较得到为 True 的话,则返回比较值为 True 的(外查询)的记录。
mysql> truncate t1; -- 清空t1
Query OK, 0 rows affected (0.07 sec)
mysql> truncate t2; -- 清空t2
Query OK, 0 rows affected (0.10 sec)
mysql> insert into t1 values(10),(4);
Query OK, 2 rows affected (0.02 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> insert into t2 values(5),(4),(3);
Query OK, 3 rows affected (0.03 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql> select a from t1 where a > all(select a from t2);
+------+
| a |
+------+
| 10 | -- (10 > 5, 4, 3 为 True) 而 (4 >5, 4, 3 为 False)
+------+
1 row in set (0.00 sec)
- ALL 关键词必须与一个比较操作符一起使用。
- NOT IN 是 <> ALL 的别名。
子查询的分类
独立子查询
不依赖外部查询而运行的子查询。
mysql> select a from t1 where a in (1,2,3,4,5);
+------+
| a |
+------+
| 4 |
+------+
1 row in set (0.00 sec)
相关子查询
引用了外部查询列的子查询。
-- 在这个例子中,子查询中使用到了外部的列t1.a
mysql> select a from t1 where a in (select * from t2 where t1.a = t2.a);
+------+
| a |
+------+
| 4 |
+------+
1 row in set (0.00 sec)
子查询的优化
MySQL 5.6 之前
在 MySQL 5.6 之前,优化器会把子查询重写成 exists 的形式。
select a from t1 where a in (select a from t2); -- 这个是一条独立的子查询,时间复杂度 O(M+N)
--
-- 经过优化器重写后
--
select a from t1 where exists (select 1 from t2 where t1.a = t2.a); -- 这是相关子查询,复杂度O(M*N + M)
所以在 MySQL 5.6 之前,部分的子查询需要重写成 join 的形式 (注意表的大小)。
mysql> select t1.a from t1 join t2 on t1.a = t2.a;
+------+
| a |
+------+
| 4 |
+------+
1 row in set (0.00 sec)
MySQL 5.6 之后
在 MySQL 5.6 之后,优化器不会将子查询重写成 exists 的形式,而是自动优化,性能有了大幅提升,可通过 explain extended 来查看子查询优化的结果。
explain extended 扩展能够在原本 explain 的基础上额外提供一些查询优化的信息,这些信息可以通过 MySQL 的show warnings 命令得到。
mysql> EXPLAIN EXTENDED SELECT * FROM employees t WHERE t.`first_name` IN ('Georgi','Bezalel','Parto','Chirstian');
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| 1 | SIMPLE | t | NULL | ALL | NULL | NULL | NULL | NULL | 299246 | 40.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 2 warnings (0.00 sec)
mysql> SHOW WARNINGS;
+---------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+---------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Warning | 1681 | 'EXTENDED' is deprecated and will be removed in a future release. |
| Note | 1003 | /* select#1 */ select `employees`.`t`.`emp_no` AS `emp_no`,`employees`.`t`.`birth_date` AS `birth_date`,`employees`.`t`.`first_name` AS `first_name`,`employees`.`t`.`last_name` AS `last_name`,`employees`.`t`.`gender` AS `gender`,`employees`.`t`.`hire_date` AS `hire_date` from `employees`.`employees` `t` where (`employees`.`t`.`first_name` in ('Georgi','Bezalel','Parto','Chirstian')) |
+---------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)
包含 NULL 值的 NOT IN
mysql> select null in ('a', 'b', null);
+--------------------------+
| null in ('a', 'b', null) |
+--------------------------+
| NULL |
+--------------------------+
1 row in set (0.00 sec)
MySQL 数据库的比较操作,除了返回 1(True), 0(False) 之外,还会返回 NULL,NULL 和 NULL 的比较,返回的还是 NULL。
mysql> select null not in ('a', 'b', null);
+------------------------------+
| null not in ('a', 'b', null) |
+------------------------------+
| NULL | -- null不在('a', 'b', null)中,返回的还是null,因为有null和null的比较
+------------------------------+
1 row in set (0.00 sec)
mysql> select 'a' not in ('a', 'b', null);
+-----------------------------+
| 'a' not in ('a', 'b', null) |
+-----------------------------+
| 0 | -- a不在('a', 'b', null)中,返回0,即False
+-----------------------------+
1 row in set (0.00 sec)
mysql> select 'c' not in ('a', 'b');
+-----------------------+
| 'c' not in ('a', 'b') |
+-----------------------+
| 1 | -- 这个返回值可以理解'c'不在('a', 'b')中,返回1,即为True
+-----------------------+
1 row in set (0.00 sec)
mysql> select 'c' not in ('a', 'b', null);
+-----------------------------+
| 'c' not in ('a', 'b', null) |
+-----------------------------+
| NULL | -- 理论上应该是返回1,即True的。但是包含了null值,则返回null
+-----------------------------+
1 row in set (0.00 sec)
对于包含了 NULL 值的 IN 操作,总是返回 True 或者 NULL,NOT IN 返回 NOT True(False)或者 NOT NULL(NULL)。
--
-- SQL语句一 使用 EXISTS
--
select customerid, companyname
from customers as A
where country = 'Spain'
and not exists
( select * from orders as B
where A.customerid = B.customerid );
--
-- SQL语句二 使用 IN
--
select customerid, companyname
from customers as A
where country = 'Spain'
and customerid not in (select customerid from orders);
-----
-- 当结果集合中没有NULL值时,上述两条SQL语句查询的结果是一致的
-----
--
-- 插入一个NULL值
--
insert into orders(orderid) values (null);
-----
-- SQL语句1 : 返回和之前一致
-- SQL语句2 : 返回为空表,因为子查询返回的结果集中存在NULL值。not in null 永远返回False或者NULL
-- 此时 where (country = 'Spain' and (False or NULL)) 为 False OR NULL,条件永远不匹配
-----
--
-- SQL语句2 改写后
--
select customerid, companyname
from customers as A
where country = 'Spain'
and customerid not in (select customerid from orders
where customerid is not null); -- 增加这个过滤条件,使用is not,而不是<>
--
-- 和 null比较,使用is和is not, 而不是 = 和 <>
--
mysql> select null = null;
+-------------+
| null = null |
+-------------+
| NULL |
+-------------+
1 row in set (0.00 sec)
mysql> select null <> null;
+--------------+
| null <> null |
+--------------+
| NULL |
+--------------+
1 row in set (0.00 sec)
mysql> select null is null;
+--------------+
| null is null |
+--------------+
| 1 | -- 返回 True
+--------------+
1 row in set (0.00 sec)
mysql> select null is not null;
+-------------------+
| null is not null |
+-------------------+
| 0 | -- 返回 False
+-------------------+
1 row in set (0.00 sec)
EXISTS 不管返回值是什么,而是看是否有行返回,所以 EXISTS 中子查询都是 select *、select 1 等,因为只关心返回是否有行(结果集)。
作者:殷建卫 链接:https://www.yuque.com/yinjianwei/vyrvkf/bpx24o 来源:殷建卫 - 架构笔记 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。