立创EDA半孔/阻焊开窗/3D模型三要素实战指南
2026/9/29 16:49:56
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上就“过滤掉左表行”?
ON和WHERE在 SQL 执行流程中的作用完全不同FROM+JOIN→ 先把两张表“拼”成一张大表ON条件决定哪些右表行能和左表匹配WHERE→ 对上一步生成的大表进行行级过滤GROUP BY,SELECT,ORDER BY...🔑
ON控制“怎么连”,WHERE控制“连完后留哪些”
假设数据如下:
| id | COUNTY_ID | ENT_TYPE | CONFIRM_STATUS |
|---|---|---|---|
| 1 | A | 1 | 2 |
| 2 | B | 1 | 2 |
| RECORD_ID | MONTH |
|---|---|
| 1 | 2025-06 |
| 1 | 2025-05 |
企业 B 没有 2025-06 月报!
ON(✅ 正确保留左表)SELECT * FROM oewrt LEFT JOIN oemr ON oewrt.id = oemr.RECORD_ID AND oemr.MONTH = '2025-06';id=RECORD_ID且MONTH='2025-06'的行| id | COUNTY_ID | RECORD_ID | MONTH |
|---|---|---|---|
| 1 | A | 1 | 2025-06 |
| 2 | B | NULL | NULL |
✅左表所有行都在,只是右表匹配不到就填NULL
WHERE(❌ 过滤掉左表)SELECT * FROM oewrt LEFT JOIN oemr ON oewrt.id = oemr.RECORD_ID WHERE oemr.MONTH = '2025-06';LEFT JOIN(不带 MONTH 条件):| id | COUNTY_ID | RECORD_ID | MONTH |
|---|---|---|---|
| 1 | A | 1 | 2025-06 |
| 1 | A | 1 | 2025-05 |
| 2 | B | NULL | NULL |
WHERE oemr.MONTH = '2025-06':2025-06 = 2025-06→ ✅ 保留2025-05 = 2025-06→ ❌ 过滤NULL = 2025-06→ ❌UNKNOWN(不是 true),被过滤!| id | COUNTY_ID | RECORD_ID | MONTH |
|---|---|---|---|
| 1 | A | 1 | 2025-06 |
❌企业 B 完全消失了!
条件在ON | 条件在WHERE | |
|---|---|---|
| 作用阶段 | 连接时(决定右表哪些行能匹配) | 连接后(对整行做过滤) |
| 对左表影响 | 永不丢左表行(LEFT JOIN 语义) | 会丢左表行(如果右表为 NULL) |
| NULL 处理 | 允许右表为 NULL | NULL = '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 = ...,否则会丢数据!COUNT(*)→ 统计所有行(包括右表为 NULL 的)COUNT(b.xxx)→ 统计b.xxx 非 NULL 的行所以你不是“去掉 null 再统计总数”,而是分别统计“总行数”和“有效关联行数”。
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。