sql中的子查询

本篇博文使用的mysql版本:5.7.21

什么是子查询?

简单说明:

子查询:subquery,一条查询语句在另外一条查询语句当中,那么该查询语句就是子查询。


子查询就是将一个查询(子查询)的结果作为另一个查询(主查询)的数据来源或判断条件的查询。常见的子查询有WHERE子查询,HAVING子查询,FROM子查询,SELECT子查询,EXISTS子查询,子查询要使用小括号()


详细说明:

子查询又称内部查询,而包含子查询的语句称之外部查询(又称主查询)。

所有的子查询可以分为两类,即 相关子查询 和 不相关子查询。

不相关子查询是独立于外部查询的子查询,它可以单独运行并产生结果。

相关子查询的执行依赖于外部查询的数据,外部查询执行一行,子查询就执行一次。


相关子查询的执行依赖于外部查询。多数情况下是子查询的WHERE子句中引用了外部查询的表。

执行过程:

(1)从外层查询中取出一个元组,将元组相关列的值传给内层查询。

(2)执行内层查询,得到子查询操作的值。

(3)外查询根据子查询返回的结果或结果集得到满足条件的行。

(4)然后外层查询取出下一个元组重复做步骤1-3,直到外层的元组全部处理完毕。


子查询特性从 mysql4.1 开始引入,在 SELECT 子句中先计算子查询,子查询结果作为外层另一个查询的过滤条件,查询可以基于一个表或者多个表。


子查询可以添加到 SELECT、UPDATE 和 DELETE 语句中,而且可以进行多层嵌套。子查询也可以使用比较运算符,如 "<"、"<="、">"、">="、"!="等。


常用的select语句的子查询操作

一条select查询语句得到的结果可能有哪些?

(1)一张表

(2)一行

(3)一个列

(4)一个字段


基于查询得到的结果,那么可以对子查询进行以下分类:

(1)标量子查询:子查询返回的结果是一个字段单元

(2)列子查询:子查询返回的结果是一个字段,但是有多个值

(3)行子查询:子查询返回的结果是一行记录(一条记录:大于一个字段)

(4)表子查询:返回的结果是一个二维表(多行多列)


准备测试数据:

create table emp (id int,name char(10),sex char,age int,dept_id int,job char(10),salary double);

insert into emp values
(1,"刘备","男",26,1,"总监",5800),
(2,"张飞","男",24,1,"员工",3000),
(3,"关羽","男",30,1,"员工",4000),
(4,"孙权","男",25,2,"总监",6000),
(5,"周瑜","男",22,2,"员工",5000),
(6,"小乔","女",31,2,"员工",4000),
(7,"曹操","男",19,3,"总监",10000),
(8,"司马懿","男",24,3,"员工",6000);


create table dept(id int primary key,name char(10));
insert into dept values(1,"市场"),(2,"行政"),(3,"财务");


当查询需求比较复杂,一次性查询无法得到结果,需要多次查询时,比方说下面这个查询需求

有这样一个查询需求:查询 市场 部门下的 所有的员工信息


sql可以这么写:

mysql> select id from dept where name ='市场';
+----+
| id |
+----+
|  1 |
+----+
1 row in set (0.00 sec)

mysql> select * from emp where dept_id = 1;
+------+--------+------+------+---------+--------+--------+
| id   | name   | sex  | age  | dept_id | job    | salary |
+------+--------+------+------+---------+--------+--------+
|    1 | 刘备   | 男   |   26 |       1 | 总监   |   5800 |
|    2 | 张飞   | 男   |   24 |       1 | 员工   |   3000 |
|    3 | 关羽   | 男   |   30 |       1 | 员工   |   4000 |
+------+--------+------+------+---------+--------+--------+
3 rows in set (0.00 sec)

mysql>

标量子查询

回过头再仔细看看上述的两条查询语句,第二条查询语句的where条件其实是用到了第一条查询语句的查询结果。为了书写简便,我们可以把这两条语句合并到一条语句中,从而减少了把第一条查询语句的结果复制粘贴到第二条查询语句中的步骤,就像这样:

