非关联去重是在子查询内用distinct或group by预先去重,不依赖外层表字段关联;推荐直接用select distinct子查询,注意列别名、索引和null安全(优先not exists)。

什么是SQL子查询非关联去重
在Oracle 21c中,用子查询实现“非关联去重”,本质是借助SELECT DISTINCT或GROUP BY在子查询内部先完成去重,再把结果作为数据源参与外层逻辑——它不依赖WHERE子句里与外层表的字段比较(即无correlated条件),所以执行计划通常更可控、性能更可预期。
直接用DISTINCT子查询最稳妥
多数场景下,你真正需要的只是“从某张表里拿去重后的值,再和其他表JOIN或做计算”,这时直接写SELECT DISTINCT子查询最清晰,也避免隐式重复:
SELECT t1.name, t2.unique_code FROM employees t1 JOIN (SELECT DISTINCT dept_code AS unique_code FROM departments) t2 ON t1.dept_id = t2.unique_code;
-
DISTINCT在子查询内执行,Oracle 21c会自动优化为HASH UNIQUE或SORT UNIQUE,无需手动干预 - 别给子查询加别名时漏掉列别名(如
dept_code AS unique_code),否则外层ON或SELECT会报ORA-00904: invalid identifier - 如果子查询涉及大表且只取少量字段,记得确认该字段上有索引——不是为了加速
DISTINCT本身,而是避免全表扫描拖慢整个子查询启动时间
用ROW_NUMBER() + WHERE过滤要小心执行顺序
有人习惯用ROW_NUMBER()伪去重,比如“每组取一条”,这在非关联场景下可行,但必须确保WHERE rn = 1写在外层,否则Oracle可能无法下推过滤,导致子查询物化全部数据:
SELECT emp_id, dept_id
FROM (
SELECT emp_id, dept_id,
ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY hire_date DESC) rn
FROM employees
)
WHERE rn = 1;
- 子查询必须带括号包裹,且
WHERE rn = 1一定在最外层——写在子查询内部(如SELECT ... FROM (SELECT ..., rn FROM ...) WHERE rn = 1)语法合法,但21c优化器未必能识别为可下推谓词 -
PARTITION BY字段类型要和外层JOIN字段一致,比如dept_id是NUMBER,就别在子查询里TO_CHAR(dept_id)后再PARTITION BY,否则去重逻辑失效 - 这种写法比
DISTINCT开销大,仅当需要“保留某条代表记录”(而非任意一条)时才用
NOT EXISTS替代IN去重时注意NULL陷阱
如果你本意是“找出在A表存在、但在B表去重后不存在的记录”,用NOT EXISTS比NOT IN安全得多——因为NOT IN (subquery)遇到子查询返回NULL会整个判定为UNKNOWN,结果为空:
SELECT id FROM orders o
WHERE NOT EXISTS (
SELECT 1 FROM (
SELECT DISTINCT customer_id FROM customers
) c WHERE c.customer_id = o.customer_id
);
- 子查询用
SELECT 1即可,Oracle不关心具体查什么,只判断是否存在 - 即使
customers.customer_id允许NULL,NOT EXISTS也不受影响;而NOT IN (SELECT DISTINCT customer_id FROM customers)只要有一个NULL,整条WHERE就失效 - 这种写法本质不是“去重”,而是利用去重后的结果集做存在性检查——重点在子查询输出是否干净,而不是怎么去重
非关联去重真正难的不是写法,而是判断“去重动作该放在哪一层”:是在子查询里做,还是留到外层聚合?Oracle 21c的统计信息和自适应执行计划会让两者性能差异变小,但语义清晰性不会变——子查询去重,意味着你明确不需要原始粒度的数据。











