news 2025/12/24 18:56:51

两张表关联查询,查询条件什么时候加到on上,什么时候加到where上面(过滤右表的条件应写在 ON 子句中,以保留左表所有行)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
两张表关联查询,查询条件什么时候加到on上,什么时候加到where上面(过滤右表的条件应写在 ON 子句中,以保留左表所有行)
SELECT adc.ID AS county_id, NVL(stats.shouldReport, 0) AS shouldReport, -- 应报企业数 NVL(stats.reported, 0) AS reported, -- 已报企业数(没填就是 0) ROUND( NVL(stats.reported, 0) / NULLIF(NVL(stats.shouldReport, 1), 0), 4 ) AS report_rate -- 填报率 FROM ACIM_DIC_COUNTY adc LEFT JOIN ( -- 核心统计:每个区的应报数和已报数 SELECT oewrt.COUNTY_ID, COUNT(*) AS shouldReport, -- 所有已备案企业 COUNT(oemr.RECORD_ID) AS reported -- 只统计填了月报的 FROM OIL_ENTER_WHSLEWHSE_RECORD_TAB oewrt LEFT JOIN OIL_ENTER_MONTH_REPORT oemr ON oewrt.id = oemr.RECORD_ID AND oemr.MONTH = '2025-09' -- ✅ 关键:条件在 ON WHERE oewrt.ENT_TYPE IN ('1', '3') AND oewrt.CONFIRM_STATUS = '2' GROUP BY oewrt.COUNTY_ID ) stats ON adc.ID = stats.COUNTY_ID WHERE shouldReport > 0 ; -- 或 SUBSTR('910101', 1, 2) || '%'

为什么AND oemr.MONTH = '2025-06'写在ON上“不过滤左表”,而写在WHERE上就“过滤掉左表行”?


🧠 核心原因:ONWHERE在 SQL 执行流程中的作用完全不同

✅ SQL 的逻辑执行顺序(简化版):
  1. FROM+JOIN→ 先把两张表“拼”成一张大表
    • 这个阶段由ON条件决定哪些右表行能和左表匹配
  2. WHERE→ 对上一步生成的大表进行行级过滤
  3. GROUP BY,SELECT,ORDER BY...

🔑ON控制“怎么连”,WHERE控制“连完后留哪些”


🌰 举个具体例子

假设数据如下:

左表:企业表(oewrt)
idCOUNTY_IDENT_TYPECONFIRM_STATUS
1A12
2B12
右表:月报表(oemr)
RECORD_IDMONTH
12025-06
12025-05

企业 B 没有 2025-06 月报!


情况一:条件写在ON(✅ 正确保留左表)

SELECT * FROM oewrt LEFT JOIN oemr ON oewrt.id = oemr.RECORD_ID AND oemr.MONTH = '2025-06';

执行过程:

  1. 对左表每一行,找右表中同时满足id=RECORD_IDMONTH='2025-06'的行
  2. 结果:
    idCOUNTY_IDRECORD_IDMONTH
    1A12025-06
    2BNULLNULL

左表所有行都在,只是右表匹配不到就填NULL


情况二:条件写在WHERE(❌ 过滤掉左表)

SELECT * FROM oewrt LEFT JOIN oemr ON oewrt.id = oemr.RECORD_ID WHERE oemr.MONTH = '2025-06';

执行过程:

  1. 先做LEFT JOIN(不带 MONTH 条件):
    idCOUNTY_IDRECORD_IDMONTH
    1A12025-06
    1A12025-05
    2BNULLNULL
  2. 再执行WHERE oemr.MONTH = '2025-06'
    • 第一行:2025-06 = 2025-06→ ✅ 保留
    • 第二行:2025-05 = 2025-06→ ❌ 过滤
    • 第三行:NULL = 2025-06→ ❌UNKNOWN(不是 true),被过滤!

最终结果:

idCOUNTY_IDRECORD_IDMONTH
1A12025-06

企业 B 完全消失了!


📌 关键区别总结

条件在ON条件在WHERE
作用阶段连接时(决定右表哪些行能匹配)连接后(对整行做过滤)
对左表影响永不丢左表行(LEFT JOIN 语义)会丢左表行(如果右表为 NULL)
NULL 处理允许右表为 NULLNULL = 'xxx'→ false/unknown → 被过滤
适用场景需要保留左表所有记录(如你的需求)只要匹配成功的记录(相当于 INNER JOIN)

💡 一句话记住:

ON是“连接规则”,WHERE是“生存规则”。
左连接中,左表行天生“存活”;但如果WHERE判它“死刑”(比如要求右表字段等于某值,但它却是 NULL),它就会被干掉。

✅ 正确认知(精炼版):

当使用LEFT JOIN时:

  • 过滤右表的条件应写在ON子句中,以保留左表所有行;
  • COUNT(右表.某字段)(如主键)来统计右表实际匹配的记录数
  • 因为COUNT(字段)会自动忽略NULL值,所以未匹配的行不会被计入,结果就是“有效关联数”。

📌 举个标准模式(你的场景)

SELECT a.COUNTY_ID, COUNT(*) AS shouldReport, -- 左表总行数(含未匹配) COUNT(b.RECORD_ID) AS reported -- 右表非 NULL 行数(自动排除未匹配) FROM OIL_ENTER_WHSLEWHSE_RECORD_TAB a LEFT JOIN OIL_ENTER_MONTH_REPORT b ON a.id = b.RECORD_ID AND b.MONTH = '2025-06' -- ✅ 右表过滤条件放 ON WHERE a.ENT_TYPE IN ('1','3') AND a.CONFIRM_STATUS = '2' GROUP BY a.COUNTY_ID;

✅ 这样:

  • 每个有备案企业的区都会出现;
  • reported自动 = 填了 2025-06 月报的企业数(没填的就是 0);
  • 不需要WHERE b.MONTH = ...,否则会丢数据!

⚠️ 但要注意两个细节(避免误解)

1. ❌ 不是“把 null 去掉从而统计总数”,而是:
  • COUNT(*)→ 统计所有行(包括右表为 NULL 的)
  • COUNT(b.xxx)→ 统计b.xxx 非 NULL 的行

所以你不是“去掉 null 再统计总数”,而是分别统计“总行数”和“有效关联行数”

2. ✅ 字段选择很重要:
  • 推荐用COUNT(b.RECORD_ID)COUNT(b.id)(右表主键/非空字段)
  • 避免用COUNT(b.MONTH),万一MONTH本身允许为 NULL(即使有记录),也会被忽略

🔁 对比错误做法

-- ❌ 错误:条件放 WHERE LEFT JOIN b ON a.id = b.RECORD_ID WHERE b.MONTH = '2025-06'

→ 未填报的企业整行消失,COUNT(*)COUNT(b.xxx)都只算填报的,且无填报的区不出现

-- ❌ 错误:用 COUNT(*) COUNT(*) AS reported -- 即使没匹配,也会计 1!

→ 因为LEFT JOIN后每行都存在,COUNT(*)永远 = 左表行数。


✅ 总结口诀(背下来!)

左连要保左,右表条件放 ON;
统计匹配数,COUNT(右表字段);
若用 WHERE 滤,左表数据全丢光;
COUNT(*) 是总数,别拿它当已报量。


你现在完全掌握了这个关键技巧!👏
只要记住:ON控连接,WHERE控生死,COUNT(字段)忽略 NULL,就能写出正确的统计 SQL。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!