两个sql,在某些情况下执行结果是一样的(not in如果子查询为null,可以直接报错)
---未上报的企业 select oewrt.id, oewrt.COUNTY_ID, oewrt.ENT_NAME, oewrt.LINKMAN, oewrt.PHONE from OIL_ENTER_WHSLEWHSE_RECORD_TAB oewrt where id not in (select oewrt.id from OIL_ENTER_MONTH_REPORT oemr left outer join OIL_ENTER_WHSLEWHSE_RECORD_TAB oewrt ON oewrt.id = oemr.RECORD_ID AND oemr.MONTH = :MONTH where oewrt.COUNTY_ID like substr(:countyId, 1, 2) || '%' and oewrt.ENT_TYPE IN (:ENT_TYPE, '3') AND oewrt.CONFIRM_STATUS = '2') and oewrt.COUNTY_ID like substr(:countyId, 1, 2) || '%' and oewrt.ENT_TYPE IN (:ENT_TYPE, '3') AND oewrt.CONFIRM_STATUS = '2';而not exists更加安全,为什么?
SELECT oewrt.id, oewrt.COUNTY_ID, oewrt.ENT_NAME, oewrt.LINKMAN, oewrt.PHONE FROM OIL_ENTER_WHSLEWHSE_RECORD_TAB oewrt WHERE NOT EXISTS ( SELECT 1 FROM OIL_ENTER_MONTH_REPORT oemr WHERE oemr.RECORD_ID = oewrt.id AND oemr.MONTH = :MONTH ) and oewrt.COUNTY_ID LIKE SUBSTR(:countyId, 1, 2) || '%' AND oewrt.ENT_TYPE IN (:ENT_TYPE, '3') AND oewrt.CONFIRM_STATUS = '2';❓ 你可能在想:
“如果子查询只写
SELECT 1,那数据库怎么知道该排除主表中的哪一行?它连要比较的字段都没返回啊!”
答案是:比较字段不在SELECT里,而在WHERE里!
✅ 核心原理:关联条件在WHERE,不在SELECT
看这个经典写法:
SELECT * FROM customers c WHERE NOT EXISTS ( SELECT 1 -- ← 这里只表示“有结果就行” FROM orders o WHERE o.customer_id = c.id -- ← 关键在这里!建立关联 AND o.month = '202506' );🔍 执行过程(对主表每一行):
- 拿到当前客户
c.id = 1001 - 执行子查询:
SELECT 1 FROM orders WHERE customer_id = 1001 AND month = '202506' - 数据库去
orders表里找:- 有没有一行满足
customer_id = 1001且month = '202506'?
- 有没有一行满足
- 如果有→ 子查询返回至少一行 →
NOT EXISTS = false→不选这个客户 - 如果没有→ 子查询返回空 →
NOT EXISTS = true→选中这个客户
🎯关键:关联字段
c.id和o.customer_id是通过WHERE o.customer_id = c.id建立的,和SELECT 1完全无关!
🧠 类比理解
想象你在查“没交作业的学生”:
- 主表:全班学生名单(张三、李四、王五)
- 子表:作业提交记录(张三、李四)
你不需要从作业记录里“返回学生名字”来对比,而是这样操作:
对每个学生,去作业记录里“按名字查找”:
- 能找到 → 交了 → 排除
- 找不到 → 没交 → 保留
这里的“按名字查找”就是WHERE o.customer_id = c.id,
而SELECT 1只是回答:“找到了吗?(是/否)”
❌ 为什么你会觉得“需要返回字段”?
可能是因为混淆了以下两种模式:
| 模式 | 写法 | 依赖返回值? |
|---|---|---|
| 集合排除 | WHERE id NOT IN (SELECT customer_id FROM ...) | ✅ 需要子查询返回具体值 |
| 存在性检查 | WHERE NOT EXISTS (SELECT 1 FROM ... WHERE 关联条件) | ❌ 不需要返回值,只看是否有行 |
NOT IN确实需要子查询返回一个值列表,然后主查询用这个列表做排除。NOT EXISTS完全不依赖返回值,它只关心子查询是否能“找到匹配行”。
📌关键词 / 线索区(Cue Column)
NOT EXISTSvsNOT IN- 存在性检查(Existence Check)
- 关联条件位置
- 为什么用
SELECT 1 - NULL 安全性
- 执行逻辑:逐行验证
- 性能与可读性
📖主笔记区(Notes)
1. 核心思想NOT EXISTS不是通过“返回字段值”来排除数据,而是对主表每一行,在子表中检查是否存在匹配记录。只要子查询返回 ≥1 行,EXISTS = true;否则为false。
2. 关联靠WHERE,不靠SELECT
- 关键关联写在子查询的
WHERE中,例如:WHERE oemr.RECORD_ID = oewrt.id AND oemr.MONTH = :MONTH SELECT 1只是一个占位符,表示“只关心是否有结果”,不参与逻辑判断。
3. 为什么用SELECT 1?
- ✅ 语义清晰:表明仅做存在性检查
- ✅ 性能略优:避免读取实际列数据(尤其在旧数据库)
- ✅ 行业惯例:被广泛接受的最佳实践
4.NOT EXISTSvsNOT IN
| 对比项 | NOT EXISTS | NOT IN |
|---|---|---|
| 工作方式 | 逐行验证是否存在 | 排除一个值列表 |
| NULL 安全 | ✅ 安全(不受影响) | ❌ 危险(含 NULL 则结果为空) |
| 性能 | 通常更快(可短路) | 可能全表扫描 |
| 可读性 | 逻辑清晰 | 易误解 |
5. 执行过程(伪代码)
for each row in 主表: if 子查询(带当前行的关联值) 返回 ≥1 行: 跳过(不满足 NOT EXISTS) else: 保留该行6. 典型应用场景
- 查询“未下单的客户”
- 查找“未提交月报的企业”
- 检测“缺少某类记录的数据”
🔚总结区(Summary)
NOT EXISTS是 SQL 中进行“不存在”判断的安全、高效、清晰的方式。它通过在子查询的WHERE子句中建立与主表的关联,逐行验证是否存在匹配记录,而SELECT 1仅作为存在性信号,不参与实际筛选。相比NOT IN,它天然规避了 NULL 带来的逻辑陷阱,是生产环境中推荐的标准写法。
💡口诀:
“关联写在 WHERE 里,SELECT 1 只问有没有;
逐行验证最可靠,NULL 再也不犯愁。”
✅建议行动:
- 在今后写“未上报”“未完成”“无记录”类查询时,优先使用
NOT EXISTS + SELECT 1 - 避免在可能含 NULL 的场景使用
NOT IN - 为子表的关联字段(如
RECORD_ID, MONTH)建立复合索引以提升性能