【Mysql】重新学一遍 SQL 执行顺序

重新学一遍 SQL 执行顺序:FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT


为什么"执行顺序"值得重新学一遍?

很多人写 SQL 是这样的:脑子里想着"我要查什么",手就直接写 SELECT 开头的语句。结果一遇到复杂查询就翻车------

  • WHERE 里用别名报错,为什么?
  • WHERE 里不能写聚合函数,凭什么?
  • LEFT JOIN 明明连了表,结果却比预想少?
  • HAVING 和 WHERE 到底什么时候该用哪个?

一、书写顺序 ≠ 执行顺序

你写出来的 SQL,从上到下是这样的(书写顺序):

sql 复制代码
SELECT    -- 3. 想查哪些列
FROM      -- 1. 从哪张表
JOIN      -- 2. 连哪张表
WHERE     -- 4. 过滤哪些行
GROUP BY  -- 5. 怎么分组
HAVING    -- 6. 过滤哪些组
ORDER BY  -- 7. 怎么排序
LIMIT     -- 8. 取前几条

但数据库实际执行的顺序是这样的:

sql 复制代码
FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT

核心认知:数据库拿到数据之后,是一步一步"加工"的------每一步吃进上一步的结果,吐出自己的结果,像一个工厂流水线。

这条流水线可以一句话记住:

先拿数据 → 再过滤 → 再分组 → 再算列 → 最后排序和分页


二、八步流水线逐段拆解

第 1 步:FROM ------ 打开数据源头

从哪张表取数据,就从哪开始。此时产生的是原始行集合

sql 复制代码
FROM orders

如果是子查询,子查询会在这里先执行完,产出一个临时表供后面使用:

sql 复制代码
FROM (SELECT id FROM orders WHERE status = 'PAID') t

第 2 步:JOIN ------ 把表拼起来

JOIN 发生在 FROM 之后、WHERE 之前。这一步把多张表按 ON 条件拼接,产出一个连接后的宽表

sql 复制代码
FROM customers c
JOIN orders o ON o.customer_id = c.id

这一步之后,后面所有步骤看到的都是"客户 × 订单"拼接后的完整行。

高频易错点①(ON vs WHERE 的坑)

LEFT JOIN 来说,右表的条件写在 ON 里 还是 WHERE 里,结果完全不同。

  • 写在 ON:保留左表所有行,右表不匹配的置 NULL
  • 写在 WHERE:不匹配的行直接被过滤掉,LEFT JOIN 效果退化成 INNER JOIN

这是"没记录也要返回"类需求(比如统计每个客户,没订单也要显示)最经典的翻车点。


第 3 步:WHERE ------ 行级过滤

WHERE 对连接后的每一行 做判断,不满足条件的行直接丢掉。这一步过滤的是"行",还没分组、还没算列

由此推出两个高频易错点:

高频易错点②(WHERE 不能用别名)

SELECT 里的别名要等到第 6 步才"出生",WHERE 在第 3 步执行,此时别名还不存在。

sql 复制代码
-- 报错:Unknown column 'total'
SELECT amount * 0.9 AS total
FROM orders
WHERE total > 100;      -- ❌ 别名此时还不存在

-- 正确:写原始表达式
WHERE amount * 0.9 > 100;   -- ✅

高频易错点③(WHERE 不能用聚合函数)

聚合函数(COUNT/SUM/MAX...)要等 GROUP BY 分组后才有意义,WHERE 在第 3 步、分组在第 4 步,所以 WHERE 里写 SUM(x) > 100 会直接报错------这是 HAVING 的地盘。


第 4 步:GROUP BY ------ 分组

把上面过滤完的行,按指定列的值分成若干组,每组一堆。

sql 复制代码
GROUP BY c.name

分组之后有个重要约束:SELECT 里只能出现"分组列"和"聚合函数"

sql 复制代码
SELECT c.name,                     -- ✅ 分组列
       COUNT(*) AS cnt,            -- ✅ 聚合函数
       c.address                   -- ❌ 非分组列、非聚合 → MySQL 默认模式会报错
FROM customers c
JOIN orders o ON o.customer_id = c.id
GROUP BY c.name;

为什么?分组后每组有多行,非分组列的"值"是不确定的(取哪一行?),数据库只能让你用"每组唯一的值"(分组列)或"把多行合成一个值"(聚合函数)。


第 5 步:HAVING ------ 组级过滤

GROUP BY 分组完成后,HAVING 对每一组做判断,淘汰不满足条件的组。

sql 复制代码
GROUP BY c.name
HAVING SUM(o.amount) >= 1000   -- 只保留总金额 ≥ 1000 的组

高频易错点④(HAVING vs WHERE 的区别)------面试几乎必问:

维度 WHERE HAVING
时机 分组 分组
对象 过滤 过滤
聚合函数 ❌ 不能用 ✅ 可以用
别名 ❌ 不能用 MySQL 里可以用(标准 SQL 不建议)

一句话:WHERE 管行,HAVING 管组。


第 6 步:SELECT ------ 算列、起别名

到这里才开始真正"算你要的列":投影需要的字段、做表达式计算、给列起别名。别名在这一步才诞生,所以它只对后面的 ORDER BY 可见,对前面的 WHERE 不可见。

sql 复制代码
SELECT c.name, SUM(o.amount) AS total

