一个 LEFT JOIN 的空行全没了|搞懂 SQL 执行顺序才明白

简介: LEFT JOIN 过滤条件写进 WHERE 导致左表行丢失,讲清执行顺序,复现 LEFT JOIN 变 INNER JOIN 与别名失效,区分逻辑顺序与执行计划,给出 ON、WHERE 判定规则。

大家好,我是数据库小学妹👋 我踩过的坑,你别再踩。

有段时间我写 SQL,总以为数据库是按我写的顺序从上往下跑的。写什么,它就按什么顺序执行。结果一个 LEFT JOIN 的过滤条件,被我写进了 WHERE。跑完一看,左边表里没配对的行全没了。看着像 LEFT JOIN,结果出来的跟 INNER JOIN 一模一样。更气人的是,它不报错。那次之后我才搞清楚,SQL 的书写顺序和执行顺序,是两码事。搞混了,语法上挑不出毛病,结果就是不对。这种错最难查,只能拿数据一行行对。

真实的执行顺序

数据库跑一条 SQL,不是从上往下读。它按一个固定的逻辑顺序走,分阶段处理。

FROM → ON → JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT

每一步的输出,是下一步的输入。这个顺序决定了你能在哪个阶段用什么东西,也决定了条件放哪个位置。最关键的几个分水岭。ON 和 JOIN 在 WHERE 之前,所以写在 ON 里的条件影响的是连接时的保留规则,而写在 WHERE 里的条件,是在连接完成后对结果再做过滤。SELECT 在 WHERE 之后,所以别名在 WHERE 里还不存在。HAVING 在 GROUP BY 之后,过滤的是聚合后的结果。顺序不用死记。后面两个翻车现场,一看就知道错在哪一步。

翻车一:LEFT JOIN 变成 INNER JOIN

来看我踩的那个坑。一张用户表,一张订单表。我想查所有用户以及他们状态为 1 的订单,没订单的也要留着。

SELECT u.user_name, o.order_no, o.amount
FROM t_user u
LEFT JOIN t_order o ON u.user_id = o.user_id
WHERE o.status = 1;

看着没问题。但跑出来的结果里,那些还没下过单的用户,一条都没了。为什么。LEFT JOIN 的语义是:左边表的所有行都保留,右边表匹配不到的行补 NULL。这一步做完,新用户的订单字段全是 NULL。然后轮到 WHERE,o.status = 1 这个条件一过滤,NULL = 1 为假,新用户的行就被扔掉了。连接阶段保住的行,在 WHERE 阶段又丢了。结果就是 LEFT JOIN 被 WHERE 条件吃掉了外层,退化成 INNER JOIN。

正确的写法,是把右边表的过滤移到 ON 里。

SELECT u.user_name, o.order_no, o.amount
FROM t_user u
LEFT JOIN t_order o ON u.user_id = o.user_id AND o.status = 1;

ON 里的条件在连接时就生效,o.status = 1 作为连接条件的一部分,不会把左表未匹配的行筛掉。来对比一下两种写法的结果差异,数值是示意,实机跑一遍更准。

CREATE TABLE t_user (user_id INT PRIMARY KEY, user_name VARCHAR(20));
CREATE TABLE t_order (order_id INT PRIMARY KEY, user_id INT, status INT, order_no VARCHAR(20), amount DECIMAL(10,2));
INSERT INTO t_user VALUES (1,'Alice'),(2,'Bob'),(3,'Carol');
INSERT INTO t_order VALUES (101,1,1,'ORD001',50.00),(102,1,2,'ORD002',30.00);
-- Carol 没有订单
写法 返回行数 Carol 在结果里吗
WHERE o.status = 1 1 行(Alice 的 ORD001) 不在
ON ... AND o.status = 1 3 行(Alice 一条、Bob 一条补 NULL、Carol 一条补 NULL) 在

翻车二:WHERE 里用不了别名

另一个坑,出在别名上。我想查总金额大于 100 的明细。

SELECT user_id, amount * quantity AS total
FROM t_item
WHERE total > 100;
-- ERROR 1054: Unknown column 'total' in 'WHERE'

报错说列不存在。但 total 明明在 SELECT 里定义了。原因在顺序。WHERE 在 SELECT 之前执行。数据库跑到 WHERE 那一步时,total 这个别名还没诞生,它不知道你在指什么。解决办法有三个,一是重复表达式。

