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