子查询指一个查询语句嵌套在另一个查询语句内部的查询,这个特性从MySQL 4.1开始引入。
SQL 中子查询的使用大大增强了 SELECT 查询的能力,因为很多时候查询需要从结果集中获取数据,或者需要从同一个表中先计算得出一个数据结果,然后与这个数据结果(可能是某个标量,也可能是某个集合)进行比较。


1. 需求分析与问题解决

1.1 实际问题

image.png
现有的解决方式:

  1. -- 需求:谁的工资比Abel
  2. -- 方式1:两次查询
  3. # 由具体的需求引入子查询
  4. SELECT
  5. employee_id,
  6. last_name,
  7. salary
  8. FROM
  9. employees
  10. WHERE
  11. last_name = 'Abel';
  12. SELECT
  13. employee_id,
  14. last_name,
  15. salary
  16. FROM
  17. employees
  18. WHERE
  19. salary > 11000;
  20. -- 方式2:自连接
  21. SELECT
  22. e1.employee_id,
  23. e1.last_name,
  24. e1.salary
  25. FROM
  26. employees e1,
  27. employees e2
  28. WHERE
  29. e2.last_name = 'Abel'
  30. AND e1.salary > e2.salary;
  31. -- 方式3:子查询
  32. SELECT
  33. last_name,
  34. salary
  35. FROM
  36. employees
  37. WHERE
  38. salary > ( SELECT salary FROM employees WHERE last_name = 'Abel' );
  39. -- 2. 称谓的规范:外查询(主插叙)和内查询(子查询)

1.2 子查询的基本使用

  • 子查询的基本语法结构

    1. SELECT select_list
    2. FROM `table`
    3. WHERE expr operator
    4. (
    5. SELECT select_list,
    6. FROM `table`
    7. );
  • 子查询(内查询)在主查询之前一次完成

  • 子查询的结果被主查询(外查询)使用
  • 注意事项
    • 子查询需要被括号包起来
    • 一般将子查询放在比较条件的右侧
    • 单行操作符对应单行子查询,多行操作符对应多行子查询

1.3 子查询的分类

分类方式1:

按照子查询返回的是一行还是多行结果,分为单行子查询多行子查询

  • 单行子查询

image.png

  • 多行子查询

image.png

分类方式2:

按照子查询是否被执行多次,分为相关(关联)子查询不相关(非关联)子查询
子查询从数据表中查询了数据结果,如果这个数据结果只执行一次,然后这个数据结果作为主查询的条件进行执行,那么这样的子查询叫做不相关子查询
同样,如果子查询需要执行多次,即采用循环的方式,先从外部查询开始,每次都传入子查询进行查询,然后再将结果反馈给外部,这种嵌套的执行方式就称为相关子查询

相关子查询的需求:查询工资大于本部门平均工资的员工的信息 不相关子查询的需求:查询工资大于本公司平均公司的员工的信息

2. 单行子查询

2.1 单行操作符

操作符 含义
= equal to
> greater than
>= greater than or equal to
< less than
<= less than or equal to
<> not equal to

2.2 代码示例

题目:查询工资大于149号员工工资的员工信息

  1. -- 题目:查询工资大于149号员工工资的员工信息
  2. SELECT
  3. employee_id,
  4. last_name,
  5. salary
  6. FROM
  7. employees
  8. WHERE
  9. salary > ( SELECT salary FROM employees WHERE employee_id = 149 );

题目:返回job_id和141号员工相同,salary比143号员工多的员工姓名,job_id和工资

  1. /* 题目:返回job_id和141号员工相同,salary比143号员工多的员工姓名,job_id和工资*/
  2. SELECT
  3. last_name,
  4. job_id,
  5. salary
  6. FROM
  7. employees
  8. WHERE
  9. job_id = ( SELECT job_id FROM employees WHERE employee_id = 141 )
  10. AND salary > ( SELECT salary FROM employees WHERE employee_id = 143 );

题目:返回公司工资最少的员工的last_name,job_idsalary

  1. SELECT
  2. last_name,
  3. job_id,
  4. salary
  5. FROM
  6. employees
  7. WHERE
  8. salary = ( SELECT MIN(salary) FROM employees );

