SQL的LEFT JOIN是常用的多表关联方式,核心作用是保留左表的所有记录,无论右表是否存在匹配数据,右表无匹配时对应字段会填充为NULL。但实际使用中常常出现左表数据丢失的情况,绝大多数原因是过滤条件的位置放错了。

LEFT JOIN的基本逻辑
LEFT JOIN的关联过程分为两步,首先根据ON子句的条件匹配左右表的记录,左表的所有记录都会保留,右表能匹配到的就拼接对应数据,匹配不到的右表字段就设为NULL,之后才会执行WHERE子句的过滤。
我们可以用两个简单的测试表来演示,先创建用户表和订单表:
-- 创建用户表,存储用户信息
CREATE TABLE user_info (
user_id INT PRIMARY KEY,
user_name VARCHAR(50)
);
-- 创建订单表,存储用户订单信息
CREATE TABLE order_info (
order_id INT PRIMARY KEY,
user_id INT,
order_amount DECIMAL(10,2)
);
-- 插入测试数据
INSERT INTO user_info VALUES (1, '张三'), (2, '李四'), (3, '王五');
INSERT INTO order_info VALUES (101, 1, 100.00), (102, 1, 200.00), (103, 2, 150.00);
过滤条件位置导致的左表数据丢失问题
错误示例:过滤条件放在WHERE子句
假设我们需要查询所有用户及其订单金额大于150的订单信息,很多开发者会写出如下语句:
SELECT
u.user_id,
u.user_name,
o.order_id,
o.order_amount
FROM user_info u
LEFT JOIN order_info o ON u.user_id = o.user_id
WHERE o.order_amount > 150;
执行这个查询后,结果只会返回张三的订单102(金额200),李四和王五的记录都会丢失。原因是WHERE子句是在LEFT JOIN关联完成之后执行过滤,此时王五没有匹配的订单,o.order_amount为NULL,NULL和150比较的结果是NULL,不满足大于150的条件,所以王五的记录被过滤掉了;李四的订单金额是150,也不满足大于150的条件,同样被过滤。
正确示例:过滤条件放在ON子句
如果希望保留所有用户,同时只匹配订单金额大于150的订单,应该把订单金额的过滤条件放在ON子句中:
SELECT
u.user_id,
u.user_name,
o.order_id,
o.order_amount
FROM user_info u
LEFT JOIN order_info o ON u.user_id = o.user_id AND o.order_amount > 150;
这次查询会返回所有3个用户的记录:张三匹配到订单102,李四和王五没有符合条件的订单,对应订单字段为NULL。因为ON子句的过滤是在关联时生效,只会影响右表的匹配结果,不会过滤左表的记录。
两种写法的核心差异总结
我们可以通过表格对比两种写法的作用阶段和效果:
| 过滤条件位置 | 作用阶段 | 对左表的影响 | 适用场景 |
|---|---|---|---|
| ON子句 | 关联阶段 | 不影响左表记录保留 | 过滤右表的匹配条件,需要保留左表全部记录时使用 |
| WHERE子句 | 关联完成后 | 可能过滤掉左表记录 | 过滤最终查询结果,需要过滤左表自身数据时使用 |
实际场景的注意事项
如果过滤的是左表自身的字段,比如要查询用户名为张三的所有订单,那么条件放在ON或者WHERE子句效果是一致的:
-- 两种写法结果一致
SELECT
u.user_id,
u.user_name,
o.order_id,
o.order_amount
FROM user_info u
LEFT JOIN order_info o ON u.user_id = o.user_id
WHERE u.user_name = '张三';
SELECT
u.user_id,
u.user_name,
o.order_id,
o.order_amount
FROM user_info u
LEFT JOIN order_info o ON u.user_id = o.user_id AND u.user_name = '张三';
但如果是过滤右表的非关联字段,一定要确认是否需要保留左表无匹配的记录,如果需要保留就放在ON子句,如果不需要保留就可以放在WHERE子句。只要记住LEFT JOIN的核心是优先保留左表全部记录,就能快速判断过滤条件的正确位置,避免左表数据丢失的问题。