SELECT user_id, amount * quantity AS total
FROM t_item
WHERE amount * quantity > 100;

二是用子查询,把别名提出来。

SELECT * FROM (
  SELECT user_id, amount * quantity AS total
  FROM t_item
) t WHERE t.total > 100;

三,在 MySQL 里可以用 HAVING 代替 WHERE 来过滤别名,因为 HAVING 在 SELECT 之后。

SELECT user_id, amount * quantity AS total
FROM t_item
HAVING total > 100;

但要注意,HAVING 是过滤聚合结果的,拿来当 WHERE 用虽然 MySQL 能跑,但可读性差,而且可能影响优化器的走法。能用 WHERE 就用 WHERE,HAVING 留给聚合场景。

逻辑顺序与执行计划,不是一回事

上面说的是逻辑顺序,SQL 语义上的阶段顺序。但优化器在真正执行时,可以重排。比如一个 WHERE 过滤条件,优化器可能把它"下推"到表扫描阶段,先在扫描时就过滤掉大量不匹配的行,再做 JOIN。比如 EXPLAIN 里看到 rows 很少,说明下推生效了。比如多个表的 JOIN 顺序,优化器可能把小表当驱动表,而不是你 SQL 里写的顺序。

这些重排是性能层面的,不影响语义结果。优化器保证重排前后的结果一样,只是在更快。所以 EXPLAIN 看的不是逻辑顺序,是优化器优化后的物理执行路径。两者都对:逻辑顺序决定你的语句对不对,执行计划决定它快不快。写 SQL 时想的是逻辑顺序,调性能时才去看执行计划。

写 SQL 时,条件到底该放哪

摸清规律,就几条判定规则。OUTER JOIN(LEFT / RIGHT)时,右边表的过滤条件,优先放 ON。放在 WHERE 里,会把外层吃掉。除非你的本意就是只保留匹配的行——那直接写 INNER JOIN 就行,别用 LEFT JOIN 再在 WHERE 里过滤。INNER JOIN 时,ON 和 WHERE 放哪都行,结果一样。但从可读性出发,连接条件放 ON,结果过滤放 WHERE,分工清楚。

聚合过滤,WHERE 过滤原始行,HAVING 过滤聚合结果。别把 SUM()、COUNT() 塞进 WHERE。

别名只在 SELECT 之后可用。ORDER BY 和 HAVING 里可以用别名,WHERE 里不行。

判断力

SQL 最难的地方,不是语法多复杂,是写的人不知道数据库在后台按什么顺序跑。书写顺序是给人看的,执行顺序是给机器跑的。把两者搞混,就会写出"看着对、结果错"的语句。最可怕的是它不报错,你只能靠对照数据才能发现不对。

避坑清单

OUTER JOIN 的过滤条件放 ON,别放 WHERE。放 WHERE 里会把外层吃掉,LEFT JOIN 变 INNER JOIN,不报错,结果直接错。

别名只属于 SELECT 之后的阶段。WHERE 里引用别名会报错,HAVING 里能用但别当 WHERE 替代方案用。

最后一条是我自己踩的。我一直以为 SQL 是按书写顺序跑的,直到 LEFT JOIN 的空行全没,才去查了这个顺序。现在每写一条带 JOIN 的 SQL,我都会问自己一遍:这个条件,是在连接之前还是之后生效?问完再落笔,比跑完再看结果省事得多。

写在最后

写 SQL 久了会发现,语法只是表层。真正决定结果对不对的,是你脑子里那条流水线。数据从 FROM 进来,经过 ON、WHERE、GROUP BY,每一步之后数据长什么样,别名到哪一步才存在,你得心里有数。数据库只负责跑,不会提醒你顺序想错了。下笔前先在脑子里过一遍,很多错根本不会发生。

你写 SQL 时,条件放 ON 还是 WHERE 是怎么判断的?有没有因为放错位置翻过车?评论区聊聊。

我是数据库小学妹,帮你少走弯路少踩坑,咱们下篇见👋