题目:查询与141号或174号员工的manager_iddepartment_id相同的其他员工的employee_idmanager_iddepartment_id 注意:需要排除141号和174号两名员工本身

  1. -- 方法1:不成对比较
  2. SELECT
  3. employee_id,
  4. manager_id,
  5. department_id
  6. FROM
  7. employees
  8. WHERE
  9. manager_id IN ( SELECT manager_id FROM employees WHERE employee_id IN ( 141, 174 ) )
  10. AND department_id IN ( SELECT department_id FROM employees WHERE employee_id IN ( 141, 174 ) )
  11. AND employee_id NOT IN ( 141, 174 );
  12. -- 方法2:成对比较
  13. SELECT
  14. employee_id,
  15. manager_id,
  16. department_id
  17. FROM
  18. employees
  19. -- 直接两个字段一起比较
  20. WHERE
  21. ( manager_id, department_id ) IN ( SELECT manager_id, department_id FROM employees WHERE employee_id IN ( 141, 174 ) )
  22. AND employee_id NOT IN ( 141, 174 );

image.png

2.3 HAVING中的子查询

  • 首先执行子查询
  • 再向主查询中的HAVING语句返回结果

    问题:查询最低工资大于50号部门最低工资的部门id和其最低工资

  1. -- 问题:查询最低工资大于50号部门最低工资的部门id和其最低工资
  2. SELECT
  3. e1.department_id,
  4. department_name,
  5. MIN(e1.salary)
  6. FROM
  7. employees e1
  8. LEFT JOIN departments d ON e1.department_id = d.department_id
  9. GROUP BY
  10. e1.department_id
  11. HAVING MIN(e1.salary) > ( SELECT MIN(e2.salary) FROM employees e2 WHERE e2.department_id = 50 );

2.4 CASE中的子查询

在CASE表达式中使用单列子查询:

题目:显式员工的employee_id,last_namelocation。其中,若员工department_idlocation_id为1800的department_id相同,则locationCanada,其余则为USA

  1. SELECT
  2. employee_id,
  3. last_name,
  4. CASE
  5. department_id
  6. WHEN ( SELECT department_id FROM departments WHERE location_id = 1800 ) THEN
  7. 'Canada' ELSE 'USA'
  8. END "location"
  9. FROM
  10. employees;

image.png

2.5 子查询中的空值问题

可能子查询中查到的数据是不存在的,那么就会涉及到空值的问题。这种情况下,子查询不会返回任何数据
不会报错,但是结果集中也不会有任何的数据
image.png

2.6 非法使用子查询

多行子查询必须使用多行比较符,如果使用了单行比较符,就会出现非法使用子查询的问题

  1. SELECT employee_id, last_name
  2. FROM employees
  3. WHERE salary =
  4. (SELECT MIN(salary)
  5. FROM employees
  6. GROUP BY department_id);

image.png

3. 多行子查询

  • 也称为集合比较子查询
  • 内查询返回多行
  • 使用多行比较符

    3.1 多行比较操作符

    | 操作符 | 含义 | | —- | —- | | IN | 等于列表中的任意一个 | | ANY | 需要和单行比较操作符一起使用,和子查询返回的某一个值比较 | | ALL | 需要和单行比较操作符一起使用,和子查询返回的所有值比较 | | SOME | 实际上是ANY的别名,作用相同,一般常使用ANY |

3.2 代码示例

操作符:IN

  1. -- IN
  2. SELECT employee_id, last_name
  3. FROM employees
  4. WHERE salary IN
  5. (SELECT MIN(salary)
  6. FROM employees
  7. GROUP BY department_id);

image.png

操作符:ANY/ALL

题目:返回其它job_id中比job_idIT_PROG部门任一工资低的员工的员工号、姓名job_id以及salary

  1. -- 题目:返回其它job_id中比job_id为‘IT_PROG’部门所有工资都低的员工的员工号、姓名、job_id以及salary
  2. SELECT
  3. employee_id,
  4. last_name,
  5. job_id,
  6. salary
  7. FROM
  8. employees
  9. WHERE
  10. job_id <> 'IT_PROG'
  11. AND salary < ANY ( SELECT salary FROM employees WHERE job_id = 'IT_PROG' );

