最稳妥方式是直接用 union all 合并各年份物理表,要求字段名、顺序、类型严格一致,需显式写列名并补 null 占位,避免 order by,注意条件下推能力差异及新增表须手动更新视图定义。

视图里用 UNION ALL 拼接不同年份的表最稳妥
直接用 UNION ALL 合并多个年份的物理表(比如 sales_2022、sales_2023、sales_2024)是跨年拼接最可控的方式。它不查重、不排序、性能好,且各表结构必须严格一致——字段名、顺序、类型都得对得上,否则会报错 ERROR: each UNION query must have the same number of columns 或类型不匹配。
实操建议:
- 所有参与
UNION ALL的表,提前用SELECT检查字段数量和类型,别依赖“看起来一样” - 显式写出列名,避免用
*:写成SELECT order_id, amount, order_date FROM sales_2022 UNION ALL SELECT order_id, amount, order_date FROM sales_2023 - 如果某年表缺字段(比如
sales_2021没有tax_rate),用NULL::DECIMAL或CAST(NULL AS DECIMAL)补位,否则类型推导失败 - 别在视图里加
ORDER BY——SQL 标准不允许视图含排序,执行时再加
WHERE 条件下推能避免全表扫描
视图本身不存储数据,只是保存查询逻辑。如果你在查询视图时加了 WHERE order_date >= '2023-01-01',数据库能否跳过 sales_2022 表?取决于优化器是否支持“分区裁剪”或“条件下推”。PostgreSQL 12+ 对 UNION ALL 视图有一定下推能力,但 MySQL 8.0 默认不支持,会扫所有子表。
实操建议:
- 在 PostgreSQL 中,给每个年份表的日期字段建索引,并确保
WHERE条件能命中索引(如用BETWEEN而非EXTRACT(YEAR FROM order_date) = 2023) - MySQL 用户更推荐用原生分区表(
PARTITION BY RANGE (YEAR(order_date))),而不是手写UNION ALL视图,否则无法规避无效扫描 - 测试执行计划:对视图执行
EXPLAIN,确认是否出现Seq Scan on sales_2022—— 如果有,说明那张表被白扫了
视图字段别名要统一,否则应用层会崩
跨年表字段名不一致很常见:sales_2022 用 amt,sales_2023 改成 amount。视图定义里没显式别名的话,最终视图字段名取第一个子查询的列名,后面列即使同义也会被强制覆盖,导致下游代码读 amount 字段时报错 column "amount" does not exist。
实操建议:
- 所有
SELECT子句中,用AS显式声明别名:例如amt AS amount、total_price AS amount - 用
\d view_name(PostgreSQL)或SHOW COLUMNS FROM view_name(MySQL)验证最终字段名是否符合预期 - 如果某年表多出调试字段(如
etl_batch_id),而其他年份没有,要么补NULL AS etl_batch_id,要么在视图外层用SELECT显式过滤掉,别留歧义字段
新增年份表后必须手动刷新视图定义
视图不会自动感知新表。今年新增了 sales_2025,但视图还是只 UNION ALL 到 2024,那 2025 年数据就永远进不来。有些团队误以为改底层表结构就能联动,其实不行。
实操建议:
- 把视图 DDL 写进版本控制(如
views/sales_by_year.sql),每次新增年份表,同步修改并重新CREATE OR REPLACE VIEW - 用脚本自动生成视图定义:比如用
psql -c "SELECT table_name FROM information_schema.tables WHERE table_name LIKE 'sales_%'"拼出完整UNION ALL语句 - 别依赖物化视图自动刷新——PostgreSQL 物化视图需手动
REFRESH,MySQL 不原生支持,且物化视图不解决“新增表”的发现逻辑










