Oracle数据库支持对多个查询结果执行集合级别的运算,这些运算来自关系代数中的并、交、差概念。Oracle实现了UNION、UNION ALL、INTERSECT和MINUS四种操作符,它们能够把两个或多个SELECT语句返回的结果集当作集合来处理。理解这些操作符的适用范围和限制条件,对于编写复杂报表、数据校验、数据迁移等场景非常有帮助。

集合操作符不同于连接查询,连接是把不同表的列横向合并,而集合操作是把多个查询的结果行纵向堆叠。因此集合操作对参与运算的查询有明确的格式要求,例如列数量必须一致、对应列的数据类型必须兼容。此外,集合操作默认会进行去重处理,除非使用UNION ALL显式保留重复行。
集合操作的基本规则
无论使用UNION、INTERSECT还是MINUS,参与运算的各个查询都必须返回相同数量的列。Oracle在解析SQL时会按照列的位置一一对应,而不是按照列名对应。也就是说,第一个查询的第一列会与第二个查询的第一列进行匹配,第二列与第二列匹配,依此类推。如果列数量不一致,Oracle会直接报错。
对应列的数据类型必须属于同一数据类型族,或者能够隐式转换。例如NUMBER列和VARCHAR2列虽然可以隐式转换,但可能会带来性能损耗和不可预期的结果。最稳妥的做法是显式使用CAST或TO_CHAR、TO_NUMBER等函数统一数据类型。集合操作结果的列名和数据类型以第一个查询为准,后续查询的列名不会影响最终结果集的列标题。
还有一个常被忽略的细节是,ORDER BY只能出现在整个集合操作语句的末尾,用来对最终合并后的结果排序。不能在参与集合操作的某个单独SELECT语句内部使用ORDER BY。如果需要先排序再合并,必须借助子查询或者使用括号调整逻辑。
UNION与UNION ALL:并集操作
UNION操作符用于把两个查询结果合并成一个结果集,并自动去除重复行。例如有两个员工表emp_a和emp_b,分别记录了不同来源的员工数据,现在希望得到一份不重复的员工清单,就可以使用UNION。
CREATE TABLE emp_a ( emp_id NUMBER, emp_name VARCHAR2(50) ); CREATE TABLE emp_b ( emp_id NUMBER, emp_name VARCHAR2(50) ); INSERT INTO emp_a VALUES (1, 'Alice'); INSERT INTO emp_a VALUES (2, 'Bob'); INSERT INTO emp_a VALUES (3, 'Cindy'); INSERT INTO emp_b VALUES (2, 'Bob'); INSERT INTO emp_b VALUES (4, 'David'); INSERT INTO emp_b VALUES (5, 'Eva'); COMMIT;
执行下面的UNION查询,结果会返回五个员工,其中emp_id为2的Bob在两个表中都存在,但只出现一次。
SELECT emp_id, emp_name FROM emp_a UNION SELECT emp_id, emp_name FROM emp_b;
UNION在去重时需要比较所有列的值,因此如果结果集较大,Oracle可能会采用排序或者哈希的方式消除重复行,这会产生额外的内存和CPU开销。如果业务逻辑明确允许重复数据存在,或者源数据本身不可能出现重复,那么应该使用UNION ALL来提升性能。
UNION ALL和UNION的语法完全一样,只是保留所有行,不做去重处理。下面这条语句会返回六行,Bob会出现两次。由于不涉及去重,UNION ALL的执行效率通常高于UNION,尤其是在大数据量场景下,这种差异会非常明显。
SELECT emp_id, emp_name FROM emp_a UNION ALL SELECT emp_id, emp_name FROM emp_b;
因此,开发者在编写SQL时应该养成一个习惯:先确认业务上是否允许重复行,如果允许,就优先使用UNION ALL。只有在需要严格去重时才使用UNION。这个选择对执行计划的影响往往比想象中大得多。
INTERSECT:交集操作
INTERSECT操作符返回两个查询结果中共同存在的行。它相当于数学集合中的交集运算,要求两个结果集中完全相同的行才会被保留下来。继续使用前面的emp_a和emp_b表,如果想找出同时出现在两个表中的员工,可以执行下面的语句。
SELECT emp_id, emp_name FROM emp_a INTERSECT SELECT emp_id, emp_name FROM emp_b;
这条语句会返回emp_id为2的Bob这一行,因为只有这一行的所有列值在两个表中完全一致。如果两个表中存在某行部分列相同、部分列不同,INTERSECT不会将其视为交集。比如emp_a中有编号2的Bob,emp_b中有编号2但姓名不同的人,那么这两行不会相等,也就不会出现在交集结果中。
关于NULL值,INTERSECT有一个容易误解的地方。在SQL中,普通比较运算里NULL与任何值比较的结果都是未知,但集合操作在判断行相等时,NULL被视为相等的值。也就是说,如果两个结果集都有某个列为NULL,且其他列值相同,这两行会被认为是重复行而被INTERSECT保留。UNION去重时也遵循同样的规则。
INTERSECT可以理解为先对两个结果集分别去重,再找出共同的行。Oracle在执行INTERSECT时通常会选择排序合并或哈希半连接等策略。如果参与运算的表有合适的索引,优化器可能会利用索引来减少数据访问量。
MINUS:差集操作
MINUS操作符返回第一个查询结果中存在但第二个查询结果中不存在的行。它是有方向性的,第一个查询称为被减集合,第二个查询称为减数集合。下面的语句返回只存在于emp_a但不存在于emp_b的员工。
SELECT emp_id, emp_name FROM emp_a MINUS SELECT emp_id, emp_name FROM emp_b;
结果会包含emp_id为1的Alice和emp_id为3的Cindy,因为这两个员工没有出现在emp_b中。emp_id为2的Bob在两个表中都存在,所以不会出现在差集结果里。如果交换两个查询的顺序,结果会完全不同。
SELECT emp_id, emp_name FROM emp_b MINUS SELECT emp_id, emp_name FROM emp_a;
这条语句返回David和Eva,即只存在于emp_b而不存在于emp_a的行。MINUS的这种方向性在实际业务中非常适用,例如找出已经删除的记录、对比两张配置表的差异、检查数据同步是否完整等。
MINUS同样会先去重再计算差集,NULL值在判断行相等时被当作相等值处理。因此如果某行在第一个结果集中出现了两次,在第二个结果集中出现了一次,MINUS的结果中将不会包含该行。如果业务要求保留第一次查询中的多次出现,需要先使用UNION ALL或者借助分析函数生成唯一标识,再进行比较。
组合使用、优先级与性能
集合操作符可以组合使用,形成更复杂的结果集运算。当一条SQL中包含多个集合操作符时,Oracle按照一定的优先级规则进行解析。默认情况下,INTERSECT的优先级高于UNION和MINUS,而UNION和MINUS处于同一优先级,按照从左到右的顺序执行。这样的规则与数学中先乘除后加减的习惯类似,但容易让不熟悉的人产生误解。
为了避免歧义,应该尽量使用括号显式指定运算顺序。括号内的集合操作会先执行,再与括号外的查询进行集合运算。例如下面这条语句先计算emp_a和emp_b的并集,再从并集中减去emp_a和emp_b的交集,最终得到两个表的对称差集。
(SELECT emp_id, emp_name FROM emp_a UNION SELECT emp_id, emp_name FROM emp_b) MINUS (SELECT emp_id, emp_name FROM emp_a INTERSECT SELECT emp_id, emp_name FROM emp_b);
这段代码的结果是Alice、Cindy、David和Eva,也就是只属于其中一张表而不属于两张表共有的部分。使用括号后,逻辑非常清晰,也避免了因为优先级不明确导致的SQL错误。
从性能角度看,集合操作通常都会涉及去重和排序,开销大于普通的单表查询。UNION ALL是最轻量的,因为它不进行去重。UNION、INTERSECT、MINUS则都可能触发排序或哈希操作,具体执行计划取决于数据量、索引情况以及优化器的选择。如果集合操作出现在大表之间,建议先通过WHERE条件减少参与运算的数据量,或者使用物化视图预先计算中间结果。
另外,集合操作中的列匹配只依据位置,因此如果后续业务调整了查询的列顺序,即使列名没有变化,结果也可能完全错误。对于长期维护的SQL,建议在注释中明确说明每个查询列的含义,必要时使用相同的列别名,方便后续阅读和修改。
Oracle集合操作UNIONINTERSECT修改时间:2026-08-22 08:03:17