不能。Oracle禁止将虚拟列直接用作PARTITION BY分区键,必须先定义GENERATED ALWAYS AS虚拟列,再在PARTITION BY中显式引用该列名,且需确保类型兼容、表达式确定性、VALUES值与运行时输出完全一致。
虚拟列能直接用于 PARTITION BY 吗?
不能。oracle 12c 不允许在 partition by 子句中直接写表达式(比如 trunc(created_date) 或 mod(id, 4)),哪怕这个表达式和你定义的虚拟列完全一致。它只接受已命名的列名——而且该列必须是表定义中显式存在的、可被分区键引用的列。
所以,想用虚拟列做分区键,第一步就是:先建虚拟列,再在 PARTITION BY 中引用它的名字。
如何正确定义带虚拟列分区的表?
关键顺序不能错:虚拟列定义 → 分区子句引用该列名。注意以下几点:
- 虚拟列必须是
GENERATED ALWAYS AS类型,且表达式结果类型要能用于分区(如NUMBER、DATE、VARCHAR2,不支持CLOB或对象类型) - 分区键列不能是虚拟列的依赖列本身(比如用
id算出shard_id,就不能再用id做另一个分区键) - 表必须是 interval 或 range/list/hash 分区表,且虚拟列需参与分区键(不能只是普通虚拟列)
CREATE TABLE sales (
id NUMBER,
sale_date DATE,
amount NUMBER,
year_month VARCHAR2(6) GENERATED ALWAYS AS (TO_CHAR(sale_date, 'YYYYMM')) VIRTUAL
)
PARTITION BY LIST (year_month) (
PARTITION p_202301 VALUES ('202301'),
PARTITION p_202302 VALUES ('202302'),
PARTITION p_other VALUES (DEFAULT)
);
常见报错及原因
遇到这些错误,基本都是因为绕过了虚拟列显式命名这一步:
-
ORA-54013: INSERT operation disallowed on virtual columns:误把虚拟列当普通列插入(和分区无关,但常一起出现) -
ORA-14039: partitioning columns must form a subset of key columns:虚拟列没出现在PARTITION BY列表里,或类型不兼容(比如用TO_CHAR(..., 'YYYY-MM')得到带短横线的字符串,但分区值写了'202301') -
ORA-14037: partition bound value mismatch:分区VALUES中的字面量类型/格式和虚拟列实际生成值不一致(大小写、空格、时区隐式转换都可能引发)
列表检查项:
- 虚拟列表达式是否确定性(
SYSDATE、USER等非确定函数禁止使用) -
VALUES子句中的值是否与虚拟列运行时输出完全一致(建议用SELECT DISTINCT查一下实际值) - 如果用
HASH分区,虚拟列类型必须支持哈希(VARCHAR2可以,LONG不行)
为什么不用函数索引+普通分区替代?
可以,但代价不同:
- 函数索引无法作为分区键,只能加速查询;而虚拟列+分区能真正实现数据物理切分,提升归档、交换分区、并行扫描效率
- 虚拟列值在插入/更新时实时计算并用于分区路由,无需额外维护;函数索引则需要 DBA 预判所有可能的分区边界并手工管理
- 对于按时间范围 + 业务维度组合分区(如
TO<em>CHAR(dt,'YYYY') || '</em>' || dept_id),虚拟列让逻辑集中、可读性强;等效的函数分区语法 Oracle 根本不支持
真正麻烦的是边界维护——比如按年月分区时,得提前建好下个月的 PARTITION,或者配 INTERVAL,但虚拟列本身不解决自动扩分区问题,那得靠 ALTER TABLE ... SET INTERVAL 配合。
虚拟列分区不是“写个表达式就自动分”,而是把表达式固化为列名,再走标准分区流程。漏掉“显式命名”这步,后面全卡住。


