顺便一提:DISTINCT 去重也发生在这个阶段之后。


第 7 步:ORDER BY ------ 排序

对整个结果集排序。因为发生在 SELECT 之后,所以 ORDER BY 可以使用别名

sql 复制代码
ORDER BY total DESC

也可以排未出现在 SELECT 里的列(某些数据库下不推荐,但语法允许)。


第 8 步:LIMIT ------ 分页收尾

最后一步,按排序结果截取前 N 行(或分页)。

sql 复制代码
LIMIT 20        -- 前 20 条
LIMIT 10, 20    -- MySQL:跳过 10 条,取 20 条

高频易错点⑤ :LIMIT 是关键字,不能加引号 ,也不能写 LIMIT = 20。它一定在 ORDER BY 之后,所以"先排序再取前 N 条"永远是对的。


三、一个完整例子,跟着流水线走一遍

假设有两张表:

customers(客户)

id name level
1 张三 VIP
2 李四 NORMAL
3 王五 VIP

orders(订单)

id customer_id amount status created_at
1 1 600 PAID 2026-06-10
2 1 500 PAID 2026-06-20
3 2 2000 PAID 2026-06-15
4 3 300 PAID 2026-06-18
5 3 900 PAID 2026-05-01
6 1 100 PENDING 2026-06-25
sql 复制代码
SELECT c.name, SUM(o.amount) AS total
FROM customers c
JOIN orders o ON o.customer_id = c.id
WHERE c.level = 'VIP'
  AND o.status = 'PAID'
  AND o.created_at >= '2026-06-01'
  AND o.created_at <  '2026-07-01'
GROUP BY c.name
HAVING SUM(o.amount) >= 1000
ORDER BY total DESC
LIMIT 2;

数据库实际怎么做:

步骤 做了什么 中间结果
1. FROM 打开 customers、orders 两张表 两堆原始行
2. JOIN 按 customer_id 拼接成宽表 6 行(客户×订单)
3. WHERE 过滤:VIP 客户 + 已支付 + 6 月订单 剩 3 行:张三600、张三500、王五900(李四非VIP去掉、王五5月单去掉、张三PENDING去掉)
4. GROUP BY 按客户名分组 组1: 张三(2行),组2: 王五(1行)
5. HAVING 总金额 ≥ 1000 张三(1100) 保留;王五(900) 淘汰
6. SELECT 算列:SUM、起别名 total 张三 1100
7. ORDER BY total 倒序 张三 1100
8. LIMIT 取前 2 条 张三 1100(不足 2 条则全返)

最终结果:

name total
张三 1100

看到没?王五的 5 月订单在 WHERE 就被过滤了,没进分组;王五 6 月只有 900,在 HAVING 被淘汰;整个过程一步不差。


四、高频易错点速查表(背下来)

# 易错点 原因 正确做法
1 WHERE 用 SELECT 别名 别名第 6 步才诞生,WHERE 在第 3 步 WHERE 写原始表达式
2 WHERE 用聚合函数 聚合要等分组(第 4 步)后才有 聚合条件放 HAVING
3 LEFT JOIN 右表条件放 WHERE 不匹配行被过滤,退化成 INNER JOIN 右表条件放 ON
4 SELECT 出现非分组列 分组后该值不确定 只写分组列 + 聚合函数
5 LIMIT 加引号 / 加等号 LIMIT 是关键字、不是变量 LIMIT 20
6 分不清 HAVING 和 WHERE 混淆了行过滤和组过滤 行用 WHERE,组用 HAVING

五、记忆口诀

"从哪来、怎么拼、滤行、分组、滤组、算列、排序、截取"

对应:

  • 从哪来 → FROM
  • 怎么拼 → JOIN
  • 滤行 → WHERE
  • 分组 → GROUP BY
  • 滤组 → HAVING
  • 算列 → SELECT
  • 排序 → ORDER BY
  • 截取 → LIMIT

再配一句灵魂总结:

SELECT 是"最后才算账"的------它是流水线的后半段,不是起点。


相关推荐
这个DBA有点耶3 小时前
数据库一体机架构演进:从硬件堆叠到软硬深度耦合
服务器·网络·数据库·硬件架构·运维开发·database·数据库架构
安_4 小时前
RAG的向量数据库:为LLM提供语义搜索能力
数据库
喜欢的名字被抢了5 小时前
程序出问题怎么查,以及如何让它不掉线
java·运维·数据库
这个DBA有点耶5 小时前
3000万个应用共享一套数据库:多租户“逻辑表”架构是如何做到的?
数据库·架构·dba
测试运维日常笔记5 小时前
Oracle 数据库连接认证方式详解
数据库·oracle
ClouGence6 小时前
PostgreSQL 同步到 Iceberg:开源、私有部署与 SaaS 方案对比
数据库·postgresql·开源
这个DBA有点耶6 小时前
DBA进阶之路:从“修数据库”到“管架构债”
数据库·架构·dba
ACP广源盛139246256736 小时前
WAIC2026 国产超节点算力浪潮下@ACP#IX9104 在算力矩阵中的定位与落地场景
大数据·数据库·人工智能·嵌入式硬件·线性代数·矩阵
珠***格7 小时前
通信链路全打通:西格电力四可装置的 4G/5G + 加密认证技术详解
大数据·数据库·分布式·5g·架构·能源