SQL语句
一、SQL 是什么?
SQL(Structured Query Language)结构化查询语言,用于与关系型数据库交互(如 MySQL、PostgreSQL、Oracle、SQL Server 等)。
主要功能包括:
- 查询数据(SELECT)
- 插入数据(INSERT)
- 更新数据(UPDATE)
- 删除数据(DELETE)
- 建表、建库(CREATE)
- 权限与索引管理
二、SQL 基础语法结构
三、表连接(JOIN)
JOIN 是 SQL 的核心,用来从多个表中获取相关数据。
假设我们有两张表:
| users | orders | ||||
|---|---|---|---|---|---|
| id | name | id | user_id | amount |
表:users
| id | name |
|---|---|
| 1 | Alice |
| 2 | Bob |
| 3 | Charlie |
| 4 | Diana |
表:orders
| id | user_id | amount |
|---|---|---|
| 1 | 1 | 100.00 |
| 2 | 1 | 200.00 |
| 3 | 2 | 300.00 |
| 4 | 3 | 50.00 |
| 5 | 3 | 70.00 |
1. 内连接(INNER JOIN)
内连接(INNER JOIN)—— 取匹配的记录,只有两张表 匹配的记录 才会显示。
graph LR
subgraph users["users 表"]
U1["Alice (id=1)"]
U2["Bob (id=2)"]
U3["Charlie (id=3)"]
U4["Diana (id=4)"]
end
subgraph orders["orders 表"]
O1["order#1 (user_id=1, 100)"]
O2["order#2 (user_id=1, 200)"]
O3["order#3 (user_id=2, 300)"]
O4["order#4 (user_id=3, 50)"]
O5["order#5 (user_id=3, 70)"]
end
%% 匹配关系
U1 --- O1
U1 --- O2
U2 --- O3
U3 --- O4
U3 --- O5
对应 SQL
查询结果
| name | amount |
|---|---|
| Alice | 100.00 |
| Alice | 200.00 |
| Bob | 300.00 |
| Charlie | 50.00 |
| Charlie | 70.00 |
2. 左连接(LEFT JOIN)
左连接(LEFT JOIN)—— 保留左表全部,左表(users)全部保留;右表(orders)如果没有匹配,则显示 NULL。
graph LR
subgraph users["users 表(左表)"]
U1["Alice (id=1)"]
U2["Bob (id=2)"]
U3["Charlie (id=3)"]
U4["Diana (id=4)"]
end
subgraph orders["orders 表(右表)"]
O1["order#1 (user_id=1, 100)"]
O2["order#2 (user_id=1, 200)"]
O3["order#3 (user_id=2, 300)"]
O4["order#4 (user_id=3, 50)"]
O5["order#5 (user_id=3, 70)"]
end
%% 匹配关系
U1 --- O1
U1 --- O2
U2 --- O3
U3 --- O4
U3 --- O5
U4 -.-> NULL["(无匹配,右表为 NULL)"]
对应 SQL
查询结果
| name | amount |
|---|---|
| Alice | 100.00 |
| Alice | 200.00 |
| Bob | 300.00 |
| Charlie | 50.00 |
| Charlie | 70.00 |
| Diana | NULL |
3. 右连接(RIGHT JOIN)
右连接(RIGHT JOIN)—— 保留右表全部,与 LEFT JOIN 相反,保留右表全部记录。
graph LR
subgraph users["users 表(左表)"]
U1["Alice (id=1)"]
U2["Bob (id=2)"]
U3["Charlie (id=3)"]
U4["Diana (id=4)"]
end
subgraph orders["orders 表(右表)"]
O1["order#1 (user_id=1, 100)"]
O2["order#2 (user_id=1, 200)"]
O3["order#3 (user_id=2, 300)"]
O4["order#4 (user_id=3, 50)"]
O5["order#5 (user_id=3, 70)"]
end
%% 匹配关系
U1 --- O1
U1 --- O2
U2 --- O3
U3 --- O4
U3 --- O5
%% Diana 没订单,不参与连接
对应 SQL
查询结果
| name | amount |
|---|---|
| Alice | 100.00 |
| Alice | 200.00 |
| Bob | 300.00 |
| Charlie | 50.00 |
| Charlie | 70.00 |
4. 全连接(FULL JOIN)
全连接(FULL JOIN)—— 两边都保留(MySQL 不支持,可用 UNION 模拟)
graph LR
subgraph users["users 表(左表)"]
U1["Alice (id=1)"]
U2["Bob (id=2)"]
U3["Charlie (id=3)"]
U4["Diana (id=4)"]
end
subgraph orders["orders 表(右表)"]
O1["order#1 (user_id=1, 100)"]
O2["order#2 (user_id=1, 200)"]
O3["order#3 (user_id=2, 300)"]
O4["order#4 (user_id=3, 50)"]
O5["order#5 (user_id=3, 70)"]
end
%% 匹配关系
U1 --- O1
U1 --- O2
U2 --- O3
U3 --- O4
U3 --- O5
U4 -.-> NULL右["(无匹配,右表为 NULL)"]
对应 SQL(MySQL 模拟实现)
查询结果
| name | amount |
|---|---|
| Alice | 100.00 |
| Alice | 200.00 |
| Bob | 300.00 |
| Charlie | 50.00 |
| Charlie | 70.00 |
| Diana | NULL |
5. 交叉连接(CROSS JOIN)
交叉连接(CROSS JOIN)—— 笛卡尔积(两表全组合),CROSS JOIN 不需要任何连接条件,它会把左表的每一行与右表的每一行 两两组合,一般不常用,会生成所有组合(性能差)。
graph LR
subgraph users["users 表(左表)"]
U1["Alice (id=1)"]
U2["Bob (id=2)"]
U3["Charlie (id=3)"]
U4["Diana (id=4)"]
end
subgraph orders["orders 表(右表)"]
O1["order#1 (100)"]
O2["order#2 (200)"]
O3["order#3 (300)"]
O4["order#4 (50)"]
O5["order#5 (70)"]
end
%% 每个用户都和所有订单组合(笛卡尔积)
U1 --- O1
U1 --- O2
U1 --- O3
U1 --- O4
U1 --- O5
U2 --- O1
U2 --- O2
U2 --- O3
U2 --- O4
U2 --- O5
U3 --- O1
U3 --- O2
U3 --- O3
U3 --- O4
U3 --- O5
U4 --- O1
U4 --- O2
U4 --- O3
U4 --- O4
U4 --- O5
对应 SQL
查询结果(部分示例)
| name | amount |
|---|---|
| Alice | 100.00 |
| Alice | 200.00 |
| Alice | 300.00 |
| Alice | 50.00 |
| Alice | 70.00 |
| Bob | 100.00 |
| Bob | 200.00 |
| Bob | 300.00 |
| Bob | 50.00 |
| Bob | 70.00 |
| Charlie | 100.00 |
| … | … |
| Diana | 70.00 |
四、分组与聚合
1. GROUP BY + 聚合函数
聚合函数用来对数据集计算汇总结果:
| 函数 | 含义 |
|---|---|
| COUNT() | 计数 |
| SUM() | 求和 |
| AVG() | 平均值 |
| MAX() | 最大值 |
| MIN() | 最小值 |
示例:每个用户的订单总额是多少?
执行逻辑图
graph TD
A["users 表"] --> B["JOIN orders ON users.id = orders.user_id"]
B --> C["分组 GROUP BY users.name"]
C --> D["聚合 SUM(orders.amount)"]
D --> E["输出 name, total_amount"]
思路拆解
- JOIN:先把
users与orders关联; - GROUP BY:按
users.name分组; - SUM:对每个分组的
amount求和; - SELECT:只输出每个用户的名字和总金额。
查询结果
| name | total_amount |
|---|---|
| Alice | 300.00 |
| Bob | 300.00 |
| Charlie | 120.00 |
Diana 没有订单,因此没有出现在结果中(因为 INNER JOIN)。
2. 使用 HAVING 过滤聚合结果
示例:只显示订单总额大于 1000 的用户(这时必须用 HAVING,因为 WHERE 不能过滤聚合结果)。
HAVING 执行逻辑图
graph TD
A["JOIN users + orders"] --> B["GROUP BY users.name"]
B --> C["计算 SUM(orders.amount)"]
C --> D["HAVING SUM(orders.amount) > 1000"]
D --> E["输出结果"]
查询结果
| name | total_amount |
|---|---|
| Alice | 300.00 |
| Bob | 300.00 |
3. WHERE 与 HAVING 的区别
| 对比项 | WHERE | HAVING |
|---|---|---|
| 作用阶段 | 分组前过滤行 | 分组后过滤分组 |
| 可用列 | 原始列 | 原始列 + 聚合函数 |
| 示例 | WHERE amount > 100 |
HAVING SUM(amount) > 500 |
4. GROUP BY 与 HAVING 区别
| 项 | GROUP BY | HAVING |
|---|---|---|
| 作用阶段 | 对数据进行分组 | 对分组后的结果过滤 |
| 能否使用聚合函数 | 不可以(只能原始列) | 可以(原始列+聚合函数) |
| 执行顺序 | 在 SELECT 之前 | 在 SELECT 之后 |
五、常见子查询与嵌套查询
1. 子查询
子查询:指一个查询语句嵌套在另一个查询语句内部。 它通常出现在:
WHERE子句中FROM子句中SELECT子句中
子查询返回的结果,可以是一条记录、一个值、一个表,或一个布尔判断。
示例:查询下过大额订单的用户
2. 子查询的常见形式
子查询的常见形式(5种)
| 类型 | 写法位置 | 说明 | 示例 |
|---|---|---|---|
| ① 标量子查询(Scalar Subquery) | SELECT 子句中 | 返回单个值 | SELECT name, (SELECT COUNT(*) FROM orders WHERE orders.user_id = users.id) |
| ② 列子查询(Column Subquery) | WHERE / HAVING | 返回一列,用于 IN | WHERE user_id IN (SELECT id FROM users) |
| ③ 行子查询(Row Subquery) | WHERE | 返回多列 | WHERE (id, name) IN (SELECT id, name FROM users) |
| ④ 表子查询(Table Subquery) | FROM 子句中 | 返回一个临时表 | FROM (SELECT user_id, SUM(amount) total FROM orders GROUP BY user_id) |
| ⑤ 相关子查询(Correlated Subquery) | WHERE 子句中,引用外层表 | 外层行依赖子查询 | WHERE amount > (SELECT AVG(amount) FROM orders WHERE orders.user_id = users.id) |
3. 常见关键字与区别
IN、EXISTS、ANY、ALL
| 关键字 | 用法 | 返回结果 | 适合场景 |
|---|---|---|---|
IN |
判断是否在一个结果集合中 | 值集合匹配 | 小结果集(效率高) |
EXISTS |
判断子查询是否返回至少一行 | 布尔(TRUE/FALSE) | 大结果集(效率更优) |
ANY |
与比较符连用(> ANY / < ANY) |
与任意值比较 | 类似最小/最大值匹配 |
ALL |
与比较符连用(> ALL / < ALL) |
必须满足所有值 | 比较严格条件 |
1. IN 示例
查询有下单记录的用户:
查询结果:
| name |
|---|
| Alice |
| Bob |
| Charlie |
(因为 Diana 没订单)
2. EXISTS 示例
与上面功能相同,但用 EXISTS:
结果相同,但执行逻辑不同:
这段 SQL 想干嘛?查询所有有下过订单的用户。
也就是说:
- 从
users表取出每个用户; - 判断:这个用户的 id 是否出现在
orders.user_id中; - 如果是 → 返回该用户;
- 如果不是 → 不返回。
EXISTS 是一个逻辑判断(布尔测试),
它不看子查询返回的值,而只关心:
子查询是否返回至少一行数据?
- 如果子查询返回了 ≥1 行 →
EXISTS结果为TRUE - 如果子查询返回 0 行 →
EXISTS结果为FALSE
为什么是 SELECT 1?
因为 EXISTS 只关心“有没有行”,根本不看列内容。
所以写成 SELECT 1、SELECT *、SELECT o.id 都可以,效果完全一样。
只不过:
SELECT 1是最简洁、最推荐的写法(标准写法)SELECT *虽然也行,但数据库会多做点解析工作(不推荐)
逻辑执行过程
| 当前用户 | 内层查询 | 是否存在行? | 是否返回 |
|---|---|---|---|
| Alice (id=1) | SELECT 1 FROM orders WHERE user_id=1 → 有2行 |
TRUE | 返回 |
| Bob (id=2) | → 有1行 | TRUE | 返回 |
| Charlie (id=3) | → 有2行 | TRUE | 返回 |
| Diana (id=4) | → 没有行 | FALSE | 不返回 |
查询结果:
| name |
|---|
| Alice |
| Bob |
| Charlie |
与 IN 的对比
| 对比项 | IN |
EXISTS |
|---|---|---|
| 含义 | 值是否在集合中 | 是否存在满足条件的记录 |
| 执行方式 | 子查询先执行一次,生成结果集 | 外层每行执行一次子查询 |
| 性能建议 | 小结果集 → 用 IN |
大结果集 → 用 EXISTS |
| 是否依赖外层表 | 否 | 是(相关子查询) |
可视化理解
graph TD
U["users 表 u"] -->|逐行| Q["子查询 SELECT 1 FROM orders o WHERE o.user_id = u.id"]
Q -->|存在匹配记录?| E{"EXISTS 结果?"}
E -->|TRUE| R["返回该用户"]
E -->|FALSE| X["跳过"]
3. > ANY
查询至少有一笔订单金额大于 Alice 的任意一笔订单金额(即大于最小订单)的用户:
ANY 类似于 “至少比其中一个值大”,ANY (100, 200)。
4. > ALL
查询金额大于 所有 Alice 的订单金额 的用户:
ALL 类似于 “比所有值都大”。
4. 相关子查询
1. 相关子查询
子查询依赖外层表的字段,每次外层表扫描一行,都会重新执行子查询。
例:查询每个用户大于自己平均订单金额的订单。
- 外层表:
orders o - 内层子查询:
SELECT AVG(o2.amount) FROM orders o2 WHERE o2.user_id = o.user_id
内层子查询依赖 外层表 o 的 user_id,这就是“相关子查询”的核心。
逐行执行逻辑
我们先看原始数据:
| id | user_id | amount |
|---|---|---|
| 1 | 1 | 100 |
| 2 | 1 | 200 |
| 3 | 2 | 300 |
| 4 | 3 | 50 |
| 5 | 3 | 70 |
外层循环每行
外层表 orders o 每一行都会检查 amount 是否大于该用户的平均订单金额。
内层子查询计算每行的平均值
| 外层行 (o.id) | user_id | amount | 内层子查询 AVG(o2.amount) | 比较 o.amount > AVG |
|---|---|---|---|---|
| 1 | 1 | 100 | AVG(100,200) = 150 | 100 > 150 → 不满足 |
| 2 | 1 | 200 | AVG(100,200) = 150 | 200 > 150 → 满足 |
| 3 | 2 | 300 | AVG(300) = 300 | 300 > 300 → 不满足 |
| 4 | 3 | 50 | AVG(50,70) = 60 | 50 > 60 → 不满足 |
| 5 | 3 | 70 | AVG(50,70) = 60 | 70 > 60 → 满足 |
输出结果
| id | user_id | amount |
|---|---|---|
| 2 | 1 | 200 |
| 5 | 3 | 70 |
只有大于自己平均订单金额的订单会被选出来。
2. 普通子查询 vs 相关子查询的执行顺序
普通子查询(不依赖外层表)
-
执行顺序:
- 先执行子查询,计算 AVG(amount) → 得到单一值
- 外层查询使用这个值去筛选每行数据
执行顺序:子查询 → 外层查询
相关子查询(依赖外层表字段)
-
执行顺序:
- 外层表逐行扫描
- 对当前外层行,执行子查询 → 计算当前用户的 AVG
- 用子查询结果判断当前行是否满足条件
- 扫描下一行,重复步骤 2-3
执行顺序:外层查询行扫描 → 对每行执行子查询
| 类型 | 子查询是否依赖外层 | 执行顺序 |
|---|---|---|
| 普通子查询 | 否 | 子查询先 → 外层查询使用结果 |
| 相关子查询 | 是 | 外层查询逐行 → 对每行执行子查询 |
5. 总结对比表
| 类型 | 出现位置 | 返回结果 | 特点 |
|---|---|---|---|
| 标量子查询 | SELECT | 单个值 | 常用于列计算 |
| 列子查询 | WHERE / HAVING | 一列 | 搭配 IN |
| 行子查询 | WHERE | 多列 | 搭配 (a,b) IN (...) |
| 表子查询 | FROM | 多行多列 | 生成中间表 |
| 相关子查询 | WHERE | 动态多次执行 | 性能差但灵活 |
| 关键字 | 含义 | 匹配规则 |
|---|---|---|
| IN | 值是否在集合中 | 完全匹配 |
| EXISTS | 是否存在满足条件的行 | 布尔判断 |
| ANY | 比较符 + 任意一个成立 | 只要部分满足 |
| ALL | 比较符 + 全部成立 | 必须全部满足 |
六、SQL 执行顺序
这条执行路径是 SQL 的“灵魂线”,理解执行顺序可以让你写更复杂的查询:
|
|
七、总结
程序员职业的基础技能,必知必会。
文章作者 会写代码的小郎中
上次更新 2017-09-13
许可协议 CC BY-NC-ND 4.0