相关文章
|
5天前
|
云安全 人工智能 运维
当攻击全面Agent化,你的安全防御能否跟上AI Speed?
多起客户侧AK泄露事件应急的真实观察
1241 4
|
28天前
|
人工智能 JSON API
全网刷屏的 Jev 模型正式开放!一手实战测评 + 保姆级教程
全网爆火的 Jev 模型是什么?有什么用?怎么使用?怎么接入 AI 编程工具?效果真的好么?傻子可懂的 Jev 保姆级实战教程 + 项目实战测评来啦
9782 32
|
8天前
|
存储 人工智能 安全
千问办公官网入口网页版登录:qwenwork.cn 注册送2000积分、登录送100积分
阿里云千问办公(QwenWork)是AI智能工作平台,支持一句话生成PPT、表格、网页、视频及数据分析。个人免费版送2000积分+每日登录赠100分,含1GB存储、5个发布页;企业版198元/席/月起。阿里千问办公QwenWork官网:https://t.aliyun.com/U/0VCTGt 阿里AI工作平台,一句话完成数据分析、PPT 生成、视频剪辑、网页搭建等复杂任务
|
3天前
|
人工智能 API 开发工具
DeepSeek Harness 神插件:白嫖 13 家大厂模型
`dsh-our-free-model` 是 DeepSeek Harness 神级插件:零配置、零API Key、零账号,聚合13家大厂免费模型(含DeepSeek V4.1 Flash、Kimi K3等),支持本地网关、用量看板与自动降级,真正实现“白嫖即用”。
|
14天前
|
存储 人工智能 并行计算
大模型本地部署终端选型方法论:以 Qwen3.8-27B 为例的四档分层完整流程
本文提出一套大模型本地部署终端选型方法论:定约束、定档位、定框架、定参数四步决策法,配合入门、主力、质量、无损四档分层模型。以 Qwen3.8-27B 实测数据为例,逐环节解读显存、带宽、存储、散热、系统、预算等要素,给出面向不同预算的优选方案、决策自查清单与市场观察框架。文末前瞻 AI 笔记本的 CPU+GPU 与统一内存两条路线,论证四步决策法在新品类上的延续性。
|
5天前
|
Web App开发 人工智能 JavaScript
太猛了,一个月干到15W+,给大家推荐一个最近爆火的开源逆向工具:REA
REA(Reverse Engineer Anything)是近期爆火的开源AI逆向工具,4月发布,1个月内斩获15万+ Star。它让AI Agent无需源码即可分析二进制、APK、JS/Electron、HAR包等17类目标,自动反编译、生成可运行代码,并强制返回带证据与局限的结论。全本地执行,MIT协议,支持Hopper/Ghidra/IDA等引擎,一行命令即可上手。
621 1
太猛了,一个月干到15W+,给大家推荐一个最近爆火的开源逆向工具:REA
|
12天前
|
人工智能 JSON 自然语言处理
2026 年 Jev 决策模型深度拆解:原理解读、实战测评与保姆级落地教程
有一款特殊AI模型在开发者圈子刷屏,它摒弃传统大模型擅长的对话聊天能力,专注做高速结构化决策,它就是TypeSafe AI推出的Jev模型。该模型由ChatGPT共同发明人Diogo Almeida主导研发,定位为**System One Model(系统一模型)**,对标人类大脑快速直觉判断的思维模式,在响应延迟、调用成本、结构化输出稳定性上相比传统生成式大模型有着巨大差异。本文会完整拆解Jev底层原理、三大核心原语能力、适用业务场景,同时提供可直接运行的curl、Python代码示例,并且结合多组实测数据,客观分析模型优势与能力边界,帮助普通开发者和AI应用从业者快速上手落地。
927 1
|
9天前
|
人工智能 JSON 自然语言处理
2026 年 Jev 决策模型全解析:核心原理拆解,性能实战测评与落地部署教程
有一款特殊AI模型在开发者圈子刷屏,它摒弃传统大模型擅长的对话聊天能力,专注做高速结构化决策,它就是TypeSafe AI推出的Jev模型。该模型由ChatGPT共同发明人Diogo Almeida主导研发,定位为System One Model(系统一模型),对标人类大脑快速直觉判断的思维模式,在响应延迟、调用成本、结构化输出稳定性上相比传统生成式大模型有着巨大差异。本文会完整拆解Jev底层原理、三大核心原语能力、适用业务场景,同时提供可直接运行的curl、Python代码示例,并且结合多组实测数据,客观分析模型优势与能力边界,帮助普通开发者和AI应用从业者快速上手落地。
592 0