iT邦幫忙

0

SQL 的查询逻辑

sql
  • 分享至 

  • xImage
  •  

学习 SQL 最重要的不是死记语法,而是先建立一个清晰的查询运行模型:

数据从哪里来 → 表怎么关联 → 数据怎么过滤 → 怎么分组 → 怎么计算 → 输出什么 → 怎么排序


1. 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
限制最终返回的数据数量

可以记成一句话:

找表 → 连表 → 过滤 → 分组 → 计算 → 输出 → 排序 → 限制。


2. 最简单的 SQL:FROM → SELECT

例如:

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
最终输出什么?

3. FROM:确定查询的起点

例如:

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

4. 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

决定了:

两张表通过什么字段建立关系。


5. JOIN 默认就是 INNER JOIN

下面两种写法:

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 没有匹配到商品,因此不会出现在最终结果中。


6. LEFT JOIN:左表全部保留

例如:

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 的左表,所以王五仍然必须保留。


7. JOIN 最重要的理解:不是所有表都必须和 FROM 基础表直接关联

这是理解多表 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 可以一层一层继续关联。


8. 多表 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

9. 多表 JOIN 应该理解成“关系链”

错误的理解方式:

              ┌── 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 最重要的思维之一。


10. 为什么后面的 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 理解成:

从一个起点开始,沿着表之间的关系,一张一张把需要的数据连接进当前查询。


11. ON:决定表之间怎么关联

例如:

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

       ↓

找到对应商品

12. WHERE:过滤不需要的数据

完成 FROM + JOIN 之后,我们已经准备好了需要的数据来源。

接下来:

WHERE pay.payment_status = '支付成功'

表示:

过滤掉不符合条件的数据。

例如:

order_id    product    payment_status
-------------------------------------
1001        手机       支付成功
1002        电脑       支付失败
1003        鼠标       支付成功
1004        键盘       未支付

经过:

WHERE payment_status = '支付成功'

剩下:

1001    手机    支付成功
1003    鼠标    支付成功

所以:

FROM / JOIN
准备数据

      ↓

WHERE
过滤数据

13. GROUP BY:对数据进行分组

假设 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 = 排序

14. SUM / COUNT / AVG:进行聚合计算

分组之后,可以使用:

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

15. SELECT:决定最终输出什么

经过:

FROM
JOIN
WHERE
GROUP BY
聚合计算

之后,再决定最终显示哪些数据。

例如:

SELECT
    p.product_id,
    p.product_name,
    SUM(oi.quantity) AS total_sales

表示最终需要:

商品ID
商品名称
商品总销量

16. ORDER BY:对结果排序

例如:

ORDER BY total_sales DESC

假设:

手机    500
电脑    800
鼠标    300

排序后:

电脑    800
手机    500
鼠标    300

其中:

DESC
大 → 小

ASC
小 → 大

所以再次记住:

GROUP BY
负责分组

ORDER BY
负责排序

17. LIMIT:限制最终返回数量

例如:

ORDER BY total_sales DESC
LIMIT 20;

意思:

销量从高到低排序

       ↓

只拿前20条

       ↓

销量TOP 20

18. 一条完整多表 SQL 的执行逻辑

例如:

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 名。


19. 普通 SQL 的完整脑内模型

以后看到普通 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

最终返回多少条?

最核心口诀:

确定起点 → 沿关系连表 → 过滤 → 分组 → 计算 → 输出 → 排序 → 限制。


20. 高级查询:子查询

掌握普通查询以后,再看:

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

21. 普通子查询:先理解内层,再理解外层

对于普通、非关联子查询,学习阶段可以理解为:

先处理内层 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)

       ↓

李四
王五

所以普通子查询可以记:

先内后外。


22. 子查询里面也可以有完整 SQL

例如:

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 可以:

从最里面开始,一层一层往外拆。


23. 关联子查询是特殊情况

例如:

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
关联子查询

它不能简单理解成:

内层一次全部执行完成
↓
再执行外层

因为内层需要外层当前行的数据。


24. SQL 查询的两套核心模型

模型一:普通 SQL

FROM
确定查询起点

 ↓

JOIN + ON
沿着表之间的关系连接其他表

 ↓

WHERE
过滤数据

 ↓

GROUP BY
分组

 ↓

SUM / COUNT / AVG
聚合计算

 ↓

SELECT
输出字段

 ↓

ORDER BY
排序

 ↓

LIMIT
限制数量

记忆:

找表 → 连表 → 过滤 → 分组 → 计算 → 输出 → 排序 → 限制。


模型二:普通子查询

发现 SQL 里面还有 SELECT

          ↓

找到最内层 SELECT

          ↓

按照普通 SQL 模型分析

FROM
 ↓
JOIN
 ↓
WHERE
 ↓
GROUP BY
 ↓
计算
 ↓
SELECT

          ↓

得到子查询结果

          ↓

把结果交给上一层

          ↓

继续按照普通 SQL 模型分析

          ↓

最终结果

记忆:

普通查询:从数据来源开始处理。

普通子查询:从最里面的查询开始理解,再一层一层向外。


25. 写 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

第九步:只需要20个

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;

26. 最终 SQL 思维导图

                         SQL 查询
                            │
              ┌─────────────┴─────────────┐
              │                           │
          普通查询                      子查询
              │                           │
              ↓                           ↓
            FROM                    找最内层 SELECT
              │                           │
              ↓                           ↓
         JOIN + ON                  按普通查询分析
              │                           │
              ↓                           ↓
     沿表之间关系继续 JOIN              得到结果
              │                           │
              ↓                           ↓
            WHERE                    返回上一层
              │                           │
              ↓                           ↓
          GROUP BY                  继续分析外层
              │
              ↓
     SUM / COUNT / AVG
              │
              ↓
           SELECT
              │
              ↓
         ORDER BY
              │
              ↓
           LIMIT
              │
              ↓
          最终结果

27. 最终总结

普通 SQL

看到:

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 的逻辑继续处理。


补充:逻辑模型 ≠ 数据库实际物理执行顺序

上面的顺序主要用于:

  • 学习 SQL
  • 阅读 SQL
  • 设计 SQL
  • 理解查询结果

MySQL、PostgreSQL 等数据库真正执行 SQL 时,还有一个:

查询优化器
Query Optimizer

优化器可能:

调整 JOIN 顺序
使用索引
改写子查询
选择不同执行计划

因此数据库底层实际执行顺序不一定严格按照我们脑内模型进行。

现阶段学习 SQL 时,优先掌握:

FROM
 ↓
JOIN
 ↓
WHERE
 ↓
GROUP BY
 ↓
聚合计算
 ↓
SELECT
 ↓
ORDER BY
 ↓
LIMIT

以及:

普通子查询
最内层 → 外层

这两套思维模型即可。


圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言