UNION 要求列数和对应类型必须兼容
MySQL 的
UNION不是简单拼接两组结果,它强制要求左右两个
SELECT语句返回的列数完全相同,且对应位置的列在隐式转换下能兼容。比如
INT和
VARCHAR可能被转成字符串合并,但
JSON和
BLOB在某些版本会报错。 列名以第一个
SELECT为准,后续的列名会被忽略 不能对单个子查询加
ORDER BY,除非配合
LIMIT(否则语法报错) 想按整结果排序,
ORDER BY必须写在最后一个子句之后,且引用的是第一个
SELECT的字段别名或位置序号(如
ORDER BY 1)
UNION vs UNION ALL:去重开销很实在
默认的
UNION会自动去重,MySQL 内部要对合并后的临时结果做排序 + 去重操作,数据量大时明显拖慢;而
UNION ALL直接追加,零额外开销。如果你能确认两边结果天然无交集(比如查不同状态的订单、不同日期的分区表),就该用
UNION ALL。
UNION等价于
UNION DISTINCT,显式写出更易读 去重逻辑基于所有列的全值比较,不是主键或某几列 如果只想要某几列去重,得在外层套
SELECT DISTINCT,不能靠
UNION实现
子查询里不能直接用 LIMIT(除非配 ORDER BY)
这是新手高频报错点:
(SELECT * FROM t1 LIMIT 1) UNION (SELECT * FROM t2)会提示
This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'—— 实际上不是 LIMIT 本身的问题,而是 MySQL 限制了带
LIMIT的子查询出现在
UNION左右两侧(5.7+ 仍存在此限制)。 绕过方法:把带
LIMIT的查询包一层派生表,例如
(SELECT * FROM (SELECT * FROM t1 ORDER BY id LIMIT 1) AS tmp)注意:派生表必须有别名,否则语法错误 如果只是想取合并后的前 N 条,直接在
UNION整体后加
LIMIT即可
SELECT id, name FROM users WHERE status = 1 UNION ALL SELECT id, name FROM users WHERE status = 2 ORDER BY id DESC LIMIT 10;
NULL 和隐式类型转换容易引发意料外结果
当两个
SELECT中同一列一边是
INT、另一边是
VARCHAR,MySQL 会尝试转成一个公共类型(通常是字符串),这时数值
0和字符串
'0'会被认为相等,
UNION去重时可能意外合并;同理,
NULL和空字符串在某些字符集下也可能被当作相同值处理。 显式用
CAST()或
CONVERT()统一类型最稳妥 测试时用
UNION ALL先看原始数据,再切回
UNION观察去重效果 涉及时间字段时,注意
DATETIME和
TIMESTAMP在时区处理上的差异可能导致表面相同实则不等
实际用的时候,先跑通
UNION ALL,再决定是否需要去重;列对齐和类型一致性,比写法“漂亮”重要得多。
