目录
在一个查询结构中我们又嵌入了另外一个查询,这种嵌套查询的方式就被成为是子查询
SQL 中子查询的使用大大增强了 SELECT 查询的能力,因为很多时候查询需要从结果集中获取数据,或者需要从同一个表中先计算得出一个数据结果,然后与这个数据结果(可能是某个标量,也可能是某个集合)进行比较
- #1. 由一个具体的需求,引入子查询
- #需求:谁的工资比Abel的高?
- #方式1:
- SELECT salary
- FROM employees
- WHERE last_name = 'Abel';

这里我们看到Abel的工资是11000,所以我们只需要查询工资大于11000的记录就可以了
- SELECT last_name,salary
- FROM employees
- WHERE salary > 11000;

但是这样写效率非常低,那我们也可以使用自连接的方式做
- #方式2:自连接
- SELECT e2.last_name,e2.salary
- FROM employees e1,employees e2
- WHERE e2.`salary` > e1.`salary` #多表的连接条件
- AND e1.last_name = 'Abel';

当然我们也可以使用下面的子查询来操作
- #方式3:子查询
- SELECT last_name,salary
- FROM employees
- WHERE salary > (
- SELECT salary
- FROM employees
- WHERE last_name = 'Abel'
- );

- #2. 称谓的规范:外查询(或主查询)、内查询(或子查询)
-
- - 子查询(内查询)在主查询之前一次执行完成。
- - 子查询的结果被主查询(外查询)使用 。
- - 注意事项
- - 子查询要包含在括号内
- - 将子查询放在比较条件的右侧(为了增强可读性)
- - 单行操作符对应单行子查询,多行操作符对应多行子查询
-
- #不推荐:
- #因为这种写法where中我们先看到的是一大堆SQL语句,代码的可读性变差,
- #所以最好将子查询条件放在比较条件右侧
- SELECT last_name,salary
- FROM employees
- WHERE (
- SELECT salary
- FROM employees
- WHERE last_name = 'Abel'
- ) < salary;
- 3. 子查询的分类
- 角度1:从内查询返回的结果的条目数
- 单行子查询 vs 多行子查询
-
- 角度2:内查询是否被执行多次
- 相关子查询 vs 不相关子查询
-
- 比如:相关子查询的需求:查询工资大于本部门平均工资的员工信息。
- 不相关子查询的需求:查询工资大于本公司平均工资的员工信息。

- #子查询的编写技巧(或步骤):① 从里往外写 ② 从外往里写
-
- #4. 单行子查询
- #4.1 单行操作符: = != > >= < <=
- #题目:查询工资大于149号员工工资的员工的信息
-
- SELECT employee_id,last_name,salary
- FROM employees
- WHERE salary > (
- SELECT salary
- FROM employees
- WHERE employee_id = 149
- );

- #题目:返回job_id与141号员工相同,salary比143号员工多的员工姓名,job_id和工资
-
- SELECT last_name,job_id,salary
- FROM employees
- WHERE job_id = (
- SELECT job_id
- FROM employees
- WHERE employee_id = 141
- )
- AND salary > (
- SELECT salary
- FROM employees
- WHERE employee_id = 143
- );

- #题目:返回公司工资最少的员工的last_name,job_id和salary
-
- SELECT last_name,job_id,salary
- FROM employees
- WHERE salary = (
- SELECT MIN(salary)
- FROM employees
- );

- #题目:查询与141号员工的manager_id和department_id相同的其他员工
- #的employee_id,manager_id,department_id。
- #方式1:
- SELECT employee_id,manager_id,department_id
- FROM employees
- WHERE manager_id = (
- SELECT manager_id
- FROM employees
- WHERE employee_id = 141
- )
- AND department_id = (
- SELECT department_id
- FROM employees
- WHERE employee_id = 141
- )
- AND employee_id <> 141;
- #方式2:
- SELECT employee_id,manager_id,department_id
- FROM employees
- #使用成对的查询方式
- WHERE (manager_id,department_id) = (
- SELECT manager_id,department_id
- FROM employees
- WHERE employee_id = 141
- )
- AND employee_id <> 141;

- #题目:查询最低工资大于110号部门最低工资的部门id和其最低工资
-
- SELECT department_id,MIN(salary)
- FROM employees
- WHERE department_id IS NOT NULL
- GROUP BY department_id
- HAVING MIN(salary) > (
- SELECT MIN(salary)
- FROM employees
- WHERE department_id = 110
- );

- #题目:显式员工的employee_id,last_name和location。
- #其中,若员工department_id与location_id为1800的department_id相同,
- #则location为’Canada’,其余则为’USA’。
-
- SELECT employee_id,last_name,CASE department_id WHEN (SELECT department_id FROM departments WHERE location_id = 1800) THEN 'Canada'
- ELSE 'USA' END "location"
- FROM employees;

