在日常开发中,多表 LEFT JOIN 后再加 WHERE 过滤,看起来人畜无害,实则暗藏“语义降级”风险。以下是一条被同事踩坑的真实 SQL(表名和字段已做模糊处理):
-- 业务场景:拿主表交易日期 + 维度表月份,保留所有交易
SELECT
t.trade_dt, -- 主表时间戳
d.dim_month -- 维度表月份
FROM main_trade t
LEFT JOIN dim_date d ON t.trade_dt = d.cal_dt
LEFT JOIN dim_date_a a ON t.trade_dt = a.cal_dt
WHERE d.dim_month > '2025-07-18'::date;
乍一看是两条 LEFT JOIN,但执行结果却把没匹配到维度表的记录全部丢光。为什么?
一、LEFT JOIN 的语义快速回顾
-
LEFT JOIN 会保留左表(main_trade)的所有行。
-
右表未匹配到的列以 NULL 填充。
二、WHERE 对 NULL 的“绝杀”
WHERE 发生在 JOIN 之后。
一旦对右表字段写了过滤条件(d.dim_month > ...
),NULL 值被排除;
于是 LEFT JOIN 退化成 INNER JOIN。
步骤 | 行数变化 | 说明 |
---|---|---|
1. LEFT JOIN | 主表 100 条 → 100 条(含 NULL) | 正常 |
2. WHERE d.dim_month > ... | 100 条 → 70 条 | NULL 被过滤,语义等价 INNER |
三、如何保留 LEFT JOIN 语义
把对右表的过滤挪到 ON 子句 里:
SELECT
t.trade_dt,
d.dim_month
FROM main_trade t
LEFT JOIN dim_date d
ON t.trade_dt = d.cal_dt
AND d.dim_month > '2025-07-18'::date -- 这里过滤
LEFT JOIN dim_date_a a
ON t.trade_dt = a.cal_dt;
此时未匹配到的交易日期仍被保留,d.dim_month
为 NULL。
四、一眼判断“是否退化”的口诀
“WHERE 里出现了右表字段,且不是 IS NULL
判断 → 等价 INNER JOIN。”
五、性能小贴士
-
若必须保留 LEFT JOIN 语义,又想在右表走索引,可在 ON 子句里加过滤,再对右表建 复合索引 (cal_dt, dim_month)。
-
如果业务可接受 INNER,则放心用 WHERE;执行计划会更简单。
六、一句话总结
LEFT JOIN 后再对右表字段做非 NULL 过滤,等同于 INNER JOIN;
要么改写成 ON 条件,要么接受语义变化,千万别被“LEFT”字样误导。