题目:返回其它job_id中比job_idIT_PROG部门所有工资都低的员工的员工号、姓名、job_id以及salary

  1. /*
  2. 题目:返回其它job_id中比job_id为IT_PROG部门所有工资都低的员工的员工号、姓名、job_id以及salary
  3. */
  4. SELECT
  5. employee_id,
  6. last_name,
  7. job_id,
  8. salary
  9. FROM
  10. employees
  11. WHERE
  12. job_id <> 'IT_PROG'
  13. AND salary < ALL ( SELECT salary FROM employees WHERE job_id = 'IT_PROG' );

题目:查询平均工资最低的部门department_id

  1. /*
  2. 查询平均工资最低的部门的部门id
  3. */
  4. SELECT
  5. department_id,
  6. AVG(salary)
  7. FROM
  8. employees
  9. GROUP BY
  10. department_id
  11. HAVING
  12. AVG(salary) <= ALL ( SELECT AVG(salary) FROM employees GROUP BY department_id );

可以将子查询的结果集当作一张虚拟的数据表来使用

  1. -- 将子查询的结果当作一张表来使用
  2. /*
  3. 查询平均工资最低的部门的部门id
  4. */
  5. SELECT
  6. MIN(`avg_salary`)
  7. FROM
  8. ( SELECT AVG(salary) `avg_salary` FROM employees GROUP BY department_id ) `t_avg_salary`;

image.png

3.3 空值问题

  1. SELECT last_name
  2. FROM employees
  3. WHERE employee_id NOT IN (
  4. SELECT manager_id
  5. FROM employees
  6. );

4. 相关子查询

4.1 相关子查询的查询流程

如果子查询的执行依赖于外部查询,通常情况下都是因为子查询中的表用到了外部的表,并进行了条件关联,因此每执行一次外部查询,子查询都要重新计算一次,这样的子查询就称之为关联子查询
相关子查询按照一行接一行的顺序执行,主查询的每一行都执行一次子查询。
image.png
说明:子查询中使用主查询中的列(子查询用到的数据和主查询是有关联的)

4.2 代码示例

回顾:查询员工中工资大于本公司平均工资的员工的last_name,salarydepartment_id

  1. -- 回顾:查询员工中工资大于本公司平均工资的员工的last_name,salary和其department_id
  2. SELECT
  3. last_name,
  4. salary,
  5. department_id
  6. FROM
  7. employees
  8. WHERE
  9. salary > ( SELECT AVG(salary) FROM employees );

题目:查询员工中工资大于本部门平均工资的员工的last_name,salary和其department_id

方法一:使用相关子查询

  1. -- 题目:查询员工中工资大于本部门平均工资的员工的last_name,salary和其department_id
  2. SELECT
  3. e1.last_name,
  4. e1.salary,
  5. e1.department_id
  6. FROM
  7. employees e1
  8. WHERE
  9. e1.salary > ( SELECT AVG(e2.salary) FROM employees e2 WHERE e1.department_id = e2.department_id );

方法二:在FROM中使用子查询

  1. -- 方法2:在FROM中使用子查询
  2. SELECT
  3. e1.last_name,
  4. e1.salary,
  5. e1.department_id
  6. FROM
  7. employees e1,( SELECT department_id,AVG(salary) `avg_salary` FROM employees GROUP BY department_id ) e2
  8. WHERE
  9. e1.department_id = e2.department_id
  10. AND e1.salary > e2.`avg_salary`;

from型的子查询:子查询是作为from的一部分,子查询要用()引起来,并且要给这个子查询取别名,把它当成一张“临时的虚拟的表”来使用。

题目:查询员工的id,salary,按照department_name 排序

  1. -- 题目:查询员工的id,salary,按照department_name 排序
  2. SELECT
  3. e.employee_id,
  4. e.department_id,
  5. e.salary
  6. FROM
  7. employees e
  8. ORDER BY ( SELECT department_name FROM departments d WHERE e.department_id = d.department_id );

可以使用子查询的位置:SELECT中,除了GROUP BYLIMIT之外,其他的位置都是可以声明子查询的

题目:employees表中employee_idjob_history表中employee_id相同的数目不小于2,输出这些相同id的员工的employee_id,last_name和其job_id

  1. /*
  2. 题目:若employees表中employee_id与job_history表中employee_id相同的数目不小于2,输出这些相同id的员工的employee_id,last_name和其job_id
  3. */
  4. SELECT
  5. e1.employee_id,
  6. e1.last_name,
  7. e1.job_id
  8. FROM
  9. employees e1
  10. WHERE
  11. 2 <= ( SELECT COUNT(*) FROM job_history j WHERE j.employee_id = e1.employee_id );

