UNION ALL 拼接多个 GROUP BY 查询前必须手动对齐各子查询的列数、顺序和数据类型,否则报错;需用 CAST 或 NULL::TYPE 显式补位,加标识字段区分来源,列名以首个子查询为准;CTE 不解决对齐问题;真合并需先 UNION ALL 再外层 GROUP BY。

不能直接把多个 GROUP BY 查询用 UNION ALL 拼起来就完事——列数、类型、顺序不一致会立刻报错,比如 ORA-01789 或 ERROR 1222。必须手动对齐结构,否则数据库根本不知道怎么拼。
UNION ALL 拼接前必须对齐字段数量和类型
每个子查询的 SELECT 列数必须完全相同,对应位置列的数据类型要兼容(比如都用 VARCHAR,别一个 INT 一个 DATE)。数据库不会自动帮你补空或转类型,尤其在 SQL Server 中,它可能按“最宽类型”推导目标列,导致 INT 被转成 DECIMAL(38,0),后续参与计算时溢出或截断。
- 用
NULL::TEXT或CAST(NULL AS VARCHAR)显式补位,别依赖隐式转换 - 加标识字段说明来源,比如
'by_dept'、'by_job',否则拼完根本分不清哪行属于哪个维度 - 所有子查询的列名以第一个为准,后面子查询的别名会被忽略——想统一字段名,只能在第一个里定义好
CTE 只是让结构更清晰,不解决对齐问题
用 WITH 把各分组逻辑拆成 CTE,比如 dept_stats、job_stats,确实方便复用和调试,但它不改变 UNION ALL 的底层规则:每个 CTE 的 SELECT 仍需人工保证列数、顺序、类型一一对应。
- CTE 内不能单独写
ORDER BY,除非配LIMIT或窗口函数,否则直接报错 - CTE 名称只能在紧随其后的主查询中引用一次,不能重复用
- 如果某个 CTE 里用了复杂
JOIN或过滤条件,把它提出来能避免重复写,但别误以为 CTE 能“自动统一输出结构”
GROUP BY 套在 UNION ALL 外面才是真合并
想把不同来源的数据统一按某字段汇总(比如合并两张费用表后按目的地算总费用),得先把数据用 UNION ALL 拼成一张临时结果集,再套一层 GROUP BY。这时候 UNION ALL 是“原料准备”,GROUP BY 才是“最终加工”。
- 写法是:
SELECT dest, SUM(cost) FROM (SELECT ... UNION ALL SELECT ...) t GROUP BY dest - 不能反过来:先各自
GROUP BY再拼,除非你明确要保留各维度的独立聚合行 - 如果源表有
NULL值(比如dept IS NULL),拼进去后可能和其它NULL行混在一起被当成同一类——业务上是否允许这样归并,得提前确认
最容易被忽略的是:UNION ALL 后的 ORDER BY 和 LIMIT 只能出现在整个语句末尾,不能分散在子查询里;而如果你需要“每组取 top 3”,UNION ALL 本身做不到,得切到 ROW_NUMBER() OVER (PARTITION BY ...) 上去解。

