- #4.2 子查询中的空值问题
- SELECT last_name, job_id
- FROM employees
- WHERE job_id =
- (SELECT job_id
- FROM employees
- WHERE last_name = 'Haas');

这里的话是因为我们的公司中根本就没有这个人,然后内部查询不到结构,外部查询也没有结果。
- #4.3 非法使用子查询
- #错误:Subquery returns more than 1 row
- SELECT employee_id, last_name
- FROM employees
- WHERE salary =
- (SELECT MIN(salary)
- FROM employees
- GROUP BY department_id);
这是因为我们的salary后面的等号是用于单行子查询的,而我们后面的查询结果是多行的,所以会报错 ,这里我们需要使用多行子查询的方法

- #5.多行子查询
- #5.1 多行子查询的操作符: IN ANY ALL SOME(同ANY)
-
- #5.2举例:
- # IN:
- SELECT employee_id, last_name
- FROM employees
- WHERE salary IN
- (SELECT MIN(salary)
- FROM employees
- GROUP BY department_id);

对于我们之前的单行子查询操作,我们其实也可以使用in来替换掉我们的等于号,但是这样做没有必要。
- # ANY / ALL:
- #题目:返回其它job_id中比job_id为‘IT_PROG’部门任一工资低的员工的员工号、
- #姓名、job_id 以及salary
-
- SELECT employee_id,last_name,job_id,salary
- FROM employees
- WHERE job_id <> 'IT_PROG'
- AND salary < ANY (
- SELECT salary
- FROM employees
- WHERE job_id = 'IT_PROG'
- );

- #题目:返回其它job_id中比job_id为‘IT_PROG’部门所有工资低的员工的员工号、
- #姓名、job_id 以及salary
- SELECT employee_id,last_name,job_id,salary
- FROM employees
- WHERE job_id <> 'IT_PROG'
- AND salary < ALL (
- SELECT salary
- FROM employees
- WHERE job_id = 'IT_PROG'
- );

- #查询平均工资最低的部门
- SELECT min(avg(salary))
- FROM employees
- GROUP BY department_id;
![]()
- #题目:查询平均工资最低的部门id
- #MySQL中聚合函数是不能嵌套使用的。
- #所以我们不能写类似于min(avg(salary)),但是在oracle中支持
- #方式1:
- SELECT department_id
- FROM employees
- GROUP BY department_id
- HAVING AVG(salary) = (
- SELECT MIN(avg_sal)
- FROM(
- SELECT AVG(salary) avg_sal
- FROM employees
- GROUP BY department_id
- #这里的话每一个查询出来的表需要有它的别名,这里我们取的别名是t_dept_avg_sal否则会报错
- ) t_dept_avg_sal
- );

这就相当于是我们先将这张表按照每一个部门进行排序,分别求出平均工资,然后再对我们这张表起一个别名,然后从这张新的起了别名的表中找到最小值。然后再从原来的对每个部门求出平均工资的表中找到和这个最小值相等的记录。
当然我们也可以采用下面的思路对我们上面的第一种方法进行优化,查找最小的,就是在我们按照部门求平均值的表中找到一个小于等于其他任何一个部门的记录。
- #方式2:
- SELECT department_id
- FROM employees
- GROUP BY department_id
- HAVING AVG(salary) <= ALL(
- SELECT AVG(salary) avg_sal
- FROM employees
- GROUP BY department_id
- );

- #5.3 空值问题
- #查询不是管理者的普通员工的名字
- SELECT last_name
- FROM employees
- WHERE employee_id NOT IN (
- SELECT manager_id
- FROM employees
- );

这是因为我们有一个manager_id是空值,所以导致我们查询得不到我们想要的结果。
- SELECT last_name
- FROM employees
- WHERE employee_id NOT IN (
- SELECT manager_id
- FROM employees
- where manager_id is not null
- );

所以在内查询有null值的情况下,我们的多行查询就会查不到值。
如果子查询的执行依赖于外部查询,通常情况下都是因为子查询中的表用到了外部的表,并进行了条件关联,因此每执行一次外部查询,子查询都要重新计算一次,这样的子查询就称之为 关联子查询 。
相关子查询的话,我们需要先从主查询中获取候选列,子查询使用主查询的数据,最后如果子查询的条件满足的话就返回该行。
- #6. 相关子查询
- #回顾:查询员工中工资大于公司平均工资的员工的last_name,salary和其department_id
- #6.1
- SELECT last_name,salary,department_id
- FROM employees
- WHERE salary > (
- SELECT AVG(salary)
- FROM employees
- );

