ora-14097源于oracle严格比对sys.col$中字段元数据(default$、intcol#、property等),ctas建表必然失败;须用dbms_metadata.get_ddl复刻源表结构,并校验约束、隐藏列、默认值等完全一致。

ORA-14097 不是“类型写错了”,而是 Oracle 在 sys.col$ 字典里逐字节比对字段元数据时发现不一致——哪怕只差一个 DEFAULT SYSDATE 和 DEFAULT NULL,或者 column_id 顺序错一位,都会直接拒绝交换。
查字段定义差异不能只看 DDL 或 user_tab_columns
用 DBMS_METADATA.GET_DDL 或 DESC 看起来一模一样,不代表能交换。Oracle 校验的是 sys.col$ 中的原始记录:包括 default$(默认值内部表达)、property(是否隐藏列)、intcol#(内部列序)、notnull$(NOT NULL 约束状态)等。
必须用字典视图比对:
SELECT column_name, column_id, data_type, data_length, data_precision, nullable, default_length FROM user_tab_columns WHERE table_name IN ('SRC_TAB', 'EXCH_TAB') ORDER BY table_name, column_idSELECT column_name, hidden_column, virtual_column, unused_col FROM user_tab_cols WHERE table_name IN ('SRC_TAB', 'EXCH_TAB')SELECT constraint_name, constraint_type, search_condition, status FROM user_constraints WHERE table_name IN ('SRC_TAB', 'EXCH_TAB') AND constraint_type IN ('P', 'C')
CTAS 创建的交换表几乎必然触发 ORA-14097
CREATE TABLE exch_tab AS SELECT * FROM src_tab 会丢失:column_id 物理顺序、NOT NULL 约束、隐藏列、未使用列、所有 DEFAULT 值、主键/唯一约束。即使你手动加了主键,column_id 和 default$ 还是和源表对不上。
正确做法是用 DBMS_METADATA.GET_DDL 拿源表 DDL,然后手工修改表名、去掉分区子句、补全约束,再执行建表;或用 ALTER TABLE ... ADD COLUMN 逐步补齐缺失元数据,而不是重建。
- 补隐藏列:
ALTER TABLE exch_tab ADD (col_x VARCHAR2(10) INVISIBLE) - 补未使用列:
ALTER TABLE exch_tab DROP UNUSED COLUMNS(先清理,再确保两边都无) - 补默认值:
ALTER TABLE exch_tab MODIFY (created_date DEFAULT SYSDATE) - 补 NOT NULL:
ALTER TABLE exch_tab MODIFY (id NOT NULL)
主键、索引、约束状态必须严格对齐
如果源分区表有主键,交换表必须有同名、同字段、同状态(ENABLED VALIDATED)的主键;若源表主键是全局索引,交换表不需要索引,但主键约束本身必须存在且有效。
常见陷阱:
- 约束状态为
NOT VALIDATED—— 查user_constraints.status,用ALTER TABLE ... ENABLE VALIDATE CONSTRAINT修复 - 本地索引导致 ORA-14098 —— 交换时不带
INCLUDING INDEXES,且交换表不能建本地索引(除非源分区表对应分区也有) - 函数索引或虚拟列未对齐 ——
user_tab_cols.virtual_column = 'YES'必须两边一致
验证前先停掉任何自动统计信息收集
交换前如果 DBMS_STATS.LOCK_TABLE_STATS 被调用过,或自动任务刚跑完,可能临时改写统计信息指针,干扰元数据一致性判断。建议在维护窗口内操作,并确认:
-
SELECT stattype_locked FROM user_tab_statistics WHERE table_name = 'SRC_TAB'返回空 -
SELECT last_analyzed FROM user_tables WHERE table_name IN ('SRC_TAB', 'EXCH_TAB')时间接近且非空
最易被忽略的一点:sys.col$.intcol# 和 column_id 不是一回事,前者是 Oracle 内部存储顺序,无法直接修改,只能靠重建表或从源 DDL 严格复刻来保证——这意味着“看着一样”不等于“能交换”。











