1. 多表查询
1.1 常见错误:笛卡尔积与属性归属
在多表查询中,笛卡尔积是一个常见的错误,它发生在查询时没有明确指定连接条件,导致每行数据都与其他表中的每行数据进行匹配。这种错误会导致结果集过大,且难以理解。
原因分析:
- 缺少连接条件:在执行多表查询时,如果没有指定连接条件,MySQL 会默认执行笛卡尔积。
示例代码:
1 | -- 错误示例:缺少连接条件 |
为了防止这种情况,必须在 WHERE 子句中指定连接条件。
属性归属:
在多表查询中,如果列名在多个表中都存在,必须明确指定该列属于哪个表,否则会导致查询错误。
示例代码:
1 | -- 错误示例:未指明属性所属表 |
1.2 连接条件与规范
相同属性连接:
当两个表中存在相同的属性时,可以通过比较这些属性来连接表。
示例代码:
1 | SELECT employee_id, department_name |
表别名:
使用别名可以简化查询语句,并避免列名冲突。
示例代码:
1 | SELECT e.employee_id, d.department_name |
多个连接条件:
当连接多个表时,至少需要 n-1 个连接条件,其中 n 是表的个数。
示例代码:
1 | SELECT employee_id, department_name, l.city, e.department_id, l.location_id |
1.3 连接类型
等值连接与非等值连接:
- 等值连接:使用
=操作符连接两个表,返回两个表中匹配的行。 - 非等值连接:使用不等号(例如
<>、>、<)连接两个表,返回不满足等值条件的行。
示例代码:
1 | -- 非等值连接 |
自连接与非自连接:
- 自连接:一个表与自身进行连接,用于查找同一表中的相关记录。
- 非自连接:多个表之间进行连接。
示例代码:
1 | -- 自连接 |
内连接与外连接:
- 内连接:只返回两个表中匹配的行。
- 外连接:包括内连接的结果,并添加外层表中不匹配的行。
示例代码:
1 | -- 左外连接 |
1.4 SQL99 连接特性
自然连接:
自然连接会自动连接所有具有相同列名和类型的列,简化了连接条件。
示例代码:
1 | SELECT e.employee_id, e.last_name, d.department_name |
USING 连接:
USING 连接指定连接时使用的列,简化了连接条件的书写。
示例代码:
1 | SELECT e.last_name, d.department_id |
2. SQL JOIN 操作详解
2.1 联接的概念
在关系型数据库中,JOIN 操作用于将两个或多个表中的行结合起来,基于它们之间的某种关联或关系。MySQL 支持多种 JOIN 类型,每种类型都有其特定的用途和语法。
2.2 7种 JOIN 类型
INNER JOIN(内连接):
- 选择两个或多个表中有匹配的记录的行。
- 如果在任一表中没有匹配,结果集中就不会有对应的行。
示例:
1
2
3SELECT e.last_name, d.department_name
FROM employees e
INNER JOIN departments d ON e.department_id = d.department_id;LEFT JOIN(左连接):
- 返回左表的所有记录,即使右表中没有匹配的记录。
- 如果右表中没有匹配,结果集中右表的部分将显示为 NULL。
示例:
1
2
3SELECT e.last_name, d.department_name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id;RIGHT JOIN(右连接):
- 返回右表的所有记录,即使左表中没有匹配的记录。
- 如果左表中没有匹配,结果集中左表的部分将显示为 NULL。
示例:
1
2
3SELECT e.last_name, d.department_name
FROM employees e
RIGHT JOIN departments d ON e.department_id = d.department_id;FULL JOIN(全连接):
- 返回左表和右表中的所有记录。
- 如果任一表中没有匹配,则在结果集中相应的部分显示为 NULL。
- MySQL 不直接支持 FULL JOIN,但可以通过 UNION 操作符结合 LEFT JOIN 和 RIGHT JOIN 来实现。
示例:
1
2
3
4
5
6
7SELECT e.last_name, d.department_name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id
UNION
SELECT e.last_name, d.department_name
FROM employees e
RIGHT JOIN departments d ON e.department_id = d.department_id;CROSS JOIN(交叉连接):
- 返回两个表的笛卡尔积,即所有可能的组合。
- 这种类型的 JOIN 不常用,因为它会产生大量的结果行。
示例:
1
2
3SELECT e.last_name, d.department_name
FROM employees e
CROSS JOIN departments d;NATURAL JOIN(自然连接):
- 根据表中相同的列名自动连接两个或多个表。
- 它类似于 INNER JOIN,但更加简洁。
示例:
1
2
3SELECT e.last_name, d.department_name
FROM employees e
NATURAL JOIN departments d;USING JOIN:
- 与 NATURAL JOIN 类似,但它指定连接时使用的列名。
示例:
1
2
3SELECT e.last_name, d.department_name
FROM employees e
JOIN departments d USING(department_id);
2.3 UNION 与 UNION ALL
UNION:
- 用于合并两个或多个 SELECT 语句的结果集。
- 自动去除重复的行。
- 如果任一 SELECT 语句返回 NULL,则结果集中也包含 NULL。
示例:
1
2
3
4
5SELECT last_name, department_id
FROM employees
UNION
SELECT last_name, department_id
FROM departments;UNION ALL:
- 与 UNION 类似,但它不会去除重复的行。
- 如果任一 SELECT 语句返回 NULL,则结果集中也包含 NULL。
示例:
1
2
3
4
5SELECT last_name, department_id
FROM employees
UNION ALL
SELECT last_name, department_id
FROM departments;
3. 流程控制与高级功能
3.1 流程控制函数
if() 函数:
if() 函数用于根据条件返回不同的值。
语法:
1 | IF(val, val1, val2) |
val:条件表达式,如果为 TRUE,则返回val1。val1:当val为 TRUE 时返回的值。val2:当val为 FALSE 时返回的值。
示例:
1 | SELECT last_name, salary, |
ifnull() 函数:
ifnull() 函数用于检查表达式是否为 NULL,如果是,则返回指定的替代值。
语法:
1 | IFNULL(val1, val2) |
val1:要检查的表达式。val2:如果val1为 NULL,则返回的值。
示例:
1 | SELECT last_name, department_id, |
3.2 case when 语句
case when 语句用于根据多个条件返回不同的值。
语法:
1 | CASE when condition1 then result1 |
condition1, condition2, ...:条件表达式,如果为 TRUE,则执行相应的result。result1, result2, ...:当条件为 TRUE 时返回的值。resultN:如果所有条件都为 FALSE,则返回的值。
示例:
1 | SELECT last_name, salary, |
3.3 case … when … 语句
case … when … 语句类似于 case when 语句,但可以用于更复杂的逻辑。
语法:
1 | SELECT column1, column2, ... |
column1, column2, ...:要选择的列。condition1, condition2, ...:条件表达式,如果为 TRUE,则执行相应的result。result1, result2, ...:当条件为 TRUE 时返回的值。alias:结果的别名。
示例:
1 | SELECT last_name, department_id, salary, |
3.4 加密与解密
MySQL 提供了几个函数用于加密和解密数据。

