《MySQL 基础篇》四:查询操作
Author: ACatSmiling
Since: 2024-09-19
多表查询
多表查询
,也称为关联查询
,指两个或更多个表一起完成查询操作。
前提条件:这些一起查询的表之间是有关系的(一对一、一对多),它们之间一定是有关联字段,这个关联字段可能建立了外键,也可能没有建立外键。比如:员工表和部门表,这两个表依靠 "部门编号" 进行关联。
笛卡尔积
笛卡尔乘积
是一个数学运算。假设有两个集合 X 和 Y,那么 X 和 Y 的笛卡尔积就是 X 和 Y 的所有可能组合,也就是第一个对象来自于 X,第二个对象来自于 Y 的所有可能。组合的个数即为两个集合中元素个数的乘积数。
SQL-92 中,笛卡尔积也称为交叉连接 ,英文是 CROSS JOIN 。在 SQL-99 中也是使用 CROSS JOIN 表示交叉连接,它的作用就是可以把任意表进行连接,即使这两张表不相关。在 MySQL 中如下情况会出现笛卡尔积:
# 查询员工姓名和所在部门名称 mysql> SELECT last_name, department_name FROM employees, departments; +-------------+----------------------+ | last_name | department_name | +-------------+----------------------+ | King | Payroll | | King | Recruiting | | King | Retail Sales | | King | Government Sales | | King | IT Helpdesk | | King | NOC | | King | IT Support | | King | Operations | | King | Contracting | | King | Construction | | King | Manufacturing | | King | Benefits | | King | Shareholder Services | | King | Control And Credit | | King | Corporate Tax | | King | Treasury | | King | Accounting | | King | Finance | | King | Executive | | King | Sales | | King | Public Relations | | King | IT | | King | Shipping | | King | Human Resources | | King | Purchasing | | King | Marketing | | King | Administration | | Kochhar | Payroll | | ...... | +-------------+----------------------+ 2889 rows in set (0.00 sec) mysql> SELECT last_name, department_name FROM employees CROSS JOIN departments; +-------------+----------------------+ | last_name | department_name | +-------------+----------------------+ | King | Payroll | | King | Recruiting | | King | Retail Sales | | King | Government Sales | | King | IT Helpdesk | | King | NOC | | King | IT Support | | King | Operations | | King | Contracting | | King | Construction | | King | Manufacturing | | King | Benefits | | King | Shareholder Services | | King | Control And Credit | | King | Corporate Tax | | King | Treasury | | King | Accounting | | King | Finance | | King | Executive | | King | Sales | | King | Public Relations | | King | IT | | King | Shipping | | King | Human Resources | | King | Purchasing | | King | Marketing | | King | Administration | | Kochhar | Payroll | | ...... | +-------------+----------------------+ 2889 rows in set (0.00 sec) mysql> SELECT last_name, department_name FROM employees INNER JOIN departments; +-------------+----------------------+ | last_name | department_name | +-------------+----------------------+ | King | Payroll | | King | Recruiting | | King | Retail Sales | | King | Government Sales | | King | IT Helpdesk | | King | NOC | | King | IT Support | | King | Operations | | King | Contracting | | King | Construction | | King | Manufacturing | | King | Benefits | | King | Shareholder Services | | King | Control And Credit | | King | Corporate Tax | | King | Treasury | | King | Accounting | | King | Finance | | King | Executive | | King | Sales | | King | Public Relations | | King | IT | | King | Shipping | | King | Human Resources | | King | Purchasing | | King | Marketing | | King | Administration | | Kochhar | Payroll | | ...... | +-------------+----------------------+ 2889 rows in set (0.00 sec) mysql> SELECT last_name, department_name FROM employees JOIN departments; +-------------+----------------------+ | last_name | department_name | +-------------+----------------------+ | King | Payroll | | King | Recruiting | | King | Retail Sales | | King | Government Sales | | King | IT Helpdesk | | King | NOC | | King | IT Support | | King | Operations | | King | Contracting | | King | Construction | | King | Manufacturing | | King | Benefits | | King | Shareholder Services | | King | Control And Credit | | King | Corporate Tax | | King | Treasury | | King | Accounting | | King | Finance | | King | Executive | | King | Sales | | King | Public Relations | | King | IT | | King | Shipping | | King | Human Resources | | King | Purchasing | | King | Marketing | | King | Administration | | Kochhar | Payroll | | ...... | +-------------+----------------------+ 2889 rows in set (0.00 sec)
上述查询结果,称为
笛卡尔积的错误
,原因:
mysql> SELECT COUNT(employee_id) FROM employees; +--------------------+ | COUNT(employee_id) | +--------------------+ | 107 | +--------------------+ 1 row in set (0.00 sec) mysql> SELECT COUNT(department_id) FROM departments; +----------------------+ | COUNT(department_id) | +----------------------+ | 27 | +----------------------+ 1 row in set (0.00 sec) mysql> SELECT 107 * 27 FROM dual; +----------+ | 107 * 27 | +----------+ | 2889 | +----------+ 1 row in set (0.00 sec)
笛卡尔积的错误会在下面条件下产生:
- 省略多个表的连接条件(或关联条件)。
- 连接条件(或关联条件)无效。
- 所有表中的所有行互相连接。
为了避免笛卡尔积, 可以在 WHERE 加入有效的连接条件。加入连接条件后,查询语法:
SELECT table1.column, table2.column FROM table1, table2 WHERE table1.column1 = table2.column2; # 连接条件
连接 n 个表,至少需要 n - 1 个连接条件。
查询员工的姓名及其部门名称,正确的写法:
mysql> SELECT last_name, department_name FROM employees, departments WHERE employees.department_id = departments.department_id; +-------------+------------------+ | last_name | department_name | +-------------+------------------+ | Whalen | Administration | | Hartstein | Marketing | | Fay | Marketing | | Raphaely | Purchasing | +-------------+------------------+ 106 rows in set (0.00 sec)
在不同表中有相同字段时,需要在字段前加上所属表名的前缀。多表查询时,建议对每一个查询字段,都添加上所属表名的前缀,这样一是避免相同字段不明确,二是可以对查询进行优化,以提高查询效率。
需要注意的是,如果使用了表的别名,在查询字段中、过滤条件中就只能使用别名进行代替,不能使用原有的表名,否则就会报错,这涉及到 SQL 语句不同分段的顺序执行问题。
阿里开发规范 :
【 强制 】对于数据库中表记录的查询和变更,只要涉及多个表,都需要在列名前加表的别名(或表名)进行限定。说明 :对多表进行查询记录、更新记录、删除记录时,如果对操作列没有限定表的别名(或表名),并且操作列在多个表中存在时,就会抛异常。
正例 :SELECT t1.name FROM table_first AS t1, table_second AS t2 WHERE t1.id = t2.id;
反例 :在某业务中,由于多表关联查询语句没有加表的别名(或表名)的限制,正常运行两年后,最近在某个表中增加一个同名字段,在预发布环境做数据库变更后,线上查询语句出现出 1052 异常:Column 'name' in field list is ambiguous。
多表查询的分类
分类 1:等值连接 vs 非等值连接
等值连接
mysql> SELECT employees.employee_id, employees.last_name, employees.department_id, departments.department_id, departments.location_id FROM employees, departments WHERE employees.department_id = departments.department_id; +-------------+-------------+---------------+---------------+-------------+ | employee_id | last_name | department_id | department_id | location_id | +-------------+-------------+---------------+---------------+-------------+ | 103 | Hunold | 60 | 60 | 1400 | | 104 | Ernst | 60 | 60 | 1400 | | 105 | Austin | 60 | 60 | 1400 | | 106 | Pataballa | 60 | 60 | 1400 | | 107 | Lorentz | 60 | 60 | 1400 | +-------------+-------------+---------------+---------------+-------------+ 106 rows in set (0.00 sec)
非等值连接
mysql> SELECT e.last_name, e.salary, j.grade_level FROM employees e, job_grades j WHERE e.salary BETWEEN j.lowest_sal AND j.highest_sal; +-------------+----------+-------------+ | last_name | salary | grade_level | +-------------+----------+-------------+ | King | 24000.00 | E | | Kochhar | 17000.00 | E | | De Haan | 17000.00 | E | | Hunold | 9000.00 | C | | Ernst | 6000.00 | C | | Austin | 4800.00 | B | | Pataballa | 4800.00 | B | | Lorentz | 4200.00 | B | | Greenberg | 12000.00 | D | | Gietz | 8300.00 | C | +-------------+----------+-------------+ 107 rows in set (0.00 sec)
分类 2:自连接 vs 非自连接
table1 和 table2 本质上是同一张表,只是用取别名的方式虚拟成两张表以代表不同的意义,然后两个表再进行内连接、外连接等查询。
mysql> SELECT CONCAT(worker.last_name, ' works for ', manager.last_name) FROM employees worker, employees manager WHERE worker.manager_id = manager.employee_id; +------------------------------------------------------------+ | CONCAT(worker.last_name, ' works for ', manager.last_name) | +------------------------------------------------------------+ | Kochhar works for King | | De Haan works for King | | Hunold works for De Haan | | Ernst works for Hunold | | Austin works for Hunold | | Pataballa works for Hunold | | Lorentz works for Hunold | | Gietz works for Higgins | +------------------------------------------------------------+ 106 rows in set (0.00 sec)
分类 3:内连接 vs 外连接
内连接
:合并具有同一列的两个以上的表的行,结果集中不包含一个表与另一个表不匹配的行。
外连接
:两个表在连接过程中除了返回满足连接条件的行以外,还返回左(或右)表中不满足条件的行 ,这种连接称为左(或右) 外连接。没有匹配的行时,结果表中相应的列为空(NULL)。
-
如果是左外连接,则连接条件中左边的表也称为
主表
,右边的表称为从表
。 -
如果是右外连接,则连接条件中右边的表也称为
主表
,左边的表称为从表
。
SQL-99 语法实现多表查询
基本语法
使用JOIN...ON
子句创建连接的语法结构:
SELECT table1.column, table2.column,table3.column FROM table1 JOIN table2 ON table1 和 table2 的连接条件 JOIN table3 ON table2 和 table3 的连接条件;
语法说明:
- 可以使用 ON 子句指定额外的连接条件。
- 这个连接条件是与其它条件分开的。
- ON 子句使语句具有更高的易读性。
- 关键字 JOIN、INNER JOIN、CROSS JOIN 的含义是一样的,都表示内连接。
它的嵌套逻辑类似我们使用的 FOR 循环:
for t1 in table1: for t2 in table2: if condition1: for t3 in table3: if condition2: output t1 + t2 + t3
SQL-99 采用的这种嵌套结构非常清爽、层次性更强、可读性更强,即使再多的表进行连接也都清晰可见。如果采用 SQL-92,可读性就会大打折扣。
内连接(INNER JOIN)的实现
语法:
SELECT 字段列表 FROM A表 INNER JOIN B表 ON 关联条件 WHERE 等其他子句;
mysql> SELECT e.employee_id, e.last_name, e.department_id, d.department_id, d.location_id FROM employees e JOIN departments d ON (e.department_id = d.department_id); +-------------+-------------+---------------+---------------+-------------+ | employee_id | last_name | department_id | department_id | location_id | +-------------+-------------+---------------+---------------+-------------+ | 103 | Hunold | 60 | 60 | 1400 | | 104 | Ernst | 60 | 60 | 1400 | | 105 | Austin | 60 | 60 | 1400 | | 106 | Pataballa | 60 | 60 | 1400 | | 107 | Lorentz | 60 | 60 | 1400 | | 204 | Baer | 70 | 70 | 2700 | +-------------+-------------+---------------+---------------+-------------+ 106 rows in set (0.00 sec) mysql> SELECT employee_id, city, department_name FROM employees e JOIN departments d ON d.department_id = e.department_id JOIN locations l ON d.location_id = l.location_id; +-------------+---------------------+------------------+ | employee_id | city | department_name | +-------------+---------------------+------------------+ | 200 | Seattle | Administration | | 201 | Toronto | Marketing | | 202 | Toronto | Marketing | | 114 | Seattle | Purchasing | | 115 | Seattle | Purchasing | | 116 | Seattle | Purchasing | | 117 | Seattle | Purchasing | | 118 | Seattle | Purchasing | | 119 | Seattle | Purchasing | | 203 | London | Human Resources | | 120 | South San Francisco | Shipping | | 206 | Seattle | Accounting | +-------------+---------------------+------------------+ 106 rows in set (0.00 sec)
外连接(OUTER JOIN)的实现
左外连接(LEFT OUTER JOIN)
语法:
# 实现查询结果是 A SELECT 字段列表 FROM A表 LEFT JOIN B表 ON 关联条件 WHERE 等其他子句;
mysql> SELECT e.last_name, e.department_id, d.department_name FROM employees e LEFT OUTER JOIN departments d ON (e.department_id = d.department_id); +-------------+---------------+------------------+ | last_name | department_id | department_name | +-------------+---------------+------------------+ | King | 90 | Executive | | Kochhar | 90 | Executive | | De Haan | 90 | Executive | | Hunold | 60 | IT | | Ernst | 60 | IT | | Livingston | 80 | Sales | | Grant | NULL | NULL | | Johnson | 80 | Sales | | Gietz | 110 | Accounting | +-------------+---------------+------------------+ 107 rows in set (0.00 sec)
内连接查询结果是 106 条记录,左外连接是 107 条记录,有一个员工不存在部门信息,内连接的时候,不会显示这个员工的信息。
右外连接(RIGHT OUTER JOIN)
语法:
# 实现查询结果是 B SELECT 字段列表 FROM A表 RIGHT JOIN B表 ON 关联条件 WHERE 等其他子句;
mysql> SELECT e.last_name, e.department_id, d.department_name FROM employees e RIGHT OUTER JOIN departments d ON (e.department_id = d.department_id); +-------------+---------------+----------------------+ | last_name | department_id | department_name | +-------------+---------------+----------------------+ | Whalen | 10 | Administration | | Hartstein | 20 | Marketing | | Fay | 20 | Marketing | | Raphaely | 30 | Purchasing | | Khoo | 30 | Purchasing | | Baida | 30 | Purchasing | | Tobias | 30 | Purchasing | | Himuro | 30 | Purchasing | | NULL | NULL | IT Support | | NULL | NULL | NOC | | NULL | NULL | IT Helpdesk | | NULL | NULL | Government Sales | | NULL | NULL | Retail Sales | | NULL | NULL | Recruiting | | NULL | NULL | Payroll | +-------------+---------------+----------------------+ 122 rows in set (0.00 sec)
右连接查询时,有些部门没有员工,也会显示出部门的信息。
需要注意的是,LEFT JOIN 和 RIGHT JOIN 只存在于 SQL-99 及以后的标准中,在 SQL-92 中不存在,只能用(+)表示。
SQL-92 中采用(+)代表从表所在的位置。即左或右外连接中,(+)表示哪个是从表。Oracle 对 SQL-92 支持较好,而 MySQL 则不支持 SQL-92 的外连接。
# 左外连接 SELECT last_name,department_name FROM employees ,departments WHERE employees.department_id = departments.department_id(+); # 右外连接 SELECT last_name,department_name FROM employees ,departments WHERE employees.department_id(+) = departments.department_id;
满外连接(FULL OUTER JOIN)
满外连接的结果 = 左右表匹配的数据 + 左表没有匹配到的数据 + 右表没有匹配到的数据。
- SQL-99 是支持满外连接的。使用 FULL JOIN 或 FULL OUTER JOIN 来实现。
- 需要注意的是:MySQL 不支持 FULL JOIN,但是可以用 LEFT JOIN UNION RIGHT JOIN 代替。
在 SQL-92 中,只有左外连接和右外连接,没有满(或全)外连接。
合并查询结果
利用 UNION 关键字可以合并查询结果,可以给出多条 SELECT 语句,并将它们的结果组合成单个结果集。合并时,两个表对应的列数和数据类型必须相同,并且相互对应。各个 SELECT 语句之间使用 UNION 或 UNION ALL 关键字分隔。
语法格式:
SELECT column,... FROM table1 UNION [ALL] SELECT column,... FROM table2;
UNION 操作符
UNION
操作符返回两个查询的结果集的并集,并去除重复记录。
UNION ALL 操作符
UNION ALL
操作符返回两个查询的结果集的并集,对于两个结果集的重复部分,不去重。
执行 UNION ALL 语句时所需要的资源比 UNION 语句少。如果明确知道合并数据后的结果数据不存在重复数据,或者不需要去除重复的数据,则尽量使用 UNION ALL 语句,以提高数据查询的效率。
举例,查询邮箱包含 a 或者部门编号大于 90 的员工信息:
mysql> SELECT * FROM employees WHERE email LIKE '%a%' OR department_id > 90; +-------------+-------------+------------+----------+--------------------+------------+------------+----------+----------------+------------+---------------+ | employee_id | first_name | last_name | email | phone_number | hire_date | job_id | salary | commission_pct | manager_id | department_id | +-------------+-------------+------------+----------+--------------------+------------+------------+----------+----------------+------------+---------------+ | 101 | Neena | Kochhar | NKOCHHAR | 515.123.4568 | 1989-09-21 | AD_VP | 17000.00 | NULL | 100 | 90 | | 102 | Lex | De Haan | LDEHAAN | 515.123.4569 | 1993-01-13 | AD_VP | 17000.00 | NULL | 100 | 90 | | 206 | William | Gietz | WGIETZ | 515.123.8181 | 1994-06-07 | AC_ACCOUNT | 8300.00 | NULL | 205 | 110 | +-------------+-------------+------------+----------+--------------------+------------+------------+----------+----------------+------------+---------------+ 67 rows in set (0.00 sec)
使用 UNION 写法:
mysql> SELECT * FROM employees WHERE email LIKE '%a%' UNION SELECT * FROM employees WHERE department_id > 90; +-------------+-------------+------------+----------+--------------------+------------+------------+----------+----------------+------------+---------------+ | employee_id | first_name | last_name | email | phone_number | hire_date | job_id | salary | commission_pct | manager_id | department_id | +-------------+-------------+------------+----------+--------------------+------------+------------+----------+----------------+------------+---------------+ | 101 | Neena | Kochhar | NKOCHHAR | 515.123.4568 | 1989-09-21 | AD_VP | 17000.00 | NULL | 100 | 90 | | 102 | Lex | De Haan | LDEHAAN | 515.123.4569 | 1993-01-13 | AD_VP | 17000.00 | NULL | 100 | 90 | | 206 | William | Gietz | WGIETZ | 515.123.8181 | 1994-06-07 | AC_ACCOUNT | 8300.00 | NULL | 205 | 110 | +-------------+-------------+------------+----------+--------------------+------------+------------+----------+----------------+------------+---------------+ 67 rows in set (0.00 sec)
7 种 SQL JOINS 的实现
中图:内连接 A ∩ B。
mysql> SELECT employee_id, last_name, department_name FROM employees e JOIN departments d ON e.department_id = d.department_id; +-------------+-------------+------------------+ | employee_id | last_name | department_name | +-------------+-------------+------------------+ | 200 | Whalen | Administration | | 201 | Hartstein | Marketing | | 202 | Fay | Marketing | | 205 | Higgins | Accounting | | 206 | Gietz | Accounting | +-------------+-------------+------------------+ 106 rows in set (0.00 sec)
左上图:左外连接。
mysql> SELECT employee_id, last_name, department_name FROM employees e LEFT JOIN departments d ON e.department_id = d.department_id; +-------------+-------------+------------------+ | employee_id | last_name | department_name | +-------------+-------------+------------------+ | 100 | King | Executive | | 101 | Kochhar | Executive | | 102 | De Haan | Executive | | 178 | Grant | NULL | | 205 | Higgins | Accounting | | 206 | Gietz | Accounting | +-------------+-------------+------------------+ 107 rows in set (0.00 sec)
右上图:右外连接。
mysql> SELECT employee_id, last_name, department_name FROM employees e RIGHT JOIN departments d ON e.department_id = d.`department_id`; +-------------+-------------+----------------------+ | employee_id | last_name | department_name | +-------------+-------------+----------------------+ | 200 | Whalen | Administration | | 201 | Hartstein | Marketing | | 202 | Fay | Marketing | | NULL | NULL | IT Helpdesk | | NULL | NULL | Government Sales | | NULL | NULL | Retail Sales | | NULL | NULL | Recruiting | | NULL | NULL | Payroll | +-------------+-------------+----------------------+ 122 rows in set (0.00 sec)
左中图:A - A ∩ B。
mysql> SELECT employee_id, last_name, department_name FROM employees e LEFT JOIN departments d ON e.department_id = d.department_id WHERE d.department_id IS NULL; +-------------+-----------+-----------------+ | employee_id | last_name | department_name | +-------------+-----------+-----------------+ | 178 | Grant | NULL | +-------------+-----------+-----------------+ 1 row in set (0.00 sec)
右中图:B - A ∩ B。
mysql> SELECT employee_id, last_name, department_name FROM employees e RIGHT JOIN departments d ON e.department_id = d.department_id WHERE e.department_id IS NULL; +-------------+-----------+----------------------+ | employee_id | last_name | department_name | +-------------+-----------+----------------------+ | NULL | NULL | Treasury | | NULL | NULL | Corporate Tax | | NULL | NULL | Control And Credit | | NULL | NULL | Shareholder Services | | NULL | NULL | Benefits | | NULL | NULL | Manufacturing | | NULL | NULL | Construction | | NULL | NULL | Contracting | | NULL | NULL | Operations | | NULL | NULL | IT Support | | NULL | NULL | NOC | | NULL | NULL | IT Helpdesk | | NULL | NULL | Government Sales | | NULL | NULL | Retail Sales | | NULL | NULL | Recruiting | | NULL | NULL | Payroll | +-------------+-----------+----------------------+ 16 rows in set (0.00 sec)
左下图:满外连接,左中图 + 右上图,A∪B。
# 没有去重操作,效率高 mysql> SELECT employee_id, last_name, department_name FROM employees e LEFT JOIN departments d ON e.department_id = d.department_id WHERE d.department_id IS NULL UNION ALL SELECT employee_id, last_name, department_name FROM employees e RIGHT JOIN departments d ON e.department_id = d.department_id; +-------------+-------------+----------------------+ | employee_id | last_name | department_name | +-------------+-------------+----------------------+ | 178 | Grant | NULL | | 200 | Whalen | Administration | | 201 | Hartstein | Marketing | | 202 | Fay | Marketing | | 114 | Raphaely | Purchasing | | NULL | NULL | Construction | | NULL | NULL | Contracting | | NULL | NULL | Operations | | NULL | NULL | IT Support | | NULL | NULL | NOC | | NULL | NULL | IT Helpdesk | | NULL | NULL | Government Sales | | NULL | NULL | Retail Sales | | NULL | NULL | Recruiting | | NULL | NULL | Payroll | +-------------+-------------+----------------------+ 123 rows in set (0.00 sec)
右下图:左中图 + 右中图,A ∪B - A ∩ B,或者 (A - A ∩ B) ∪ (B - A ∩ B)。
mysql> SELECT employee_id, last_name, department_name FROM employees e LEFT JOIN departments d ON e.department_id = d.department_id WHERE d.department_id IS NULL UNION ALL SELECT employee_id, last_name, department_name FROM employees e RIGHT JOIN departments d ON e.department_id = d.department_id WHERE e.department_id IS NULL; +-------------+-----------+----------------------+ | employee_id | last_name | department_name | +-------------+-----------+----------------------+ | 178 | Grant | NULL | | NULL | NULL | Treasury | | NULL | NULL | Corporate Tax | | NULL | NULL | Control And Credit | | NULL | NULL | Shareholder Services | | NULL | NULL | Benefits | | NULL | NULL | Manufacturing | | NULL | NULL | Construction | | NULL | NULL | Contracting | | NULL | NULL | Operations | | NULL | NULL | IT Support | | NULL | NULL | NOC | | NULL | NULL | IT Helpdesk | | NULL | NULL | Government Sales | | NULL | NULL | Retail Sales | | NULL | NULL | Recruiting | | NULL | NULL | Payroll | +-------------+-----------+----------------------+ 17 rows in set (0.00 sec)
左中图语法格式:
# 实现 A - A ∩ B SELECT 字段列表 FROM A表 LEFT JOIN B表 ON 关联条件 WHERE 从表关联字段 IS NULL AND 等其他子句;
右中图语法格式:
# 实现 B - A ∩ B SELECT 字段列表 FROM A表 RIGHT JOIN B表 ON 关联条件 WHERE 从表关联字段 IS NULL AND 等其他子句;
左下图语法格式:
# 实现查询结果是 A ∪ B,用左外的 A,union 右外的 B SELECT 字段列表 FROM A表 LEFT JOIN B表 ON 关联条件 WHERE 等其他子句 UNION SELECT 字段列表 FROM A表 RIGHT JOIN B表 ON 关联条件 WHERE 等其他子句;
SQL-99 语法新特性
自然连接
SQL-99 在 SQL-92 的基础上提供了一些特殊语法,比如NATURAL JOIN
用来表示自然连接。我们可以把自然连接理解为 SQL-92 中的等值连接,它会自动查询两张连接表中所有相同的字段,然后进行等值连接。
在 SQL-92 标准中:
mysql> SELECT employee_id, last_name, department_name FROM employees e JOIN departments d ON e.department_id = d.department_id AND e.manager_id = d.manager_id; +-------------+------------+-----------------+ | employee_id | last_name | department_name | +-------------+------------+-----------------+ | 202 | Fay | Marketing | | 115 | Khoo | Purchasing | | 116 | Baida | Purchasing | | 117 | Tobias | Purchasing | | 118 | Himuro | Purchasing | | 119 | Colmenares | Purchasing | | 129 | Bissot | Shipping | | 130 | Atkinson | Shipping | | 131 | Marlow | Shipping | | 132 | Olson | Shipping | | 184 | Sarchand | Shipping | | 185 | Bull | Shipping | | 186 | Dellinger | Shipping | | 187 | Cabrio | Shipping | | 104 | Ernst | IT | | 105 | Austin | IT | | 106 | Pataballa | IT | | 107 | Lorentz | IT | | 150 | Tucker | Sales | | 151 | Bernstein | Sales | | 152 | Hall | Sales | | 153 | Olsen | Sales | | 154 | Cambrault | Sales | | 155 | Tuvault | Sales | | 101 | Kochhar | Executive | | 102 | De Haan | Executive | | 109 | Faviet | Finance | | 110 | Chen | Finance | | 111 | Sciarra | Finance | | 112 | Urman | Finance | | 113 | Popp | Finance | | 206 | Gietz | Accounting | +-------------+------------+-----------------+ 32 rows in set (0.00 sec)
在 SQL-99 中可以写成:
mysql> SELECT employee_id, last_name, department_name FROM employees e NATURAL JOIN departments d; +-------------+------------+-----------------+ | employee_id | last_name | department_name | +-------------+------------+-----------------+ | 202 | Fay | Marketing | | 115 | Khoo | Purchasing | | 116 | Baida | Purchasing | | 117 | Tobias | Purchasing | | 118 | Himuro | Purchasing | | 119 | Colmenares | Purchasing | | 129 | Bissot | Shipping | | 130 | Atkinson | Shipping | | 131 | Marlow | Shipping | | 132 | Olson | Shipping | | 184 | Sarchand | Shipping | | 185 | Bull | Shipping | | 186 | Dellinger | Shipping | | 187 | Cabrio | Shipping | | 104 | Ernst | IT | | 105 | Austin | IT | | 106 | Pataballa | IT | | 107 | Lorentz | IT | | 150 | Tucker | Sales | | 151 | Bernstein | Sales | | 152 | Hall | Sales | | 153 | Olsen | Sales | | 154 | Cambrault | Sales | | 155 | Tuvault | Sales | | 101 | Kochhar | Executive | | 102 | De Haan | Executive | | 109 | Faviet | Finance | | 110 | Chen | Finance | | 111 | Sciarra | Finance | | 112 | Urman | Finance | | 113 | Popp | Finance | | 206 | Gietz | Accounting | +-------------+------------+-----------------+ 32 rows in set (0.00 sec)
USING 连接
当进行连接的时候,SQL-99 还支持使用USING
指定数据表里的同名字段进行等值连接,但是只能配合 JOIN 一起使用。比如:
mysql> SELECT employee_id, last_name, department_name FROM employees e JOIN departments d USING (department_id); +-------------+-------------+------------------+ | employee_id | last_name | department_name | +-------------+-------------+------------------+ | 200 | Whalen | Administration | | 201 | Hartstein | Marketing | | 202 | Fay | Marketing | | 205 | Higgins | Accounting | | 206 | Gietz | Accounting | +-------------+-------------+------------------+ 106 rows in set (0.00 sec)
与自然连接 NATURAL JOIN 不同的是,USING 指定了具体的相同的字段名称,需要在 USING 的括号 () 中填入要指定的同名字段。同时使用 JOIN...USING 可以简化 JOIN ON 的等值连接。它与下面的 SQL 查询结果是相同的:
mysql> SELECT employee_id, last_name, department_name FROM employees e, departments d WHERE e.department_id = d.department_id;
小结
表连接的约束条件可以有三种方式:WHERE,ON,USING。
- WHERE:适用于所有关联查询。
- ON :只能和 JOIN 一起使用,只能写关联条件。虽然关联条件可以并到 WHERE 中和其他条件一起写,但分开写可读性更好。
- USING:只能和 JOIN 一起使用,要求两个关联字段在关联表中名称一致,而且只能表示关联字段值相等。
# 把关联条件写在 where 后面 mysql> SELECT last_name, department_name FROM employees, departments WHERE employees.department_id = departments.department_id; # 把关联条件写在 on 后面,只能和 JOIN 一起使用 mysql> SELECT last_name, department_name FROM employees INNER JOIN departments ON employees.department_id = departments.department_id; mysql> SELECT last_name, department_name FROM employees CROSS JOIN departments ON employees.department_id = departments.department_id; mysql> SELECT last_name, department_name FROM employees JOIN departments ON employees.department_id = departments.department_id; # 把关联字段写在 using() 中,只能和 JOIN 一起使用,而且两个表中的关联字段必须名称相同,而且只能表示 = # 查询员工姓名与基本工资 mysql> SELECT last_name, job_title FROM employees INNER JOIN jobs USING(job_id); # n 张表关联,至少需要 n-1 个关联条件 # 查询员工姓名,基本工资,部门名称 mysql> SELECT last_name, job_title, department_name FROM employees, departments, jobs WHERE employees.department_id = departments.department_id AND employees.job_id = jobs.job_id; mysql> SELECT last_name, job_title, department_name FROM employees INNER JOIN departments INNER JOIN jobs ON employees.department_id = departments.department_id AND employees.job_id = jobs.job_id;
注意:
要控制连接表的数量
。多表连接就相当于嵌套 for 循环一样,非常消耗资源,会让 SQL 查询性能下降得很严重,因此不要连接不必要的表。在许多 DBMS 中,也都会有最大连接表的限#制。
连表查询的条件问题
正确写法:
SELECT ledger.credit_code AS '统一社会信用代码', info.company_name AS '企业名称', ledger.hazard_ledger_id AS '主键', (CASE ledger.danger_level WHEN 1 THEN '一级' WHEN 2 THEN '二级' WHEN 3 THEN '三级' WHEN 4 THEN '四级' ELSE ledger.danger_level END) AS '隐患等级', (CASE ledger.manager_level WHEN 0 THEN '集团级' WHEN 1 THEN '公司级' WHEN 2 THEN '企业级' WHEN 3 THEN '车间级' WHEN 4 THEN '装置级' ELSE ledger.manager_level END) AS '管理级别', ledger.danger_type AS '隐患类型分类编码', (CASE WHEN ledger.danger_type LIKE '1-%' THEN '健康' WHEN ledger.danger_type LIKE '2-%' THEN '安全' WHEN ledger.danger_type LIKE '3-%' THEN '环保' WHEN ledger.danger_type LIKE '4-%' THEN '节能' WHEN ledger.danger_type LIKE '5-%' THEN '低碳' WHEN ledger.danger_type LIKE '6-%' THEN '安保' ELSE ledger.danger_type END) AS '隐患类型一级分类', -- ledger.danger_name AS '隐患名称', ledger.danger_description AS '隐患描述', ledger.danger_src AS '隐患来源', ledger.danger_type AS '隐患类型(专业)', ledger.danger_type1 AS '隐患类型(危害因素)', ledger.danger_type2 AS '隐患类型(体系)', ledger.find_by_name AS '发现人', ledger.find_date AS '发现时间', ledger.registration_by_name AS '登记人', ledger.regist_time AS '登记时间', '自建系统' AS '系统类型', NOW() AS '导出时间' FROM dp_phd_ledger ledger LEFT JOIN company_info info ON ledger.credit_code = info.credit_code AND info.deleted = 0 # 注意此处 WHERE ledger.deleted = 0 AND ledger.danger_type IS NOT NULL AND ledger.find_date >= '2023-05-01 00:00:00' ORDER BY ledger.credit_code, DATE_FORMAT(ledger.find_date, '%Y-%m-%d')
错误写法:
SELECT ledger.credit_code AS '统一社会信用代码', info.company_name AS '企业名称', ledger.hazard_ledger_id AS '主键', (CASE ledger.danger_level WHEN 1 THEN '一级' WHEN 2 THEN '二级' WHEN 3 THEN '三级' WHEN 4 THEN '四级' ELSE ledger.danger_level END) AS '隐患等级', (CASE ledger.manager_level WHEN 0 THEN '集团级' WHEN 1 THEN '公司级' WHEN 2 THEN '企业级' WHEN 3 THEN '车间级' WHEN 4 THEN '装置级' ELSE ledger.manager_level END) AS '管理级别', ledger.danger_type AS '隐患类型分类编码', (CASE WHEN ledger.danger_type LIKE '1-%' THEN '健康' WHEN ledger.danger_type LIKE '2-%' THEN '安全' WHEN ledger.danger_type LIKE '3-%' THEN '环保' WHEN ledger.danger_type LIKE '4-%' THEN '节能' WHEN ledger.danger_type LIKE '5-%' THEN '低碳' WHEN ledger.danger_type LIKE '6-%' THEN '安保' ELSE ledger.danger_type END) AS '隐患类型一级分类', -- ledger.danger_name AS '隐患名称', ledger.danger_description AS '隐患描述', ledger.danger_src AS '隐患来源', ledger.danger_type AS '隐患类型(专业)', ledger.danger_type1 AS '隐患类型(危害因素)', ledger.danger_type2 AS '隐患类型(体系)', ledger.find_by_name AS '发现人', ledger.find_date AS '发现时间', ledger.registration_by_name AS '登记人', ledger.regist_time AS '登记时间', '自建系统' AS '系统类型', NOW() AS '导出时间' FROM dp_phd_ledger ledger LEFT JOIN company_info info ON ledger.credit_code = info.credit_code # 注意此处 WHERE info.deleted = 0 AND ledger.deleted = 0 AND ledger.danger_type IS NOT NULL AND ledger.find_date >= '2023-05-01 00:00:00' ORDER BY ledger.credit_code, DATE_FORMAT(ledger.find_date, '%Y-%m-%d')
对比正确的连表查询条件写法,错误的写法中,将右表的 info.deleted = 0 条件放到了 WHERE 中,正确的写法则是放在了 ON 中,原因:dp_phd_ledger 为左表,company_info 表为右表,LEFT JOIN 时,company_info 表可能没有数据,此时,company_info 表的 deleted 字段为 NULL,不满足 deleted = 0 条件,数据会丢失
。而将 info.deleted = 0 条件放到 ON 中,会先过滤 company_info 表的数据,此时不会丢失数据。
子查询
子查询指一个查询语句嵌套在另一个查询语句内部的查询,这个特性从 MySQL 4.1 开始引入。
SQL 中子查询的使用大大增强了 SELECT 查询的能力,因为很多时候查询需要从结果集中获取数据,或者需要从同一个表中先计算得出一个数据结果,然后与这个数据结果(可能是某个标量,也可能是某个集合)进行比较。
需求分析与问题解决
实际问题
现有解决方式:
# 方式一 mysql> SELECT salary FROM employees WHERE last_name = 'Abel'; +----------+ | salary | +----------+ | 11000.00 | +----------+ 1 row in set (0.00 sec) mysql> SELECT last_name, salary FROM employees WHERE salary > 11000; +-----------+----------+ | last_name | salary | +-----------+----------+ | King | 24000.00 | | Kochhar | 17000.00 | | De Haan | 17000.00 | | Greenberg | 12000.00 | | Russell | 14000.00 | | Partners | 13500.00 | | Errazuriz | 12000.00 | | Ozer | 11500.00 | | Hartstein | 13000.00 | | Higgins | 12000.00 | +-----------+----------+ 10 rows in set (0.00 sec) # 方式二:自连接 mysql> SELECT e2.last_name, e2.salary FROM employees e1, employees e2 WHERE e1.last_name = 'Abel' AND e1.`salary` < e2.`salary`; +-----------+----------+ | last_name | salary | +-----------+----------+ | King | 24000.00 | | Kochhar | 17000.00 | | De Haan | 17000.00 | | Greenberg | 12000.00 | | Russell | 14000.00 | | Partners | 13500.00 | | Errazuriz | 12000.00 | | Ozer | 11500.00 | | Hartstein | 13000.00 | | Higgins | 12000.00 | +-----------+----------+ 10 rows in set (0.01 sec) # 方式三:子查询 mysql> SELECT last_name, salary FROM employees WHERE salary > (SELECT salary FROM employees WHERE last_name = 'Abel'); +-----------+----------+ | last_name | salary | +-----------+----------+ | King | 24000.00 | | Kochhar | 17000.00 | | De Haan | 17000.00 | | Greenberg | 12000.00 | | Russell | 14000.00 | | Partners | 13500.00 | | Errazuriz | 12000.00 | | Ozer | 11500.00 | | Hartstein | 13000.00 | | Higgins | 12000.00 | +-----------+----------+ 10 rows in set (0.00 sec)
子查询的基本使用
子查询的基本语法结构:
- 子查询(内查询)在主查询之前一次执行完成。
- 子查询的结果被主查询(外查询)使用。
注意事项:
- 子查询要包含在括号内。
- 将子查询放在比较条件的右侧。
- 单行操作符对应单行子查询,多行操作符对应多行子查询。
子查询的分类
分类方式一:按内查询的结果返回一条还是多条记录,将子查询分为单行子查询
、多行子查询
。
-
单行子查询
-
多行子查询
分类方式二:按内查询是否被执行多次,将子查询划分为相关(或关联)子查询
和不相关(或非关联)子查询
。
- 子查询从数据表中查询了数据结果,如果这个数据结果只执行一次,然后这个数据结果作为主查询的条件进行执行,那么这样的子查询叫做不相关子查询。
- 同样,如果子查询需要执行多次,即采用循环的方式,先从外部查询开始,每次都传入子查询进行查询,然后再将结果反馈给外部,这种嵌套的执行方式就称为相关子查询。
单行子查询
单行比较操作符
操作符 | 含义 |
---|---|
= | equal to |
> | greater than |
>= | greater than or equal to |
< | less than |
<= | less than or equal to |
<> | not equal to |
代码示例
查询工资大于 149 号员工工资的员工的信息:
mysql> SELECT last_name, salary FROM employees WHERE salary > (SELECT salary FROM employees WHERE employee_id = 149); +-----------+----------+ | last_name | salary | +-----------+----------+ | King | 24000.00 | | Kochhar | 17000.00 | | De Haan | 17000.00 | | Greenberg | 12000.00 | | Raphaely | 11000.00 | | Russell | 14000.00 | | Partners | 13500.00 | | Errazuriz | 12000.00 | | Cambrault | 11000.00 | | Ozer | 11500.00 | | Abel | 11000.00 | | Hartstein | 13000.00 | | Higgins | 12000.00 | +-----------+----------+ 13 rows in set (0.00 sec)
返回 job_id 与 141 号员工相同,salary 比 143 号员工多的员工姓名,job_id 和工资:
mysql> 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 | +-------------+----------+---------+ | Nayer | ST_CLERK | 3200.00 | | Mikkilineni | ST_CLERK | 2700.00 | | Bissot | ST_CLERK | 3300.00 | | Atkinson | ST_CLERK | 2800.00 | | Mallin | ST_CLERK | 3300.00 | | Rogers | ST_CLERK | 2900.00 | | Ladwig | ST_CLERK | 3600.00 | | Stiles | ST_CLERK | 3200.00 | | Seo | ST_CLERK | 2700.00 | | Rajs | ST_CLERK | 3500.00 | | Davies | ST_CLERK | 3100.00 | +-------------+----------+---------+ 11 rows in set (0.00 sec)
返回公司工资最少的员工的 last_name,job_id 和 salary:
mysql> SELECT last_name, job_id, salary FROM employees WHERE salary = (SELECT MIN(salary) FROM employees); +-----------+----------+---------+ | last_name | job_id | salary | +-----------+----------+---------+ | Olson | ST_CLERK | 2100.00 | +-----------+----------+---------+ 1 row in set (0.00 sec)
查询与 141 号或 174 号员工的 manager_id 和 department_id 相同的其他员工的 employee_id,manager_id,department_id:
# 方式一 mysql> SELECT employee_id, manager_id, department_id FROM employees WHERE manager_id IN (SELECT manager_id FROM employees WHERE employee_id IN (174,141)) AND department_id IN (SELECT department_id FROM employees WHERE employee_id IN (174,141)) AND employee_id NOT IN (174,141); +-------------+------------+---------------+ | employee_id | manager_id | department_id | +-------------+------------+---------------+ | 142 | 124 | 50 | | 143 | 124 | 50 | | 144 | 124 | 50 | | 196 | 124 | 50 | | 197 | 124 | 50 | | 198 | 124 | 50 | | 199 | 124 | 50 | | 175 | 149 | 80 | | 176 | 149 | 80 | | 177 | 149 | 80 | | 179 | 149 | 80 | +-------------+------------+---------------+ 11 rows in set (0.00 sec) # 方式二 mysql> SELECT employee_id, manager_id, department_id FROM employees WHERE (manager_id, department_id) IN (SELECT manager_id, department_id FROM employees WHERE employee_id IN (141,174)) AND employee_id NOT IN (141,174); +-------------+------------+---------------+ | employee_id | manager_id | department_id | +-------------+------------+---------------+ | 142 | 124 | 50 | | 143 | 124 | 50 | | 144 | 124 | 50 | | 196 | 124 | 50 | | 197 | 124 | 50 | | 198 | 124 | 50 | | 199 | 124 | 50 | | 175 | 149 | 80 | | 176 | 149 | 80 | | 177 | 149 | 80 | | 179 | 149 | 80 | +-------------+------------+---------------+ 11 rows in set (0.00 sec)
HAVING 中的子查询
执行过程:
- 首先,执行子查询;
- 然后,向主查询中的 HAVING 子句返回结果。
查询最低工资大于 50 号部门最低工资的部门 id 和其最低工资:
mysql> SELECT department_id, MIN(salary) FROM employees GROUP BY department_id HAVING MIN(salary) > (SELECT MIN(salary) FROM employees WHERE department_id = 50); +---------------+-------------+ | department_id | MIN(salary) | +---------------+-------------+ | NULL | 7000.00 | | 10 | 4400.00 | | 20 | 6000.00 | | 30 | 2500.00 | | 40 | 6500.00 | | 60 | 4200.00 | | 70 | 10000.00 | | 80 | 6100.00 | | 90 | 17000.00 | | 100 | 6900.00 | | 110 | 8300.00 | +---------------+-------------+ 11 rows in set (0.00 sec)
CASE 中的子查询
查询员工的 employee_id,last_name 和 location。其中,若员工 department_id 与 location_id 为1800 的员工的 department_id 相同,则 location 为 Canada,其余则为 USA。
mysql> 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; +-------------+-------------+----------+ | employee_id | last_name | location | +-------------+-------------+----------+ | 100 | King | USA | | 101 | Kochhar | USA | | 102 | De Haan | USA | | 103 | Hunold | USA | | 104 | Ernst | USA | | 105 | Austin | USA | | 106 | Pataballa | USA | | 107 | Lorentz | USA | | 108 | Greenberg | USA | | 109 | Faviet | USA | | 110 | Chen | USA | | 111 | Sciarra | USA | | 112 | Urman | USA | | 113 | Popp | USA | | 200 | Whalen | USA | | 201 | Hartstein | Canada | | 202 | Fay | Canada | | 203 | Mavris | USA | | 204 | Baer | USA | | 205 | Higgins | USA | | 206 | Gietz | USA | +-------------+-------------+----------+ 107 rows in set (0.00 sec)
空值问题
mysql> SELECT last_name, job_id FROM employees WHERE job_id = (SELECT job_id FROM employees WHERE last_name = 'Haas'); Empty set (0.00 sec) mysql> SELECT job_id FROM employees WHERE last_name = 'Haas'; Empty set
employees 中没有叫 Haas 的员工,子查询不返回任何行,主查询也为空值。
非法使用子查询
mysql> SELECT employee_id, last_name FROM employees WHERE salary = (SELECT MIN(salary) FROM employees GROUP BY department_id); ERROR 1242 (21000): Subquery returns more than 1 row
多行子查询,但使用了单行比较符。
多行子查询
多行子查询,也称为集合比较子查询,其特点如下:
- 内查询返回多行。
- 使用多行比较操作符。
多行比较操作符
操作符 | 含义 |
---|---|
IN | 等于列表中的任意一个 |
ANY | 需要和单行比较操作符一起使用,和子查询返回的某一个 值比较 |
ALL | 需要和单行比较操作符一起使用,和子查询返回的所有 值比较 |
SOME | 实际上是 ANY 的别名,作用相同,一般常使用 ANY |
代码示例
返回其它 job_id 中比 job_id 为 IT_PROG 部门任一工资低的员工的员工号、姓名、job_id 以及 salary:
mysql> SELECT employee_id, last_name, job_id, salary FROM employees WHERE salary < ANY (SELECT salary FROM employees WHERE job_id = 'IT_PROG') AND job_id <> 'IT_PROG'; +-------------+-------------+------------+---------+ | employee_id | last_name | job_id | salary | +-------------+-------------+------------+---------+ | 110 | Chen | FI_ACCOUNT | 8200.00 | | 111 | Sciarra | FI_ACCOUNT | 7700.00 | | 112 | Urman | FI_ACCOUNT | 7800.00 | | 113 | Popp | FI_ACCOUNT | 6900.00 | | 115 | Khoo | PU_CLERK | 3100.00 | | 116 | Baida | PU_CLERK | 2900.00 | | 117 | Tobias | PU_CLERK | 2800.00 | | 118 | Himuro | PU_CLERK | 2600.00 | | 119 | Colmenares | PU_CLERK | 2500.00 | | 120 | Weiss | ST_MAN | 8000.00 | | 121 | Fripp | ST_MAN | 8200.00 | | 122 | Kaufling | ST_MAN | 7900.00 | | 123 | Vollman | ST_MAN | 6500.00 | | 124 | Mourgos | ST_MAN | 5800.00 | | 125 | Nayer | ST_CLERK | 3200.00 | | 203 | Mavris | HR_REP | 6500.00 | | 206 | Gietz | AC_ACCOUNT | 8300.00 | +-------------+-------------+------------+---------+ 76 rows in set (0.00 sec) mysql> SELECT salary FROM employees WHERE job_id = 'IT_PROG'; +---------+ | salary | +---------+ | 9000.00 | | 6000.00 | | 4800.00 | | 4800.00 | | 4200.00 | +---------+ 5 rows in set (0.00 sec)
返回其它 job_id 中比 job_id 为 IT_PROG 部门所有工资都低的员工的员工号、姓名、job_id 以及 salary:
mysql> SELECT employee_id, last_name, job_id, salary FROM employees WHERE salary < ALL (SELECT salary FROM employees WHERE job_id = 'IT_PROG') AND job_id <> 'IT_PROG'; +-------------+-------------+----------+---------+ | employee_id | last_name | job_id | salary | +-------------+-------------+----------+---------+ | 115 | Khoo | PU_CLERK | 3100.00 | | 116 | Baida | PU_CLERK | 2900.00 | | 117 | Tobias | PU_CLERK | 2800.00 | | 118 | Himuro | PU_CLERK | 2600.00 | | 119 | Colmenares | PU_CLERK | 2500.00 | | 125 | Nayer | ST_CLERK | 3200.00 | | 126 | Mikkilineni | ST_CLERK | 2700.00 | | 127 | Landry | ST_CLERK | 2400.00 | | 128 | Markle | ST_CLERK | 2200.00 | | 129 | Bissot | ST_CLERK | 3300.00 | | 130 | Atkinson | ST_CLERK | 2800.00 | | 131 | Marlow | ST_CLERK | 2500.00 | | 132 | Olson | ST_CLERK | 2100.00 | | 193 | Everett | SH_CLERK | 3900.00 | | 194 | McCain | SH_CLERK | 3200.00 | | 195 | Jones | SH_CLERK | 2800.00 | | 196 | Walsh | SH_CLERK | 3100.00 | | 197 | Feeney | SH_CLERK | 3000.00 | | 198 | OConnell | SH_CLERK | 2600.00 | | 199 | Grant | SH_CLERK | 2600.00 | +-------------+-------------+----------+---------+ 44 rows in set (0.00 sec)
查询平均工资最低的部门 id:
# 方式一 mysql> 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); +---------------+ | department_id | +---------------+ | 50 | +---------------+ 1 row in set (0.00 sec) # 方式二 mysql> 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); +---------------+ | department_id | +---------------+ | 50 | +---------------+ 1 row in set (0.00 sec)
MySQL 中聚合函数不能嵌套使用。
空值问题
mysql> SELECT last_name FROM employees WHERE employee_id NOT IN (SELECT manager_id FROM employees); Empty set (0.00 sec) mysql> SELECT manager_id FROM employees; +------------+ | manager_id | +------------+ | NULL | | 100 | | 100 | | 100 | | 149 | | 149 | | 201 | | 205 | +------------+ 107 rows in set (0.00 sec) mysql> SELECT last_name FROM employees WHERE employee_id NOT IN (SELECT manager_id FROM employees WHERE manager_id IS NOT NULL); +-------------+ | last_name | +-------------+ | Ernst | | Austin | | Pataballa | | Lorentz | | Faviet | | Mavris | | Baer | | Gietz | +-------------+ 89 rows in set (0.00 sec)
因为子查询中,返回了一个 NULL 值,导致主查询失败。添加条件后,主查询正常。
相关子查询
相关子查询执行流程
如果子查询的执行依赖于外部查询,通常情况下都是因为子查询中的表用到了外部的表,并进行了条件关联,因此每执行一次外部查询,子查询都要重新计算一次,这样的子查询就称之为关联子查询
。
相关子查询按照一行接一行的顺序执行,主查询的每一行都执行一次子查询。
代码示例
查询员工中工资大于本部门平均工资的员工的 last_name,salary 和其 department_id:
# 相关子查询 mysql> SELECT e1.last_name, e1.salary, e1.department_id FROM employees e1 WHERE salary > (SELECT AVG(salary) FROM employees e2 WHERE e2.department_id = e1.department_id); +-----------+----------+---------------+ | last_name | salary | department_id | +-----------+----------+---------------+ | King | 24000.00 | 90 | | Hunold | 9000.00 | 60 | | Ernst | 6000.00 | 60 | | Greenberg | 12000.00 | 100 | | Faviet | 9000.00 | 100 | | Raphaely | 11000.00 | 30 | | Weiss | 8000.00 | 50 | | Fripp | 8200.00 | 50 | | Kaufling | 7900.00 | 50 | | Vollman | 6500.00 | 50 | | Mourgos | 5800.00 | 50 | | Ladwig | 3600.00 | 50 | | Rajs | 3500.00 | 50 | | Russell | 14000.00 | 80 | | Partners | 13500.00 | 80 | | Errazuriz | 12000.00 | 80 | | Cambrault | 11000.00 | 80 | | Zlotkey | 10500.00 | 80 | | Tucker | 10000.00 | 80 | | Bernstein | 9500.00 | 80 | | Hall | 9000.00 | 80 | | King | 10000.00 | 80 | | Sully | 9500.00 | 80 | | McEwen | 9000.00 | 80 | | Vishney | 10500.00 | 80 | | Greene | 9500.00 | 80 | | Ozer | 11500.00 | 80 | | Bloom | 10000.00 | 80 | | Fox | 9600.00 | 80 | | Abel | 11000.00 | 80 | | Sarchand | 4200.00 | 50 | | Bull | 4100.00 | 50 | | Chung | 3800.00 | 50 | | Dilly | 3600.00 | 50 | | Bell | 4000.00 | 50 | | Everett | 3900.00 | 50 | | Hartstein | 13000.00 | 20 | | Higgins | 12000.00 | 110 | +-----------+----------+---------------+ 38 rows in set (0.00 sec) # 方式二:在 FROM 中使用子查询 mysql> SELECT e1.last_name, e1.salary, e1.department_id FROM employees e1, (SELECT department_id, AVG(salary) dept_avg_sal FROM employees GROUP BY department_id) e2 WHERE e1.department_id = e2.department_id AND e2.dept_avg_sal < e1.salary; +-----------+----------+---------------+ | last_name | salary | department_id | +-----------+----------+---------------+ | Hartstein | 13000.00 | 20 | | Raphaely | 11000.00 | 30 | | Weiss | 8000.00 | 50 | | Fripp | 8200.00 | 50 | | Kaufling | 7900.00 | 50 | | Vollman | 6500.00 | 50 | | Mourgos | 5800.00 | 50 | | Ladwig | 3600.00 | 50 | | Rajs | 3500.00 | 50 | | Sarchand | 4200.00 | 50 | | Bull | 4100.00 | 50 | | Chung | 3800.00 | 50 | | Dilly | 3600.00 | 50 | | Bell | 4000.00 | 50 | | Everett | 3900.00 | 50 | | Hunold | 9000.00 | 60 | | Ernst | 6000.00 | 60 | | Russell | 14000.00 | 80 | | Partners | 13500.00 | 80 | | Errazuriz | 12000.00 | 80 | | Cambrault | 11000.00 | 80 | | Zlotkey | 10500.00 | 80 | | Tucker | 10000.00 | 80 | | Bernstein | 9500.00 | 80 | | Hall | 9000.00 | 80 | | King | 10000.00 | 80 | | Sully | 9500.00 | 80 | | McEwen | 9000.00 | 80 | | Vishney | 10500.00 | 80 | | Greene | 9500.00 | 80 | | Ozer | 11500.00 | 80 | | Bloom | 10000.00 | 80 | | Fox | 9600.00 | 80 | | Abel | 11000.00 | 80 | | King | 24000.00 | 90 | | Greenberg | 12000.00 | 100 | | Faviet | 9000.00 | 100 | | Higgins | 12000.00 | 110 | +-----------+----------+---------------+ 38 rows in set (0.00 sec)
FROM 型的子查询:子查询是作为 FROM 的一部分,子查询要用 () 引起来,并且要给这个子查询取别名, 把它当成一张 "临时的虚拟的表" 来使用。(主表与虚拟表关联查询)
查询员工的 id,salary,按照 department_name 排序:
# 在 ORDER BY 中使用子查询 mysql> SELECT e.employee_id, e.salary FROM employees e ORDER BY (SELECT department_name FROM departments d WHERE e.department_id = d.department_id); +-------------+----------+ | employee_id | salary | +-------------+----------+ | 178 | 7000.00 | | 205 | 12000.00 | | 206 | 8300.00 | | 200 | 4400.00 | | 100 | 24000.00 | | 101 | 17000.00 | | 102 | 17000.00 | | 108 | 12000.00 | | 109 | 9000.00 | | 110 | 8200.00 | | 111 | 7700.00 | | 196 | 3100.00 | | 197 | 3000.00 | | 198 | 2600.00 | | 199 | 2600.00 | +-------------+----------+ 107 rows in set (0.00 sec)
在SELECT中,除了GROUP BY 和 LIMIT之外,其他位置都可以声明子查询!
若 employees 表中 employee_id 与 job_history 表中 employee_id 相同的数目不小于 2,输出这些相同 id 的员工的 employee_id,last_name 和其 job_id:
mysql> SELECT e.employee_id, e.last_name, e.job_id FROM employees e WHERE 2 <= (SELECT COUNT(*) FROM job_history WHERE employee_id = e.employee_id); +-------------+-----------+---------+ | employee_id | last_name | job_id | +-------------+-----------+---------+ | 101 | Kochhar | AD_VP | | 176 | Taylor | SA_REP | | 200 | Whalen | AD_ASST | +-------------+-----------+---------+ 3 rows in set (0.00 sec)
EXISTS 与 NOT EXISTS 关键字
关联子查询通常也会和 EXISTS 操作符一起来使用,用来检查在子查询中是否存在满足条件的行。
-
如果在子查询中不存在满足条件的行:
- 条件返回 FALSE;
- 继续在子查询中查找。(不满足继续找)
-
如果在子查询中存在满足条件的行:
- 条件返回 TRUE;
- 不在子查询中继续查找。(满足即返回)
NOT EXISTS 关键字表示如果不存在某种条件,则返回 TRUE,否则返回 FALSE。
查询公司管理者的 employee_id,last_name,job_id,department_id 信息:
# 方式一:自连接 mysql> SELECT DISTINCT e1.employee_id, e1.last_name, e1.job_id, e1.department_id FROM employees e1 JOIN employees e2 WHERE e1.employee_id = e2.manager_id; +-------------+-----------+---------+---------------+ | employee_id | last_name | job_id | department_id | +-------------+-----------+---------+---------------+ | 100 | King | AD_PRES | 90 | | 101 | Kochhar | AD_VP | 90 | | 102 | De Haan | AD_VP | 90 | | 103 | Hunold | IT_PROG | 60 | | 108 | Greenberg | FI_MGR | 100 | | 114 | Raphaely | PU_MAN | 30 | | 120 | Weiss | ST_MAN | 50 | | 121 | Fripp | ST_MAN | 50 | | 122 | Kaufling | ST_MAN | 50 | | 123 | Vollman | ST_MAN | 50 | | 124 | Mourgos | ST_MAN | 50 | | 145 | Russell | SA_MAN | 80 | | 146 | Partners | SA_MAN | 80 | | 147 | Errazuriz | SA_MAN | 80 | | 148 | Cambrault | SA_MAN | 80 | | 149 | Zlotkey | SA_MAN | 80 | | 201 | Hartstein | MK_MAN | 20 | | 205 | Higgins | AC_MGR | 110 | +-------------+-----------+---------+---------------+ 18 rows in set (0.00 sec) # 方式二:存在一条记录,employees 表中的 manager_id 等于 employee_id,即该 employee 为管理者 mysql> SELECT employee_id, last_name, job_id, department_id FROM employees e1 WHERE EXISTS (SELECT * FROM employees e2 WHERE e2.manager_id = e1.employee_id); +-------------+-----------+---------+---------------+ | employee_id | last_name | job_id | department_id | +-------------+-----------+---------+---------------+ | 100 | King | AD_PRES | 90 | | 101 | Kochhar | AD_VP | 90 | | 102 | De Haan | AD_VP | 90 | | 103 | Hunold | IT_PROG | 60 | | 108 | Greenberg | FI_MGR | 100 | | 114 | Raphaely | PU_MAN | 30 | | 120 | Weiss | ST_MAN | 50 | | 121 | Fripp | ST_MAN | 50 | | 122 | Kaufling | ST_MAN | 50 | | 123 | Vollman | ST_MAN | 50 | | 124 | Mourgos | ST_MAN | 50 | | 145 | Russell | SA_MAN | 80 | | 146 | Partners | SA_MAN | 80 | | 147 | Errazuriz | SA_MAN | 80 | | 148 | Cambrault | SA_MAN | 80 | | 149 | Zlotkey | SA_MAN | 80 | | 201 | Hartstein | MK_MAN | 20 | | 205 | Higgins | AC_MGR | 110 | +-------------+-----------+---------+---------------+ 18 rows in set (0.00 sec) # 方式三:相关子查询 mysql> SELECT employee_id, last_name, job_id, department_id FROM employees WHERE employee_id IN (SELECT DISTINCT manager_id FROM employees); +-------------+-----------+---------+---------------+ | employee_id | last_name | job_id | department_id | +-------------+-----------+---------+---------------+ | 100 | King | AD_PRES | 90 | | 101 | Kochhar | AD_VP | 90 | | 102 | De Haan | AD_VP | 90 | | 103 | Hunold | IT_PROG | 60 | | 108 | Greenberg | FI_MGR | 100 | | 114 | Raphaely | PU_MAN | 30 | | 120 | Weiss | ST_MAN | 50 | | 121 | Fripp | ST_MAN | 50 | | 122 | Kaufling | ST_MAN | 50 | | 123 | Vollman | ST_MAN | 50 | | 124 | Mourgos | ST_MAN | 50 | | 145 | Russell | SA_MAN | 80 | | 146 | Partners | SA_MAN | 80 | | 147 | Errazuriz | SA_MAN | 80 | | 148 | Cambrault | SA_MAN | 80 | | 149 | Zlotkey | SA_MAN | 80 | | 201 | Hartstein | MK_MAN | 20 | | 205 | Higgins | AC_MGR | 110 | +-------------+-----------+---------+---------------+ 18 rows in set (0.00 sec)
查询 departments 表中,不存在于 employees 表中的部门的 department_id 和 department_name:
mysql> SELECT department_id, department_name FROM departments d WHERE NOT EXISTS (SELECT * FROM employees e WHERE e.department_id = d.department_id); +---------------+----------------------+ | department_id | department_name | +---------------+----------------------+ | 120 | Treasury | | 130 | Corporate Tax | | 140 | Control And Credit | | 150 | Shareholder Services | | 160 | Benefits | | 170 | Manufacturing | | 180 | Construction | | 190 | Contracting | | 200 | Operations | | 210 | IT Support | | 220 | NOC | | 230 | IT Helpdesk | | 240 | Government Sales | | 250 | Retail Sales | | 260 | Recruiting | | 270 | Payroll | +---------------+----------------------+ 16 rows in set (0.00 sec)
相关更新
语法:
UPDATE table1 alias1 SET column = (SELECT expression FROM table2 alias2 WHERE alias1.column = alias2.column);
使用相关子查询依据一个表中的数据更新另一个表的数据。
在 employees 中增加一个 department_name 字段,数据为员工对应的部门名称:
mysql> ALTER TABLE employees ADD(department_name VARCHAR(50)); Query OK, 0 rows affected (0.04 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> UPDATE employees e SET department_name = (SELECT department_name FROM departments d WHERE e.department_id = d.department_id); Query OK, 106 rows affected (0.01 sec)
相关删除
语法:
DELETE FROM table1 alias1 WHERE column operator (SELECT expression FROM table2 alias2 WHERE alias1.column = alias2.column);
使用相关子查询依据一个表中的数据删除另一个表的数据。
删除表 employees 中,与 emp_history 表皆有的数据:
mysql> DELETE FROM employees e WHERE e.employee_id IN (SELECT eh.employee_id FROM emp_history eh WHERE eh.employee_id = e.employee_id);
子查询与自连接的对比
查询工资比 Abel 高的员工信息:
# 方式一:子查询 mysql> SELECT last_name, salary FROM employees WHERE salary > (SELECT salary FROM employees WHERE last_name = 'Abel'); # 方式二:自连接 mysql> SELECT e2.last_name, e2.salary FROM employees e1, employees e2 WHERE e1.last_name = 'Abel' AND e1.salary < e2.salary;
此情况中,即可以使用子查询,也可以使用自连接。一般情况建议使用自连接,因为在许多 DBMS 的处理过程中,对于自连接的处理速度要比子查询快得多。
可以这样理解:子查询实际上是通过未知表进行查询后的条件判断,而自连接是通过已知的自身数据表进行条件判断,因此在大部分 DBMS 中都对自连接处理进行了优化。
原文链接
https://github.com/ACatSmiling/zero-to-zero/blob/main/RelationalDatabase/mysql.md
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· 2 本地部署DeepSeek模型构建本地知识库+联网搜索详细步骤