image.png

4.3 EXISTS和NOT EXISTS

  • 关联子查询通常也会与EXISTS操作符一起来使用,用来检查在子查询中是否存在满足条件的行
  • 如果在子查询中不存在满足条件的行
    • 条件返回FALSE
    • 继续在子查询中进行查找
  • 如果在子查询中存在满足条件的行
    • 退出子查询
    • 条件返回TRUE
  • NOT EXISTS关键字表示如果不存在某种条件,就返回TRUE,否则返回FALSE

题目:查询公司管理者的employee_id,last_name,job_id,department_id信息

方法1:自连接

  1. -- 方法1:自连接
  2. SELECT
  3. DISTINCT
  4. mgr.employee_id,
  5. mgr.last_name,
  6. mgr.job_id,
  7. mgr.department_id
  8. FROM
  9. employees e
  10. JOIN employees mgr ON e.manager_id = mgr.employee_id;

方法2:子查询

先查出所有管理者的id

  1. SELECT
  2. e.employee_id,
  3. e.last_name,
  4. e.job_id,
  5. e.department_id
  6. FROM
  7. employees e
  8. WHERE
  9. e.employee_id IN ( SELECT DISTINCT e1.manager_id FROM employees e1 WHERE e1.manager_id );

方法3:EXISTS

  1. SELECT
  2. e2.employee_id,
  3. e2.last_name,
  4. e2.job_id,
  5. e2.department_id
  6. FROM
  7. employees e2
  8. WHERE EXISTS ( SELECT * FROM employees e1 WHERE e2.employee_id = e1.manager_id );

题目:查询departments表中,不存在于employees表中的部门的department_iddepartment_name

方法1:右连接

  1. SELECT
  2. d.department_id,
  3. d.department_name
  4. FROM
  5. employees e
  6. RIGHT JOIN departments d ON e.department_id = d.department_id
  7. WHERE
  8. e.department_id IS NULL;

方法2:NOT EXISTS

  1. -- 方法2NOT EXISTS
  2. SELECT
  3. d1.department_id,
  4. d1.department_name
  5. FROM
  6. departments d1
  7. WHERE NOT EXISTS ( SELECT * FROM employees e WHERE e.department_id = d1.department_id );

4.4 相关更新

  1. UPDATE table1 alias1
  2. SET column = (SELECT expression
  3. FROM table2 alias2
  4. WHERE alias1.column = alias2.column);

使用相关子查询依据一个表中的数据更新另一个表的数据。

题目:在employees中增加一个department_name字段,数据为员工对应的部门名称

  1. # 1)
  2. ALTER TABLE employees
  3. ADD(department_name VARCHAR2(14));
  4. # 2)
  5. UPDATE employees e
  6. SET department_name = (SELECT department_name
  7. FROM departments d
  8. WHERE e.department_id = d.department_id);

4.5 相关删除

  1. DELETE FROM table1 alias1
  2. WHERE column operator (SELECT expression
  3. FROM table2 alias2
  4. WHERE alias1.column = alias2.column);

使用相关子查询依据一个表中的数据删除另一个表的数据。

题目:删除表employees中,其与emp_history表皆有的数据

  1. DELETE FROM employees e
  2. WHERE employee_id in
  3. (SELECT employee_id
  4. FROM emp_history
  5. WHERE employee_id = e.employee_id);

5. 一个思考题

问题:谁的工资比Abel的高?
解答:

  1. #方式1:自连接
  2. SELECT e2.last_name,e2.salary
  3. FROM employees e1,employees e2
  4. WHERE e1.last_name = 'Abel'
  5. AND e1.`salary` < e2.`salary`
  1. #方式2:子查询
  2. SELECT last_name,salary
  3. FROM employees
  4. WHERE salary > (
  5. SELECT salary
  6. FROM employees
  7. WHERE last_name = 'Abel'
  8. );

问题:以上两种方式有好坏之分吗?
解答:自连接方式好!
题目中可以使用子查询,也可以使用自连接。一般情况建议你使用自连接,因为在许多 DBMS 的处理过程中,对于自连接的处理速度要比子查询快得多。
可以这样理解:子查询实际上是通过未知表进行查询后的条件判断,而自连接是通过已知的自身数据表进行条件判断,因此在大部分 DBMS 中都对自连接处理进行了优化。