加密函数:
ENCRYPT(value, key): 使用指定的密钥对值进行加密。AES_ENCRYPT(value, key): 使用 AES 算法对值进行加密。
解密函数:
DECRYPT(value, key): 使用指定的密钥对加密的值进行解密。AES_DECRYPT(value, key): 使用 AES 算法对加密的值进行解密。
示例:
1 | -- 加密 |
4. SQL 查询执行原理与子查询深入
4.1 SQL 查询执行过程
SQL 查询的执行过程是一个复杂的过程,涉及多个阶段,以下是 MySQL 执行 SQL 查询的基本步骤:
1. FROM 子句解析:
- MySQL 首先解析 FROM 子句,确定查询涉及的表。
2. JOIN 子句解析:
- 如果查询中包含 JOIN 操作,MySQL 会解析 JOIN 子句,确定连接类型(如 INNER JOIN、LEFT JOIN 等)和连接条件。
3. ON 子句解析:
- ON 子句定义了连接条件,MySQL 会根据这些条件将相关行进行匹配。
4. (LEFT|RIGHT) JOIN 解析:
- 对于 LEFT JOIN 或 RIGHT JOIN,MySQL 会确定哪些行应该包含在结果集中,即使它们在另一个表中没有匹配的行。
5. WHERE 子句解析:
- WHERE 子句用于过滤行,只保留满足条件的行。
6. GROUP BY 子句解析:
- GROUP BY 子句用于对结果集进行分组,通常与聚合函数一起使用。
7. HAVING 子句解析:
- HAVING 子句用于过滤分组后的结果,类似于 WHERE 子句,但它应用于分组后的数据。
8. SELECT 子句解析:
- SELECT 子句指定了要从表中选择的列。
9. DISTINCT 子句解析:
- DISTINCT 子句用于去除结果集中的重复行。
10. ORDER BY 子句解析:
- ORDER BY 子句用于对结果集进行排序。
11. LIMIT 子句解析:
- LIMIT 子句用于限制结果集的大小。
12. 执行查询:
- MySQL 根据上述解析步骤执行查询,生成最终的结果集。
4.2 自连接与子查询
自连接:
自连接是指一个表与其自身进行连接,这在处理具有层次结构的数据时非常有用。
示例:
1 | -- 查询所有管理者的姓名和他们的直接下属的姓名 |
子查询:
子查询是一种嵌套查询,它可以在 SELECT、FROM 或 WHERE 子句中使用。
单行子查询:
单行子查询返回单个值。
示例:
1 | -- 查询所有工资高于部门平均工资的员工 |
多行子查询:
多行子查询返回多个值。
示例:
1 | -- 查询所有部门 ID 和部门名称,这些部门包含工资高于平均工资的员工 |
相关子查询:
相关子查询是指子查询依赖于外层查询中的值。
示例:
1 | -- 查询所有部门的平均工资,但只显示那些平均工资高于公司平均工资的部门 |
5. 子查询的广泛应用与常见问题解析
5.1 子查询在 HAVING 子句中的应用
HAVING 子句通常与 GROUP BY 子句结合使用,用于对分组后的结果进行过滤。子查询在 HAVING 子句中可以提供更复杂的过滤条件。
示例:
1 | -- 查询所有部门 ID 和部门名称,这些部门的最低工资高于部门 50 的最低工资 |
5.2 子查询在 CASE WHEN 语句中的应用
CASE WHEN 语句可以与子查询结合,用于根据不同的条件返回不同的值。
示例:
1 | -- 查询员工信息,并根据部门 ID 判断是否位于加拿大 |
5.3 常见问题解析
NULL 值:
NULL 值会导致
NOT IN永远为真,因为NOT IN检查的是是否存在不等于的值,而 NULL 与任何值比较都为假。示例:
1
2SELECT * FROM employees WHERE department_id NOT IN (NULL);
-- 结果为空,因为 NULL 不等于任何值多行子查询与
=的使用:=不能用于多行子查询,因为=要求子查询返回单个值,而多行子查询会返回多行。示例:
1
2SELECT * FROM employees WHERE salary = (SELECT salary FROM employees WHERE employee_id = 100);
-- 错误,因为子查询返回多行
5.4 多行子查询
多行子查询返回多行结果,可以使用以下操作符:
IN:用于检查值是否在子查询的结果集中。ALL:用于检查外层查询的值是否小于子查询结果集中的所有值。ANY(或SOME):用于检查外层查询的值是否小于子查询结果集中的任意一个值。
示例:
1 | -- 查询所有部门的平均工资,但只显示那些平均工资低于所有部门的最低平均工资的部门 |
5.5 关联子查询
关联子查询是指子查询的执行依赖于外层查询的结果。
示例:
1 | -- 查询所有工资高于其部门平均工资的员工 |
5.6 子查询作为临时表
有时候,可以将子查询的结果视为一个临时表,这可以通过在子查询前加上括号来实现。
示例:
1 | -- 使用子查询作为临时表 |
5.7 使用 EXISTS 和 NOT EXISTS
EXISTS 和 NOT EXISTS 是用来判断子查询是否返回至少一行结果的逻辑操作符。
EXISTS:如果子查询返回至少一行结果,则结果为真。NOT EXISTS:如果子查询返回至少一行结果,则结果为假。
示例:
1 | -- 查询没有员工的部门 |