
UNION ALL 拼接多个 GROUP BY 查询时,列对齐必须严格一致
直接写 SELECT dept, COUNT(*) FROM emp GROUP BY dept 和 SELECT job, COUNT(*) FROM emp GROUP BY job 然后用 UNION ALL 连起来,会报错 ORA-01789 或 ERROR 1222:列数不匹配。这不是语法错误,而是 SQL 执行模型强制要求——UNION ALL 是对结果集做垂直拼接,不是“合并逻辑”。
必须手动对齐字段数量、顺序和类型:
- 所有子查询 SELECT 列数必须相同(哪怕某列是
NULL占位) - 对应位置的列数据类型要兼容(比如都用
VARCHAR,别一个INT一个DATE) - 建议显式命名列,避免因别名丢失导致后续引用混乱
示例:
SELECT 'dept' AS level, dept AS group_name, COUNT(*) AS cnt FROM emp GROUP BY dept UNION ALL SELECT 'job', job, COUNT(*) FROM emp GROUP BY job UNION ALL SELECT 'total', 'all', COUNT(*) FROM emp;
用 CTE 预先封装各分组逻辑,再 UNION ALL 拼接更清晰
把每个 GROUP BY 查询写进 CTE,能避免重复写表名、WHERE 条件或复杂 JOIN,也方便复用中间结果。但注意:CTE 本身不改变 UNION ALL 的列对齐规则,只是让结构更易读、调试更方便。
常见误用是以为 CTE 能“自动统一字段”,其实仍需人工对齐:
- 每个 CTE 的 SELECT 列数、顺序、类型仍需一一对应
- CTE 名称不能重复,且只能在紧随其后的主查询中引用一次(标准 SQL 中)
- 若某个 CTE 里用了
ORDER BY,必须配合LIMIT或窗口函数,否则报错(多数数据库不允许 CTE 内单独排序)
示例:
WITH dept_stats AS ( SELECT 'dept' AS level, dept AS group_name, COUNT(*) AS cnt FROM emp GROUP BY dept ), job_stats AS ( SELECT 'job', job, COUNT(*) FROM emp GROUP BY job ), total_stats AS ( SELECT 'total', 'all', COUNT(*) FROM emp ) SELECT * FROM dept_stats UNION ALL SELECT * FROM job_stats UNION ALL SELECT * FROM total_stats;
UNION ALL + CTE 组合下,NULL 值和空字符串容易引发统计偏差
当不同分组维度存在 NULL 值(比如 dept IS NULL),拼接后可能被误认为同一类汇总行;而用空字符串 '' 替代 NULL 又可能和真实业务值冲突(如部门名确实为 '')。
推荐做法是用明确标识符区分来源和空值含义:
- 用字符串字面量标注层级,如
'dept_null'、'job_unknown' - 避免在
GROUP BY字段上直接COALESCE(col, 'N/A'),这会影响索引使用和分组粒度 - 若需展示“未分类”行,应在原始数据层补全或用 LEFT JOIN 补缺,而非靠拼接后识别
例如:SELECT 'dept', COALESCE(dept, 'unknown') AS group_name, COUNT(*) FROM emp GROUP BY COALESCE(dept, 'unknown') —— 这样比拼接后处理更可控。
性能与可维护性:ROLLUP / GROUPING SETS 往往比 UNION ALL + CTE 更优
如果目标只是做多级汇总(如部门→岗位→总计),UNION ALL + CTE 看似灵活,实则重复扫描表多次,执行计划难优化。而 GROUP BY ROLLUP 或 GROUPING SETS 只需单次扫描,引擎可复用排序/聚合中间结果。
但要注意兼容性:
-
ROLLUP在 MySQL 8.0+、PostgreSQL 9.5+、SQL Server、Oracle 均支持;SQLite 不支持 -
GROUPING SETS在 PostgreSQL、SQL Server、Oracle 支持较好;MySQL 目前不支持(截至 2026 年 4 月) - 若必须用
UNION ALL,记得给每个子查询加合适的 WHERE 过滤(如时间范围),避免全表扫多遍
真正难处理的是“非层级交叉汇总”——比如既要按地区统计,又要按产品线统计,还要按客户等级统计,且三者无包含关系。这时 UNION ALL + CTE 是合理选择,但务必检查每条路径的执行代价。

















