<p>UNION等集合运算符不能直接用于WHERE或IN子句,因其非单值表达式;必须包裹为带别名的派生表(如SELECT * FROM (SELECT a FROM t1 UNION SELECT b FROM t2) AS t),且列数、类型须严格对齐。</p>
嵌套查询里不能直接用 UNION 或 INTERSECT 作为子查询的主体,但可以把它们放在 FROM 子句中当派生表用——这是最稳妥、兼容性最好的写法。
因为 UNION、INTERSECT、EXCEPT 是集合运算符,不是表达式,不能返回单值。像 WHERE id IN (SELECT a FROM t1 UNION SELECT b FROM t2) 这种写法在多数数据库(如 PostgreSQL、SQL Server)会报错,提示“subquery must return only one column”或“syntax near UNION”。MySQL 8.0+ 虽支持,但语义容易误读:它其实把整个 UNION 当作一个结果集,而 IN 只能接受单列子查询。
常见错误现象:
ERROR: syntax error at or near "UNION"(PostgreSQL)Incorrect syntax near the keyword 'UNION'(SQL Server,尤其在子查询未加括号或缺少别名时)必须把集合运算的结果当作一个临时表(派生表),再参与外层查询。关键点是:加括号、显式 AS 别名、列名对齐。
例如,要查“既是销售员又是技术支持,且薪资 > 8000 的员工”:
SELECT e.name, e.salaryFROM employees eINNER JOIN ( SELECT emp_id FROM sales_team INTERSECT SELECT emp_id FROM support_team) AS qualified ON e.emp_id = qualified.emp_idWHERE e.salary > 8000;
要点:
INTERSECT 和 UNION 都必须包裹在 (...) 中,否则解析失败AS qualified 不可省略(MySQL 5.7+ 允许省略,但 SQL Server / PostgreSQL 强制要求)SELECT 的列数、类型、顺序必须一致;若原表字段名不同,可在子查询中统一用别名,如 SELECT id AS emp_id FROM t1
ORDER BY —— 派生表本身无序,排序会被忽略,除非配合 LIMIT 或窗口函数当你要“合并多个来源的候选 ID,再统一过滤”时,UNION ALL 比 UNION 更高效,也更可控。
比如:从三个历史分区表中找出所有活跃用户 ID,再查其最新登录时间:
SELECT u.id, MAX(l.login_time) AS last_loginFROM ( SELECT user_id AS id FROM users_2024_q1 UNION ALL SELECT user_id AS id FROM users_2024_q2 UNION ALL SELECT user_id AS id FROM users_2024_q3) AS uJOIN login_logs l ON u.id = l.user_idGROUP BY u.id;
注意:
UNION ALL 是因为不需要去重(ID 本就唯一),避免额外的哈希去重开销UNION,但性能下降明显UNION,此时只能改用 OR 或临时表Oracle 用 MINUS,老版本 MySQL 不支持 INTERSECT,PostgreSQL 支持但语法严格。遇到不支持时,用 EXISTS 嵌套更通用:
-- 想实现:SELECT id FROM t1 INTERSECT SELECT id FROM t2SELECT t1.idFROM t1WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.id = t1.id);
这个写法在所有主流数据库都有效,且执行计划通常和 INTERSECT 接近。但要注意:
EXISTS 不自动去重,如果 t1 有重复 id,结果也会重复;需加 DISTINCT 显式控制t2.id 有 NULL,EXISTS 仍可能返回意外结果(NULL 不等于任何值),而 INTERSECT 会按标准 SQL 处理 NULL 相等性EXISTS 层级变深,可读性下降,不如直接升级数据库或用 CTE 拆解真正容易被忽略的是列定义一致性——哪怕只是 INT 和 BIGINT 的隐式转换,在某些数据库(如 SQL Server)中会导致 UNION 报错,而不是静默转换。动手前先 DESCRIBE 或 SELECT TOP 1 看两表字段类型,比调半天错误更有用。