mysql> select * from emp where dept_id = (select id from dept where name = '市场');
+------+--------+------+------+---------+--------+--------+
| id   | name   | sex  | age  | dept_id | job    | salary |
+------+--------+------+------+---------+--------+--------+
|    1 | 刘备   | 男   |   26 |       1 | 总监   |   5800 |
|    2 | 张飞   | 男   |   24 |       1 | 员工   |   3000 |
|    3 | 关羽   | 男   |   30 |       1 | 员工   |   4000 |
+------+--------+------+------+---------+--------+--------+
3 rows in set (0.00 sec)

mysql>

我们把第二条查询语句用小括号()扩起来作为一个操作数放到了第一条查询语句的搜索条件处,这样就起到了合并两条查询语句的作用。小括号中的查询语句也被称为子查询或者内层查询,使用内层查询的结果作为搜索条件的操作数的查询称为外层查询。如果你在一个查询语句中需要用到更多的表的话,那么在一个子查询中可以继续嵌套另一个子查询,在执行查询语句时,将按照从内到外的顺序依次执行这些查询。


在上面这个例子中的子查询的结果只有一个值(也就是'市场'的id值),这种子查询称之为标量子查询。正因为标量子查询单纯的代表一个值,所以它可以作为表达式的操作数来参与运算,它除了用在外层查询的搜索条件中以外,也可以被放到查询列表处,比如这样:

mysql> SELECT (SELECT id FROM dept WHERE name = '市场') AS ID值;
+-------+
| ID值  |
+-------+
|     1 |
+-------+
1 row in set (0.00 sec)

mysql>

标量子查询单纯的代表一个值,由标量子查询作为的操作数 组成的搜索条件只要符合表达语法就可以。


在比方说我们来查询大于市场部门的id值 下的部门下的所有员工信息,可以这么写:

mysql> SELECT * FROM emp WHERE dept_id > (SELECT id  FROM dept WHERE name = '市场');
+------+-----------+------+------+---------+--------+--------+
| id   | name      | sex  | age  | dept_id | job    | salary |
+------+-----------+------+------+---------+--------+--------+
|    4 | 孙权      | 男   |   25 |       2 | 总监   |   6000 |
|    5 | 周瑜      | 男   |   22 |       2 | 员工   |   5000 |
|    6 | 小乔      | 女   |   31 |       2 | 员工   |   4000 |
|    7 | 曹操      | 男   |   19 |       3 | 总监   |  10000 |
|    8 | 司马懿    | 男   |   24 |       3 | 员工   |   6000 |
+------+-----------+------+------+---------+--------+--------+
5 rows in set (0.00 sec)

mysql>

列子查询

查询 市场 或者 财务 部门下的所有员工信息:

mysql> SELECT * FROM emp WHERE dept_id IN (SELECT id FROM dept WHERE name = '市场' or name = '财务');
+------+-----------+------+------+---------+--------+--------+
| id   | name      | sex  | age  | dept_id | job    | salary |
+------+-----------+------+------+---------+--------+--------+
|    1 | 刘备      | 男   |   26 |       1 | 总监   |   5800 |
|    2 | 张飞      | 男   |   24 |       1 | 员工   |   3000 |
|    3 | 关羽      | 男   |   30 |       1 | 员工   |   4000 |
|    7 | 曹操      | 男   |   19 |       3 | 总监   |  10000 |
|    8 | 司马懿    | 男   |   24 |       3 | 员工   |   6000 |
+------+-----------+------+------+---------+--------+--------+
5 rows in set (0.00 sec)

mysql>

很显然上面子查询语句的结果集中并不是一个单独的值,而是一个列(上述例子中的子查询语句的结果集中是一个单列,该列包含2个值,分别是:1和3),所以它对应的子查询也被称之为列子查询。因为列子查询得到的结果是多个值,相当于一个列表。所以上边使用的例子是使用IN操作符和子查询的结果组成表达式来作为外层查询的搜索条件的。

行子查询

既然有列子查询,大家肯定就好奇有没有行子查询。哈哈,当然有了,只要子查询的结果集中最多只包含一条记录,而且这条记录中有超过一个列的数据(如果该条记录只包含一个列的话,该子查询就成了标量子查询),那么这个子查询就可以被称之为行子查询,比如这样:

mysql> SELECT * FROM emp WHERE (dept_id,name) = (SELECT id,name FROM dept LIMIT 1);
Empty set (0.00 sec)

mysql> 

-- 哈哈哈,查询结果为空,很尴尬,目前提供的测试数据没有符合这个行子查询的数据~~~ 但是这条sql语句大家应该都能看懂吧~

该子查询的查询列表是id, name。我们在子查询语句中加了LIMIT 1这个子句,意味着子查询最多只能返回一条记录,所以该子查询就可以被看作一个行子查询


注意: 在想要得到标量子查询或者行子查询,但又不能保证子查询的结果集只有一条记录时,应该使用LIMIT 1子句来限制记录数量。

表子查询

如果子查询结果集中包含多行多列,那么这个子查询也可以被称之为表子查询,比如这样:

mysql> select * from (select * from emp where dept_id in (1,2) ) as temp_table order by temp_table.salary desc;
+------+--------+------+------+---------+--------+--------+
| id   | name   | sex  | age  | dept_id | job    | salary |
+------+--------+------+------+---------+--------+--------+
|    4 | 孙权   | 男   |   25 |       2 | 总监   |   6000 |
|    1 | 刘备   | 男   |   26 |       1 | 总监   |   5800 |
|    5 | 周瑜   | 男   |   22 |       2 | 员工   |   5000 |
|    6 | 小乔   | 女   |   31 |       2 | 员工   |   4000 |
|    3 | 关羽   | 男   |   30 |       1 | 员工   |   4000 |
|    2 | 张飞   | 男   |   24 |       1 | 员工   |   3000 |
+------+--------+------+------+---------+--------+--------+
6 rows in set (0.00 sec)

mysql> 

-- 虽然这条sql语句,根本不需要子查询,一条sql语句也可以实现,不过为了描述清楚 表子查询,就不用计较这些东西啦。

在这个例子中的子查询执行之后的结果集中包含多行多列,所以可以被看作是一个表子查询

EXISTS和NOT EXISTS子查询

关键字:EXISTS

语法:EXISTS (SELECT ...)

描述:当子查询结果集不是空集时表达式为真


关键字:NOT EXISTS

语法:NOT EXISTS (SELECT ...)

描述:当子查询结果集是空集时表达式为真


举个栗子:

mysql> SELECT * FROM emp WHERE EXISTS (SELECT * FROM dept WHERE name = '产品');
Empty set (0.00 sec)

mysql>

其中子查询的意思是在dept表中查找部门名字为产品的数据,很显然并没有该条数据,所以子查询的结果集是一个空集,于是EXISTS表达式的结果为FALSE,所以外层查询也就不查了,直接返回了一个Empty set,表示没有结果。你可以自己试一下NOT EXISTS的使用。


尾声:

SELECT查询的结果作为另一个 SELECT语句的条件。子查询可以一次性完成很多逻辑上需要多个步骤才能完成的SQL操作。子查询虽然可以使查询语句很灵活,但执行效率不高。执行子查询时(表子查询及相关需要创建临时表的其它子查询语句), MYSQL需要为内层查询语句的查询结果建立一个临时表。然后外层查询语句从临时表中查询记录。查询完毕后,再撤销这些临时表。因此,子查询的速度会受到一定的影响。如果查询的数据量比较大,这种影响就会随之增大。


在mysql中,可以使用连接(JOIN)查询来替代子查询。连接查询不需要建立临时表,其速度比子查询要快,如果查询中使用索引的话,性能会更好。连接之所以更有效率,是因为 MYSQL不需要在内存中创建临时表来完成查询工作。


EXPLAIN 或 DESC 查看sql执行计划结果的 Extra 列中,如果包含 Using Temporary 就表示用到了临时表。


所以 请拿起 EXPLAIN / DESC 武器,如果你看到了 Using Temporary,请优化。


子查询不是mysql独有的特性,sqlserver等数据库亦可以使用。



愿你的善良  带有菩萨心肠  亦有金刚锋芒    -->【前半生】



声明:禁止任何非法用途使用,凡因违规使用而引起的任何法律纠纷,本站概不负责。

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

精彩评论

全部回复12人评论7,777人参与