学习 SQL 最重要的不是死记语法,而是先建立一个清晰的查询运行模型:
数据从哪里来 → 表怎么关联 → 数据怎么过滤 → 怎么分组 → 怎么计算 → 输出什么 → 怎么排序
一条比较完整的 SQL:
SELECT ...
FROM ...
JOIN ...
WHERE ...
GROUP BY ...
ORDER BY ...
LIMIT ...
虽然代码从 SELECT 开始写,但是学习和理解 SQL 时,可以按照下面的逻辑分析:
① FROM
确定最开始的数据来源
↓
② JOIN
根据表之间的关系连接其他表
↓
③ WHERE
过滤掉不需要的数据
↓
④ GROUP BY
按照指定字段进行分组
↓
⑤ SUM / COUNT / AVG...
对分组后的数据进行聚合计算
↓
⑥ SELECT
选择最终需要输出的字段
↓
⑦ ORDER BY
对结果进行排序
↓
⑧ LIMIT
限制最终返回的数据数量
可以记成一句话:
找表 → 连表 → 过滤 → 分组 → 计算 → 输出 → 排序 → 限制。
例如:
SELECT
name
FROM users;
假设:
users
id name age
-----------------
1 张三 25
2 李四 30
3 王五 28
理解时不要先想 SELECT。
先看:
FROM users
意思是:
从
users获取数据。
然后:
SELECT name
意思是:
最终只输出
name。
所以逻辑:
FROM users
↓
获得 users 数据
↓
SELECT name
↓
只输出 name
结果:
name
----
张三
李四
王五
因此最基础的 SQL 思维:
FROM
数据从哪里来?
↓
SELECT
最终输出什么?
例如:
SELECT *
FROM order_items;
这里:
FROM order_items
可以理解成:
本次查询从
order_items开始获取数据。
假设:
order_items
order_id product_id quantity
----------------------------------
1001 1 2
1001 2 1
1002 1 3
如果需要的数据全部都在 order_items:
SELECT
order_id,
product_id,
quantity
FROM order_items;
就不需要 JOIN。
但是如果还想知道:
商品叫什么?
用户是谁?
订单是否支付成功?
商品属于什么分类?
这些数据可能存在其他表中。
这时候就需要:
JOIN
例如:
FROM order_items oi
JOIN products p
ON oi.product_id = p.product_id
可以理解:
order_items
作为查询起点
↓
通过 product_id
↓
找到 products 中对应商品
↓
把 products 的数据连接进来
关系:
order_items products
------------ --------
product_id ─────────────→ product_id
quantity product_name
brand
category_id
所以:
ON oi.product_id = p.product_id
决定了:
两张表通过什么字段建立关系。
下面两种写法:
JOIN products p
ON oi.product_id = p.product_id
和:
INNER JOIN products p
ON oi.product_id = p.product_id
含义相同。
所以:
JOIN
=
INNER JOIN
INNER JOIN 的规则:
两边能够匹配成功的数据才保留。
例如:
order_items
product_id quantity
----------------------
1 2
2 3
999 1
products:
product_id product_name
--------------------------
1 手机
2 电脑
3 鼠标
执行:
SELECT
oi.product_id,
oi.quantity,
p.product_name
FROM order_items oi
JOIN products p
ON oi.product_id = p.product_id;
得到:
product_id quantity product_name
--------------------------------------
1 2 手机
2 3 电脑
999 没有匹配到商品,因此不会出现在最终结果中。
例如:
FROM users u
LEFT JOIN orders o
ON u.id = o.user_id
这里:
users = 左表
orders = 右表
可以理解成:
以 users 为基础,把 orders 中能够匹配的数据连接进来。
LEFT JOIN 的核心规则:
左表全部保留
右表能够匹配 → 正常连接
右表不能匹配 → 使用 NULL
可以记:
A LEFT JOIN B:A 全要,B 随缘。
例如:
users
id name
-----------
1 张三
2 李四
3 王五
orders:
user_id product
------------------
1 手机
1 电脑
2 鼠标
SQL:
SELECT
u.name,
o.product
FROM users u
LEFT JOIN orders o
ON u.id = o.user_id;
结果:
name product
---------------
张三 手机
张三 电脑
李四 鼠标
王五 NULL
因为王五没有订单,但是 users 是 LEFT JOIN 的左表,所以王五仍然必须保留。
这是理解多表 JOIN 非常重要的一点。
假设:
FROM users u
不要错误理解成:
后面所有 JOIN 都必须使用 users 的字段。
这是不对的。
例如下面这条 SQL 完全正常:
SELECT
u.name,
o.order_id,
p.product_name,
c.category_name
FROM users u
JOIN orders o
ON u.id = o.user_id
JOIN order_items oi
ON o.order_id = oi.order_id
JOIN products p
ON oi.product_id = p.product_id
JOIN categories c
ON p.category_id = c.category_id;
这里形成的是一条关系链:
users
│
│ id = user_id
↓
orders
│
│ order_id
↓
order_items
│
│ product_id
↓
products
│
│ category_id
↓
categories
所以 JOIN 可以一层一层继续关联。
仔细分析:
FROM users u
第一步:
当前数据:
users
然后:
JOIN orders o
ON u.id = o.user_id
现在可以理解为:
当前数据:
users
+
orders
也就是说,现在查询中已经可以使用:
u.id
u.name
o.order_id
o.user_id
因此下一步完全可以:
JOIN order_items oi
ON o.order_id = oi.order_id
这里使用的:
o.order_id
不是最开始 users 的字段。
而是刚刚 JOIN 进来的:
orders
中的字段。
完成之后:
当前数据:
users
+
orders
+
order_items
现在又可以使用:
oi.product_id
所以继续:
JOIN products p
ON oi.product_id = p.product_id
完成:
users
+
orders
+
order_items
+
products
现在可以使用:
p.category_id
所以继续:
JOIN categories c
ON p.category_id = c.category_id
最终:
users
+
orders
+
order_items
+
products
+
categories
错误的理解方式:
┌── orders
│
users ────────┼── order_items
│
├── products
│
└── categories
认为:
所有表都必须直接和 users 关联。
这是错误的。
正确理解:
users
↓
orders
↓
order_items
↓
products
↓
categories
数据库中的表本身就可能形成关系网络。
例如:
payments
↑
│
users ───→ orders ─────┤
│ │
│ ↓
│ logistics
↓
order_items
↓
products
↓
categories
对应:
FROM users u
JOIN orders o
ON u.id = o.user_id
JOIN payments pay
ON o.order_id = pay.order_id
JOIN logistics l
ON o.order_id = l.order_id
JOIN order_items oi
ON o.order_id = oi.order_id
JOIN products p
ON oi.product_id = p.product_id
JOIN categories c
ON p.category_id = c.category_id
所以以后写 JOIN,不要问:
“这张表怎么和最开始的基础表关联?”
而应该问:
“我要加入这张表,它和当前已经加入的哪张表存在关联关系?”
这是多表 JOIN 最重要的思维之一。
因为每执行/理解一层 JOIN,当前查询的数据范围就在扩大。
例如:
FROM users u
当前:
users
↓
JOIN orders o
ON u.id = o.user_id
当前:
users + orders
↓
JOIN order_items oi
ON o.order_id = oi.order_id
当前:
users + orders + order_items
↓
JOIN products p
ON oi.product_id = p.product_id
当前:
users + orders + order_items + products
↓
JOIN categories c
ON p.category_id = c.category_id
当前:
users
+
orders
+
order_items
+
products
+
categories
因此可以把多表 JOIN 理解成:
从一个起点开始,沿着表之间的关系,一张一张把需要的数据连接进当前查询。
例如:
JOIN orders o
ON u.id = o.user_id
其中:
JOIN orders
表示:
我要把 orders 加入查询。
而:
ON u.id = o.user_id
表示:
users 和 orders 通过什么条件进行匹配。
所以:
JOIN
决定:加入谁
ON
决定:怎么加入
例如:
JOIN products p
ON oi.product_id = p.product_id
表示:
加入 products
↓
通过 product_id
↓
找到对应商品
完成 FROM + JOIN 之后,我们已经准备好了需要的数据来源。
接下来:
WHERE pay.payment_status = '支付成功'
表示:
过滤掉不符合条件的数据。
例如:
order_id product payment_status
-------------------------------------
1001 手机 支付成功
1002 电脑 支付失败
1003 鼠标 支付成功
1004 键盘 未支付
经过:
WHERE payment_status = '支付成功'
剩下:
1001 手机 支付成功
1003 鼠标 支付成功
所以:
FROM / JOIN
准备数据
↓
WHERE
过滤数据
假设 WHERE 过滤之后得到:
product_id quantity
----------------------
1 2
1 3
1 1
2 5
2 2
3 4
现在要求:
统计每一种商品的销量。
首先:
GROUP BY product_id
相当于:
商品1
------
2
3
1
商品2
------
5
2
商品3
------
4
所以:
GROUP BY的作用是分组。
特别注意:
GROUP BY ≠ 排序
GROUP BY = 分组
ORDER BY = 排序
分组之后,可以使用:
SUM() 求和
COUNT() 统计数量
AVG() 平均值
MAX() 最大值
MIN() 最小值
例如:
SELECT
product_id,
SUM(quantity) AS total_sales
FROM order_items
GROUP BY product_id;
先:
GROUP BY product_id
得到:
商品1:
2
3
1
商品2:
5
2
商品3:
4
然后:
SUM(quantity)
分别计算:
商品1:
2 + 3 + 1 = 6
商品2:
5 + 2 = 7
商品3:
4
得到:
product_id total_sales
-------------------------
1 6
2 7
3 4
经过:
FROM
JOIN
WHERE
GROUP BY
聚合计算
之后,再决定最终显示哪些数据。
例如:
SELECT
p.product_id,
p.product_name,
SUM(oi.quantity) AS total_sales
表示最终需要:
商品ID
商品名称
商品总销量
例如:
ORDER BY total_sales DESC
假设:
手机 500
电脑 800
鼠标 300
排序后:
电脑 800
手机 500
鼠标 300
其中:
DESC
大 → 小
ASC
小 → 大
所以再次记住:
GROUP BY
负责分组
ORDER BY
负责排序
例如:
ORDER BY total_sales DESC
LIMIT 20;
意思:
销量从高到低排序
↓
只拿前20条
↓
销量TOP 20
例如:
SELECT
p.product_id,
p.product_name,
c.category_name,
p.brand,
SUM(oi.quantity) AS total_sales
FROM order_items oi
JOIN products p
ON oi.product_id = p.product_id
JOIN categories c
ON p.category_id = c.category_id
JOIN payments pay
ON oi.order_id = pay.order_id
WHERE pay.payment_status = '支付成功'
GROUP BY
p.product_id,
p.product_name,
c.category_name,
p.brand
ORDER BY total_sales DESC
LIMIT 20;
按照逻辑一步一步拆:
① FROM order_items
从订单商品记录开始
↓
② JOIN products
order_items.product_id
↓
products.product_id
获得商品信息
↓
③ JOIN categories
注意:
这里不需要回到最开始的 order_items。
直接使用刚刚 JOIN 进来的 products:
products.category_id
↓
categories.category_id
获得分类信息
↓
④ JOIN payments
使用:
order_items.order_id
↓
payments.order_id
获得支付信息
所以表关系实际上是:
┌── products ──→ categories
│
order_items ──┤
│
└── payments
然后:
⑤ WHERE
只留下:
payment_status = 支付成功
↓
⑥ GROUP BY
按照商品分组
↓
⑦ SUM(quantity)
计算每个商品总销量
↓
⑧ SELECT
输出:
商品ID
商品名称
分类
品牌
总销量
↓
⑨ ORDER BY
按照销量从高到低排序
↓
⑩ LIMIT
只取前20名
最终业务含义:
从订单商品记录开始 → 关联商品信息 → 再通过商品表关联分类 → 关联支付信息 → 过滤掉未支付成功的数据 → 按商品分组 → 计算销量 → 按销量降序排列 → 返回前 20 名。
以后看到普通 SQL:
SELECT ...
FROM A
JOIN B ON ...
JOIN C ON ...
JOIN D ON ...
WHERE ...
GROUP BY ...
ORDER BY ...
LIMIT ...
直接转换成:
① FROM
从哪张表开始?
↓
② JOIN
需要加入哪张表?
↓
③ ON
新表和当前已经加入的哪张表有关?
通过什么字段关联?
↓
④ 继续 JOIN
继续沿着表之间的关系
把其他表加入进来
↓
⑤ WHERE
哪些数据不要?
↓
⑥ GROUP BY
按照什么分组?
↓
⑦ SUM / COUNT / AVG
每一组计算什么?
↓
⑧ SELECT
最终输出什么?
↓
⑨ ORDER BY
最终怎么排序?
↓
⑩ LIMIT
最终返回多少条?
最核心口诀:
确定起点 → 沿关系连表 → 过滤 → 分组 → 计算 → 输出 → 排序 → 限制。
掌握普通查询以后,再看:
Subquery
子查询
子查询就是:
一个 SQL 查询内部,又出现了一个 SELECT。
例如:
SELECT
u.name
FROM users u
WHERE u.id IN (
SELECT o.user_id
FROM orders o
WHERE o.amount > 1000
);
这里有:
外层查询
SELECT u.name
FROM users u
WHERE u.id IN (...)
以及:
内层查询
SELECT o.user_id
FROM orders o
WHERE o.amount > 1000
对于普通、非关联子查询,学习阶段可以理解为:
先处理内层 SELECT
↓
内层产生结果
↓
结果交给外层
↓
外层继续处理
↓
最终结果
例如:
SELECT
u.name
FROM users u
WHERE u.id IN (
SELECT o.user_id
FROM orders o
WHERE o.amount > 1000
);
先看:
SELECT o.user_id
FROM orders o
WHERE o.amount > 1000;
假设:
orders
order_id user_id amount
-----------------------------
1001 1 500
1002 2 1500
1003 3 3000
1004 4 200
经过:
WHERE amount > 1000
得到:
user_id
-------
2
3
所以原来的 SQL 在逻辑上可以继续理解成:
SELECT
u.name
FROM users u
WHERE u.id IN (2, 3);
假设:
users
id name
-----------
1 张三
2 李四
3 王五
4 赵六
那么:
WHERE id IN (2,3)
↓
李四
王五
所以普通子查询可以记:
先内后外。
例如:
SELECT
u.name
FROM users u
WHERE u.id IN (
SELECT
o.user_id
FROM orders o
WHERE o.status = '已完成'
GROUP BY o.user_id
HAVING SUM(o.amount) > 10000
);
先完全不管外层。
只看:
SELECT
o.user_id
FROM orders o
WHERE o.status = '已完成'
GROUP BY o.user_id
HAVING SUM(o.amount) > 10000;
继续使用普通 SQL 思维:
FROM orders
↓
找到订单数据
WHERE
↓
只留下已完成订单
GROUP BY user_id
↓
按照用户分组
SUM(amount)
↓
计算每个用户消费金额
HAVING
↓
只留下总消费 > 10000 的组
SELECT user_id
↓
得到用户ID
假设结果:
2
5
8
再返回外层:
SELECT
u.name
FROM users u
WHERE u.id IN (2,5,8);
继续执行外层逻辑。
因此复杂 SQL 可以:
从最里面开始,一层一层往外拆。
例如:
SELECT
u.name,
(
SELECT COUNT(*)
FROM orders o
WHERE o.user_id = u.id
) AS order_count
FROM users u;
这里内层:
WHERE o.user_id = u.id
使用了外层:
users u
中的:
u.id
所以内层依赖外层当前数据。
逻辑上可以理解:
外层:
张三 id=1
↓
内层:
WHERE o.user_id = 1
↓
COUNT = 5
然后:
外层:
李四 id=2
↓
内层:
WHERE o.user_id = 2
↓
COUNT = 3
最终:
name order_count
-------------------
张三 5
李四 3
王五 0
这种叫:
Correlated Subquery
关联子查询
它不能简单理解成:
内层一次全部执行完成
↓
再执行外层
因为内层需要外层当前行的数据。
FROM
确定查询起点
↓
JOIN + ON
沿着表之间的关系连接其他表
↓
WHERE
过滤数据
↓
GROUP BY
分组
↓
SUM / COUNT / AVG
聚合计算
↓
SELECT
输出字段
↓
ORDER BY
排序
↓
LIMIT
限制数量
记忆:
找表 → 连表 → 过滤 → 分组 → 计算 → 输出 → 排序 → 限制。
发现 SQL 里面还有 SELECT
↓
找到最内层 SELECT
↓
按照普通 SQL 模型分析
FROM
↓
JOIN
↓
WHERE
↓
GROUP BY
↓
计算
↓
SELECT
↓
得到子查询结果
↓
把结果交给上一层
↓
继续按照普通 SQL 模型分析
↓
最终结果
记忆:
普通查询:从数据来源开始处理。
普通子查询:从最里面的查询开始理解,再一层一层向外。
假设需求:
查询支付成功订单中,销量最高的 20 个商品。
不要第一时间写:
SELECT ...
而是先思考业务关系。
order_items
所以:
FROM order_items oi
商品名称在:
products
所以:
JOIN products p
ON oi.product_id = p.product_id
支付信息在:
payments
所以:
JOIN payments pay
ON oi.order_id = pay.order_id
WHERE pay.payment_status = '支付成功'
GROUP BY
p.product_id,
p.product_name
SUM(oi.quantity)
SELECT
p.product_id,
p.product_name,
SUM(oi.quantity) AS total_sales
ORDER BY total_sales DESC
LIMIT 20;
最终:
SELECT
p.product_id,
p.product_name,
SUM(oi.quantity) AS total_sales
FROM order_items oi
JOIN products p
ON oi.product_id = p.product_id
JOIN payments pay
ON oi.order_id = pay.order_id
WHERE pay.payment_status = '支付成功'
GROUP BY
p.product_id,
p.product_name
ORDER BY total_sales DESC
LIMIT 20;
SQL 查询
│
┌─────────────┴─────────────┐
│ │
普通查询 子查询
│ │
↓ ↓
FROM 找最内层 SELECT
│ │
↓ ↓
JOIN + ON 按普通查询分析
│ │
↓ ↓
沿表之间关系继续 JOIN 得到结果
│ │
↓ ↓
WHERE 返回上一层
│ │
↓ ↓
GROUP BY 继续分析外层
│
↓
SUM / COUNT / AVG
│
↓
SELECT
│
↓
ORDER BY
│
↓
LIMIT
│
↓
最终结果
看到:
SELECT ...
FROM A
JOIN B ...
JOIN C ...
WHERE ...
GROUP BY ...
ORDER BY ...
LIMIT ...
不要只按照代码从上往下读。
脑子里应该是:
FROM
从哪里开始?
↓
JOIN
需要什么其他数据?
↓
ON
这张新表和当前已经加入的哪张表有关?
↓
继续 JOIN
沿着数据库表之间的关系继续连接
↓
WHERE
哪些数据不要?
↓
GROUP BY
按照什么分组?
↓
SUM / COUNT / AVG
每组计算什么?
↓
SELECT
最终显示什么?
↓
ORDER BY
怎么排序?
↓
LIMIT
最终要多少条?
最重要的一点:
FROM 只是确定查询的起点,并不意味着后面所有 JOIN 都必须直接与 FROM 的基础表关联。
后面的 JOIN 可以使用前面已经加入查询的表继续关联:
users
↓
orders
↓
order_items
↓
products
↓
categories
所以多表 JOIN 更准确的理解是:
从一张表开始,然后沿着数据库中表与表之间的真实关系,一步一步把需要的数据连接进来。
看到:
SELECT ...
FROM ...
WHERE ... IN (
SELECT ...
FROM ...
WHERE ...
);
学习阶段按照:
最内层查询
↓
得到结果
↓
把结果交给外层
↓
外层继续处理
↓
最终结果
理解。
最终可以把 SQL 查询浓缩成两句话:
普通 SQL:确定起点 → 沿关系连表 → 过滤 → 分组 → 计算 → 输出 → 排序 → 限制。
普通子查询:先分析最里面的查询,得到结果以后交给外层,再按照普通 SQL 的逻辑继续处理。
上面的顺序主要用于:
MySQL、PostgreSQL 等数据库真正执行 SQL 时,还有一个:
查询优化器
Query Optimizer
优化器可能:
调整 JOIN 顺序
使用索引
改写子查询
选择不同执行计划
因此数据库底层实际执行顺序不一定严格按照我们脑内模型进行。
现阶段学习 SQL 时,优先掌握:
FROM
↓
JOIN
↓
WHERE
↓
GROUP BY
↓
聚合计算
↓
SELECT
↓
ORDER BY
↓
LIMIT
以及:
普通子查询
最内层 → 外层
这两套思维模型即可。