就如上面6.1中的查询就是一个不相关子查询,实际上salary仅仅是和AVG所返回的那个数进行了比较,并没有达到我们相关子查询的标准。
下面的查询方式中就是一个先关子查询,因为我们在子查询中调用了外部的表e1
- #题目:查询员工中工资大于本部门平均工资的员工的last_name,salary和其department_id
- #方式1:使用相关子查询
- SELECT last_name,salary,department_id
- FROM employees e1
- WHERE salary > (
- #将部门编号等于e1中的部门标号的数据取出来然后对它们的salary进行求平均值操作。
- SELECT AVG(salary)
- FROM employees e2
- WHERE department_id = e1.`department_id`
- );
- #方式2:在FROM中声明子查询
- SELECT e.last_name,e.salary,e.department_id
- FROM employees e,(
- #按照各个部门的平均工资生成一张新的表,起别名为t_dept_avg_sal
- SELECT department_id,AVG(salary) avg_sal
- FROM employees
- GROUP BY department_id) t_dept_avg_sal
- WHERE e.department_id = t_dept_avg_sal.department_id
- AND e.salary > t_dept_avg_sal.avg_sal;

- #题目:查询员工的id,salary,按照department_name 排序
- #这里我们的部门名字的表并不在我们的employee表中,所以我们需要使用相关子查询从我们的
- #departments表中查找我们的部门名字,其判定的依据为部门的ID相等。
- #然后使用order by,asc升序排序。
- SELECT employee_id,salary
- FROM employees e
- ORDER BY (
- SELECT department_name
- FROM departments d
- WHERE e.`department_id` = d.`department_id`
- ) ASC;

- #结论:在SELECT中,除了GROUP BY 和 LIMIT之外,其他位置都可以声明子查询!
-
- SELECT ....,....,....(存在聚合函数)
- FROM ... (LEFT / RIGHT)JOIN ....ON 多表的连接条件
- (LEFT / RIGHT)JOIN ... ON ....
- WHERE 不包含聚合函数的过滤条件
- GROUP BY ...,....
- HAVING 包含聚合函数的过滤条件
- ORDER BY ....,...(ASC / DESC )
- LIMIT ...,....
- #题目:若employees表中employee_id与job_history表中employee_id相同的数目不小于2,
- #输出这些相同id的员工的employee_id,last_name和其job_id
- #也就是输出这些员工跳转的次数大于2的员工信息
-
- SELECT *
- FROM job_history;
-
- SELECT employee_id,last_name,job_id
- FROM employees e
- WHERE 2 <= (
- #统计对应的员工在job_history表中出现的次数
- SELECT COUNT(*)
- FROM job_history j
- WHERE e.`employee_id` = j.`employee_id`
- );

关联子查询通常也会和 EXISTS操作符一起来使用,用来检查在子查询中是否存在满足条件的行。
如果在子查询中不存在满足条件的行:
条件返回 FALSE
继续在子查询中查找
如果在子查询中存在满足条件的行:
不在子查询中继续查找
条件返回 TRUE
NOT EXISTS关键字表示如果不存在某种条件,则返回TRUE,否则返回FALSE。
- #6.2 EXISTS 与 NOT EXISTS关键字
-
- #题目:查询公司管理者的employee_id,last_name,job_id,department_id信息
- #方式1:自连接
- #管理者的ID等于它自己,利用这个条件进行查询
- SELECT DISTINCT mgr.employee_id,mgr.last_name,mgr.job_id,mgr.department_id
- FROM employees emp JOIN employees mgr
- ON emp.manager_id = mgr.employee_id;

- #方式2:子查询
-
- SELECT employee_id,last_name,job_id,department_id
- FROM employees
- WHERE employee_id IN (
- SELECT DISTINCT manager_id
- FROM employees
- );

- #方式3:使用EXISTS
- SELECT employee_id,last_name,job_id,department_id
- FROM employees e1
- WHERE EXISTS (
- SELECT *
- FROM employees e2
- WHERE e1.`employee_id` = e2.`manager_id`
- );es
- );

- #题目:查询departments表中,不存在于employees表中的部门的department_id和department_name
- #就是查找哪些部门是没有员工的
- #方式1:
- SELECT d.department_id,d.department_name
- FROM employees e RIGHT JOIN departments d
- ON e.`department_id` = d.`department_id`
- WHERE e.`department_id` IS NULL;
- #方式2:
- SELECT department_id,department_name
- FROM departments d
- WHERE NOT EXISTS (
- SELECT *
- FROM employees e
- WHERE d.`department_id` = e.`department_id`
- );
-
- SELECT COUNT(*)
- FROM departments;
