docs(board4分表): 更新数据库排查与重建说明文档

master
zangch@mesnac.com 3 weeks ago
parent d22e7ca0ee
commit c3fedce7d5

@ -1,358 +1,159 @@
# Board4 数据库排查与自动化重建说明
# Board4 数据库排查与重建说明
## 1. 文档目的
本文档用于沉淀本次 `Board4` 产量统计方案在数据库侧的完整排查过程、最终状态和自动化 SQL 备份,避免未来数据库被重置后无法快速恢复
本文档用于沉淀本次 `Board4` 产量统计改造在数据库侧的完整排查过程、最终数据库对象状态、重建顺序和 SQL 备份
本文档覆盖
重点说明
1. 本次数据库对象排查过程
2. 当前数据库对象最终状态
3. 自动化部署链路说明
4. 数据库重建顺序
5. 所有关键 SQL 原文备份
1. 数据库负责:
- `RT_DAILY_PROD_STATE`
- `DEVICE_DAILY_PRODUCTION`
- 当前月分表触发器
- `PKG_DEVICE_PROD_CALC`
- `JOB_FLUSH_RT_TO_DAY`
2. **月分表新建职责不再由数据库自动化完成**
3. **新建月分表由 C# 采集程序负责实现**
---
## 2. 背景与目标
## 2. 当前最终职责边界
本次数据库侧改造围绕以下目标展开:
### 2.1 数据库负责的内容
1. 为 `Board4.dayTotal`、`Board4.monthTotal`、`deviceProductionList` 引入实时表 `RT_DAILY_PROD_STATE`
2. 为月累计提供权威日汇总表 `DEVICE_DAILY_PRODUCTION`
3. 让自动采集设备写入月分表 `BASE_DEVICE_PARAM_VAL_YYYYMM`
4. 让自动采集分表通过触发器自动更新 `RT_DAILY_PROD_STATE`
5. 让数据库夜间自动执行 `RT -> DAY` 搬迁
6. 让数据库在月底自动创建下月分表和下月触发器
1. 保存实时产量状态:`RT_DAILY_PROD_STATE`
2. 保存日产量汇总:`DEVICE_DAILY_PRODUCTION`
3. 通过当前月分表触发器把自动采集明细同步到 RT
4. 通过 `PKG_DEVICE_PROD_CALC` 把昨日 RT 搬迁到 DAY
5. 通过 `JOB_FLUSH_RT_TO_DAY` 每天 00:10 自动执行 RT -> DAY
当前分表规则已统一为:
### 2.2 C# 采集程序负责的内容
```text
BASE_DEVICE_PARAM_VAL_YYYYMM
```
1. 创建当前月/后续月份的采集分表:
- `BASE_DEVICE_PARAM_VAL_202603`
- `BASE_DEVICE_PARAM_VAL_202604`
- ...
2. 保证采集入库时目标月分表已经存在
3. 新建月分表对应触发器的部署流程由业务发布过程配套完成
例如:
### 2.3 Java 负责的内容
```text
BASE_DEVICE_PARAM_VAL_202603
BASE_DEVICE_PARAM_VAL_202604
```
1. OLD 设备写 `BASE_DEVICE_PARAM_VAL`
2. OLD 设备在写入成功后,由 Java Service 同步更新 `RT_DAILY_PROD_STATE`
---
## 3. 本次排查过程
## 3. 数据库排查过程总结
### 3.1 第一轮检
### 3.1 初始排
开始执行了基于 `USER_TABLES / USER_OBJECTS / USER_SCHEDULER_JOBS` 的检查 SQL结果全部为空。
初通过 `USER_TABLES / USER_OBJECTS / USER_SCHEDULER_JOBS` 查询关键对象时结果为空。
最初判断有两种可能
后续确认
1. 当前登录用户不是实际业务 schema
2. 业务 schema 尚未部署任何对象
1. 实际业务 schema`HAIWEI`
2. 需要通过 `ALL_TABLES / ALL_OBJECTS / ALL_TRIGGERS / ALL_SCHEDULER_JOBS` 查询全局对象
后续用户确认:
### 3.2 关键对象定位结果
```text
BASE_DEVICE_PARAM_VAL_202603 已经建了
```
确认在 `HAIWEI` schema 下已存在:
这说明:
1. 之前查询使用的视角不对
2. 需要改用 `ALL_TABLES / ALL_OBJECTS / ALL_SCHEDULER_JOBS` 继续排查
---
### 3.2 第二轮检查:确认实际对象所在 schema
通过 `ALL_TABLES / ALL_OBJECTS / ALL_TRIGGERS / ALL_SCHEDULER_JOBS` 排查后,确认对象位于:
```text
HAIWEI
```
确认已存在对象:
1. `HAIWEI.BASE_DEVICE_PARAM_VAL_202603`
2. `HAIWEI.RT_DAILY_PROD_STATE`
3. `HAIWEI.DEVICE_DAILY_PRODUCTION`
4. `HAIWEI.PKG_DEVICE_PROD_CALC`
5. `HAIWEI.PKG_DEVICE_PROD_CALC BODY`
当时仍缺失:
1. `TRG_BDPV_202603_RT`
2. `JOB_FLUSH_RT_TO_DAY`
3. `PKG_DEVICE_PARAM_PARTITION`
4. `JOB_ENSURE_DEVICE_PARAM_PARTITION`
---
### 3.3 当前月触发器与调度任务部署
后续补部署后,再次查询,确认以下对象已存在:
1. `HAIWEI.TRG_BDPV_202603_RT`
2. `HAIWEI.JOB_FLUSH_RT_TO_DAY`
3. `HAIWEI.JOB_ENSURE_DEVICE_PARAM_PARTITION`
当时 `JOB` 状态为:
```text
JOB_ENSURE_DEVICE_PARAM_PARTITION TRUE SCHEDULED RUN_COUNT=0 FAILURE_COUNT=0
JOB_FLUSH_RT_TO_DAY TRUE SCHEDULED RUN_COUNT=0 FAILURE_COUNT=0
```
这说明:
1. 任务已经创建并启用
2. 但尚未到首次实际执行时间
3. 仍需继续验证“自动建下月分表+触发器”能力
---
### 3.4 手工验证自动建下月分表能力时出现 ORA-01031
执行:
```sql
BEGIN
HAIWEI.PKG_DEVICE_PARAM_PARTITION.ENSURE_MONTH_TABLE_AND_TRIGGER('202604', '202603');
END;
/
```
返回:
```text
ORA-01031: 权限不足
ORA-06512: 在 "HAIWEI.PKG_DEVICE_PARAM_PARTITION", line 52
ORA-06512: 在 "HAIWEI.PKG_DEVICE_PARAM_PARTITION", line 164
```
问题定位:
1. 不是 SQL 逻辑问题
2. 是 Oracle 包内执行 DDL 时的权限问题
3. `PKG_DEVICE_PARAM_PARTITION` 内部需要执行:
- `CREATE TABLE`
- `CREATE INDEX`
- `CREATE OR REPLACE TRIGGER`
Oracle 特性说明:
1. 包里执行 DDL 依赖的是 **直接授予的系统权限**
2. 不是依赖角色权限
3. Oracle 没有 `GRANT CREATE INDEX TO user` 这种系统权限,索引能力依赖表对象和表空间配额
---
### 3.5 权限排查与修正
在权限排查过程中发现一个执行细节:
1. 当前 SQL 客户端执行 `GRANT` 语句时,**不能带尾部分号**
2. 带分号时会报:
```text
ORA-00933: SQL 命令未正确结束
```
最终应采用“不带分号、单条执行”的方式,例如:
```sql
GRANT CREATE TRIGGER TO HAIWEI
```
本次建议的最小权限模型为:
```sql
GRANT CREATE TABLE TO HAIWEI
GRANT CREATE TRIGGER TO HAIWEI
GRANT CREATE JOB TO HAIWEI
GRANT CREATE PROCEDURE TO HAIWEI
```
注意:
1. 不需要 `CREATE ANY TABLE`
2. 不需要 `CREATE ANY TRIGGER`
3. 不需要维护其它 schema
4. 只允许 `HAIWEI` 在自己 schema 下自动维护本系统对象
---
### 3.6 表空间配额检查
后续补查了 `HAIWEI` 的表空间信息。
用户反馈的关键结果为:
```text
HAIWEI_DATA -1 10215620608
```
可解释为:
1. `HAIWEI` 在业务表空间 `HAIWEI_DATA` 上已有配额
2. `MAX_BYTES = -1` 一般表示无限配额
3. `BYTES = 10215620608` 表示已经占用约 9.5GB
结论:
1. 当前自动建表失败的根因不是表空间配额
2. 核心根因是 DDL 权限不足
---
### 3.7 自动建下月分表能力最终验证通过
在权限与环境调整完成后,再次执行:
```sql
BEGIN
HAIWEI.PKG_DEVICE_PARAM_PARTITION.ENSURE_MONTH_TABLE_AND_TRIGGER('202604', '202603');
END;
/
```
执行成功。
这意味着:
1. `PKG_DEVICE_PARAM_PARTITION` 可以在 `HAIWEI` schema 内自动建下月分表
2. 也可以自动创建对应下月触发器
3. 后续不需要每个月手工执行触发器模板
---
## 4. 当前数据库最终状态
根据本次排查与执行结果,当前数据库已经具备以下能力。
### 4.1 已存在的关键表
1. `HAIWEI.BASE_DEVICE_PARAM_VAL_202603`
2. `HAIWEI.RT_DAILY_PROD_STATE`
3. `HAIWEI.DEVICE_DAILY_PRODUCTION`
### 4.2 已存在的关键包
1. `HAIWEI.PKG_DEVICE_PROD_CALC`
2. `HAIWEI.PKG_DEVICE_PROD_CALC BODY`
3. `HAIWEI.PKG_DEVICE_PARAM_PARTITION`
4. `HAIWEI.PKG_DEVICE_PARAM_PARTITION BODY`
### 4.3 已存在的关键触发器
1. `HAIWEI.TRG_BDPV_202603_RT`
### 4.4 已存在的关键调度任务
1. `HAIWEI.JOB_FLUSH_RT_TO_DAY`
2. `HAIWEI.JOB_ENSURE_DEVICE_PARAM_PARTITION`
---
## 5. 自动化链路说明
### 5.1 当前月自动采集链路
```text
自动采集数据
-> BASE_DEVICE_PARAM_VAL_202603
-> TRG_BDPV_202603_RT
-> RT_DAILY_PROD_STATE
```
1. `BASE_DEVICE_PARAM_VAL_202603`
2. `RT_DAILY_PROD_STATE`
3. `DEVICE_DAILY_PRODUCTION`
4. `PKG_DEVICE_PROD_CALC`
5. `TRG_BDPV_202603_RT`
6. `JOB_FLUSH_RT_TO_DAY`
说明:
1. 仅自动采集设备会走当前月分表
2. 触发器只处理:
- `PARAM_NAME = '机台状态-实际产出数量'`
3. 触发器会执行 delta-sum 累计逻辑
1. 当前月分表已经存在
2. 当前月触发器已经存在
3. RT / DAY 基础表已经存在
4. 夜间 RT->DAY 搬迁包和 job 已经存在
### 3.3 自动建分表方案废弃
本次过程中曾经设计过数据库自动创建下月分表/触发器的方案,但当前最终决策为:
1. 删除数据库自动建月分表脚本
2. 取消数据库侧月底自动建分表/触发器方案
3. 后续月份新建分表改为由 **C# 采集程序** 负责
原因:
1. 分表本身是采集入库前提
2. 由采集程序自己保障分表存在更符合职责边界
3. 可避免数据库额外 DDL 权限复杂度
4. 可避免“数据库月底 job 未执行,但采集程序已切月写入”的风险
---
## 4. 当前数据库最终对象状态
### 4.1 表
1. `HAIWEI.BASE_DEVICE_PARAM_VAL_202603`
2. `HAIWEI.RT_DAILY_PROD_STATE`
3. `HAIWEI.DEVICE_DAILY_PRODUCTION`
### 4.2 触发器
1. `HAIWEI.TRG_BDPV_202603_RT`
### 4.3 包
1. `HAIWEI.PKG_DEVICE_PROD_CALC`
2. `HAIWEI.PKG_DEVICE_PROD_CALC BODY`
### 4.4 调度任务
1. `HAIWEI.JOB_FLUSH_RT_TO_DAY`
---
## 5. 数据库完整链路
### 5.1 自动采集设备链路
```text
C# 采集程序
-> 确保 BASE_DEVICE_PARAM_VAL_202603 已存在
-> 插入 BASE_DEVICE_PARAM_VAL_202603
-> TRG_BDPV_202603_RT
-> RT_DAILY_PROD_STATE
-> JOB_FLUSH_RT_TO_DAY
-> PKG_DEVICE_PROD_CALC
-> DEVICE_DAILY_PRODUCTION
```
### 5.2 OLD 设备链路
```text
PDA / Java 写入
PDA / Java
-> BASE_DEVICE_PARAM_VAL
-> BaseDeviceParamValServiceImpl.syncRtStateIfNeeded(...)
-> RT_DAILY_PROD_STATE
-> Java Service 同步更新 RT_DAILY_PROD_STATE
```
说明:
1. OLD 设备不依赖数据库触发器
2. OLD 的 RT 更新由 Java 服务层同步完成
### 5.3 夜间汇总链路
### 5.3 Board4 查询链路
```text
JOB_FLUSH_RT_TO_DAY
-> PKG_DEVICE_PROD_CALC.FLUSH_RT_TO_DAY
-> RT_DAILY_PROD_STATE(昨天)
-> DEVICE_DAILY_PRODUCTION
-> 删除昨天 RT
Board4.dayTotal
-> RT_DAILY_PROD_STATE(当天)
Board4.monthTotal
-> DEVICE_DAILY_PRODUCTION(本月已汇总部分)
+ RT_DAILY_PROD_STATE(本月未入DAY部分)
Board4.deviceProductionList
-> RT_DAILY_PROD_STATE(当天 current_total)
```
调度时间:
```text
每天 00:10:00
```
### 5.4 下月分表自动准备链路
```text
JOB_ENSURE_DEVICE_PARAM_PARTITION
-> PKG_DEVICE_PARAM_PARTITION.ENSURE_MONTH_TABLE_AND_TRIGGER
-> 用当前月表克隆下月表
-> 创建下月索引
-> 创建下月 RT 触发器
```
调度时间:
```text
每月 28/29/30/31 日 23:55:00
```
这样设计的原因:
1. 不同月份天数不同
2. 过程本身是幂等的
3. 重复触发也不会重复建对象
---
## 6. 现在是否已经“完全自动运行”
## 6. 数据库重建顺序
从机制角度看,当前已经具备:
1. 当前月自动采集数据自动进 RT
2. 夜间自动 RT -> DAY
3. 月底自动准备下月分表与触发器
因此,当前可以认为:
```text
数据库自动化链路已经部署完成
```
但仍建议在以下两个时间点做一次运维验证:
1. `JOB_FLUSH_RT_TO_DAY` 首次实际运行后,确认 `RUN_COUNT > 0`
2. 月底任务首次自动执行后,确认已自动生成:
- `BASE_DEVICE_PARAM_VAL_202604`
- `TRG_BDPV_202604_RT`
---
## 7. 数据库重建顺序
如果未来数据库被重置,可按以下顺序恢复:
如果未来数据库被重置,按以下顺序执行。
### 第一步:创建基础表
@ -365,80 +166,47 @@ JOB_ENSURE_DEVICE_PARAM_PARTITION
### 第三步:确保当前月分表存在
4. 手工创建当前月分表,例如:
4. **C# 采集程序** 或其配套建表脚本创建当前月分表,例如:
`BASE_DEVICE_PARAM_VAL_202603`
说明:
1. 自动建分表包默认是“用当前月分表去克隆下月分表”
2. 所以首次上线时,当前月分表仍需人工准备
### 第四步:为当前月分表创建触发器
### 第四步:部署当前月触发器
5. `base_device_param_val_202603_trigger.sql`
### 第五步:创建夜间调度任务
### 第五步:创建夜间 RT->DAY job
6. `device_prod_calc_scheduler_job.sql`
### 第六步:创建自动建下月分表包
7. `device_param_partition_auto_pkg.sql`
### 第七步:创建自动建下月分表调度任务
8. `device_param_partition_auto_scheduler_job.sql`
---
## 8. 脚本执行顺序与作用总表
## 7. 当前资源目录保留的 SQL 文件
为避免数据库重置后执行顺序混乱,建议严格按下表顺序执行。
| 顺序 | 文件名 | 文件位置 | 是否必须 | 作用 |
|------|--------|----------|----------|------|
| 1 | `rt_daily_prod_state.sql` | `aucma-base/src/main/resources` | 必须 | 创建实时产量状态表 `RT_DAILY_PROD_STATE`,承接自动采集触发器与 OLD 设备 Java 同步累计 |
| 2 | `device_daily_production.sql` | `aucma-base/src/main/resources` | 必须 | 创建设备日产量权威汇总表 `DEVICE_DAILY_PRODUCTION`,承接夜间 RT->DAY 搬迁 |
| 3 | `device_prod_calc_pkg.sql` | `aucma-base/src/main/resources` | 必须 | 创建夜间搬迁包 `PKG_DEVICE_PROD_CALC`,负责把昨日 RT 数据搬迁到 DAY |
| 4 | `base_device_param_val_202603_trigger.sql` | `aucma-base/src/main/resources` | 当前月必须 | 为当前月分表 `BASE_DEVICE_PARAM_VAL_202603` 创建触发器 `TRG_BDPV_202603_RT`,让自动采集写入实时更新 RT |
| 5 | `device_prod_calc_scheduler_job.sql` | `aucma-base/src/main/resources` | 必须 | 创建夜间调度任务 `JOB_FLUSH_RT_TO_DAY`,每天 00:10 自动执行 `PKG_DEVICE_PROD_CALC.FLUSH_RT_TO_DAY` |
| 6 | `device_param_partition_auto_pkg.sql` | `aucma-base/src/main/resources` | 推荐且建议必须 | 创建自动建下月分表/触发器包 `PKG_DEVICE_PARAM_PARTITION`,避免每月手工建新分表 |
| 7 | `device_param_partition_auto_scheduler_job.sql` | `aucma-base/src/main/resources` | 推荐且建议必须 | 创建月底自动建下月分表与触发器的任务 `JOB_ENSURE_DEVICE_PARAM_PARTITION` |
补充说明:
1. 第 4 步之所以仍要“当前月必须手工执行一次”,是因为自动建月分表包默认以“当前月分表”为源克隆“下月分表”,所以首次上线必须先把当前月表和当前月触发器补齐。
2. 第 6、7 步执行完成后,后续月份无需再手工创建 `BASE_DEVICE_PARAM_VAL_YYYYMM``TRG_BDPV_YYYYMM_RT`
3. 如果数据库不是首次重置,而只是补装夜间任务,可以在确认基础表和当前月触发器都存在后,从第 5 步开始执行。
---
## 9. 自动化 SQL 文件清单
本次自动化相关 SQL 一共 7 个:
1. `zs_aucma-mes-back/aucma-base/src/main/resources/rt_daily_prod_state.sql`
2. `zs_aucma-mes-back/aucma-base/src/main/resources/device_daily_production.sql`
3. `zs_aucma-mes-back/aucma-base/src/main/resources/device_prod_calc_pkg.sql`
4. `sql/base_device_param_val_202603_trigger.sql`
5. `sql/device_prod_calc_scheduler_job.sql`
6. `sql/device_param_partition_auto_pkg.sql`
7. `sql/device_param_partition_auto_scheduler_job.sql`
以下为全部 SQL 原文备份。
---
## 10. SQL 原文备份
### 9.1 `rt_daily_prod_state.sql`
文件位置:
当前保留在:
```text
zs_aucma-mes-back/aucma-base/src/main/resources/rt_daily_prod_state.sql
zs_aucma-mes-back/aucma-base/src/main/resources
```
的数据库脚本有 5 个:
1. `rt_daily_prod_state.sql`
2. `device_daily_production.sql`
3. `device_prod_calc_pkg.sql`
4. `base_device_param_val_202603_trigger.sql`
5. `device_prod_calc_scheduler_job.sql`
说明:
1. 这些脚本是数据库恢复必需的脚本
2. 不再保留“自动建下月分表/触发器”的数据库脚本
3. 后续月份分表由 C# 采集程序负责
---
## 8. SQL 原文备份
### 8.1 `rt_daily_prod_state.sql`
```sql
-- RT_DAILY_PROD_STATE设备当日实时产量状态表
-- 兼容Oracle 11 / Oracle 19
@ -477,15 +245,7 @@ CREATE INDEX IDX_RT_DAILY_PROD_STATE_DEVICE
ON RT_DAILY_PROD_STATE (DEVICE_CODE, PROD_DATE);
```
---
### 9.2 `device_daily_production.sql`
文件位置:
```text
zs_aucma-mes-back/aucma-base/src/main/resources/device_daily_production.sql
```
### 8.2 `device_daily_production.sql`
```sql
-- DEVICE_DAILY_PRODUCTION设备日产量权威汇总表
@ -519,15 +279,7 @@ CREATE INDEX IDX_DEVICE_DAILY_PRODUCTION_DEVICE
ON DEVICE_DAILY_PRODUCTION (DEVICE_CODE, PROD_DATE);
```
---
### 9.3 `device_prod_calc_pkg.sql`
文件位置:
```text
zs_aucma-mes-back/aucma-base/src/main/resources/device_prod_calc_pkg.sql
```
### 8.3 `device_prod_calc_pkg.sql`
```sql
-- 设备产量夜间搬迁过程
@ -597,22 +349,13 @@ END PKG_DEVICE_PROD_CALC;
/
```
---
### 9.4 `base_device_param_val_202603_trigger.sql`
文件位置:
```text
sql/base_device_param_val_202603_trigger.sql
```
### 8.4 `base_device_param_val_202603_trigger.sql`
```sql
-- 2026年03月自动采集分表触发器
-- 说明:
-- 1. 本脚本是 base_device_param_val_partition_trigger_template.sql 的 202603 实例化版本。
-- 2. 挂载表BASE_DEVICE_PARAM_VAL_202603
-- 3. 仅处理自动采集设备OLD 设备 RT 累计由 Java Service 同步完成。
-- 1. 挂载表BASE_DEVICE_PARAM_VAL_202603
-- 2. 仅处理自动采集设备OLD 设备 RT 累计由 Java Service 同步完成。
CREATE OR REPLACE TRIGGER TRG_BDPV_202603_RT
AFTER INSERT ON BASE_DEVICE_PARAM_VAL_202603
@ -687,15 +430,7 @@ END;
/
```
---
### 9.5 `device_prod_calc_scheduler_job.sql`
文件位置:
```text
sql/device_prod_calc_scheduler_job.sql
```
### 8.5 `device_prod_calc_scheduler_job.sql`
```sql
-- 夜间 RT -> DAY 搬迁调度任务
@ -736,283 +471,19 @@ END;
---
### 9.6 `device_param_partition_auto_pkg.sql`
## 9. 最终结论
文件位置:
当前最终数据库方案是:
1. 当前月分表由 C# 采集程序创建
2. 当前月分表触发器由数据库脚本部署
3. 自动采集明细通过触发器实时进入 RT
4. OLD 数据由 Java 同步进入 RT
5. 夜间由 `JOB_FLUSH_RT_TO_DAY` 驱动 `PKG_DEVICE_PROD_CALC` 搬迁到 DAY
换句话说:
```text
sql/device_param_partition_auto_pkg.sql
数据库侧保留“实时累计 + 夜间汇总”
分表创建职责回归 C# 采集程序
```
```sql
-- 自动创建设备参数月分表与对应触发器
-- 说明:
-- 1. 仅需部署一次本包,后续可由 DBMS_SCHEDULER 自动调用。
-- 2. 默认使用“当前月分表”克隆出“下月分表”,并自动创建下月 RT 触发器。
-- 3. 本包是幂等的:目标分表或目标触发器已存在时会自动跳过。
-- 4. 首次上线前,仍需人工确保“当前月分表”已经存在,例如 BASE_DEVICE_PARAM_VAL_202603。
CREATE OR REPLACE PACKAGE PKG_DEVICE_PARAM_PARTITION AS
PROCEDURE ENSURE_MONTH_TABLE_AND_TRIGGER(
P_TARGET_SUFFIX IN VARCHAR2 DEFAULT TO_CHAR(ADD_MONTHS(TRUNC(SYSDATE, 'MM'), 1), 'YYYYMM'),
P_SOURCE_SUFFIX IN VARCHAR2 DEFAULT TO_CHAR(TRUNC(SYSDATE, 'MM'), 'YYYYMM')
);
END PKG_DEVICE_PARAM_PARTITION;
/
CREATE OR REPLACE PACKAGE BODY PKG_DEVICE_PARAM_PARTITION AS
C_TABLE_PREFIX CONSTANT VARCHAR2(64) := 'BASE_DEVICE_PARAM_VAL_';
C_TRIGGER_PREFIX CONSTANT VARCHAR2(64) := 'TRG_BDPV_';
PROCEDURE VALIDATE_SUFFIX(P_SUFFIX IN VARCHAR2) IS
BEGIN
IF P_SUFFIX IS NULL OR NOT REGEXP_LIKE(P_SUFFIX, '^\d{6}$') THEN
RAISE_APPLICATION_ERROR(-20001, '非法分表后缀: ' || NVL(P_SUFFIX, 'NULL'));
END IF;
END VALIDATE_SUFFIX;
FUNCTION OBJECT_EXISTS(P_OBJECT_NAME IN VARCHAR2, P_OBJECT_TYPE IN VARCHAR2) RETURN NUMBER IS
V_COUNT NUMBER;
BEGIN
SELECT COUNT(1)
INTO V_COUNT
FROM USER_OBJECTS
WHERE OBJECT_NAME = UPPER(P_OBJECT_NAME)
AND OBJECT_TYPE = UPPER(P_OBJECT_TYPE);
RETURN V_COUNT;
END OBJECT_EXISTS;
FUNCTION TABLE_EXISTS(P_TABLE_NAME IN VARCHAR2) RETURN NUMBER IS
V_COUNT NUMBER;
BEGIN
SELECT COUNT(1)
INTO V_COUNT
FROM USER_TABLES
WHERE TABLE_NAME = UPPER(P_TABLE_NAME);
RETURN V_COUNT;
END TABLE_EXISTS;
PROCEDURE EXECUTE_DDL(P_SQL IN CLOB) IS
V_CURSOR INTEGER;
BEGIN
V_CURSOR := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(V_CURSOR, P_SQL, DBMS_SQL.NATIVE);
DBMS_SQL.CLOSE_CURSOR(V_CURSOR);
EXCEPTION
WHEN OTHERS THEN
IF DBMS_SQL.IS_OPEN(V_CURSOR) THEN
DBMS_SQL.CLOSE_CURSOR(V_CURSOR);
END IF;
RAISE;
END EXECUTE_DDL;
PROCEDURE CREATE_TARGET_TABLE(P_SOURCE_TABLE IN VARCHAR2, P_TARGET_TABLE IN VARCHAR2) IS
V_SQL CLOB;
BEGIN
V_SQL := 'CREATE TABLE ' || P_TARGET_TABLE || ' AS SELECT * FROM ' || P_SOURCE_TABLE || ' WHERE 1 = 0';
EXECUTE IMMEDIATE V_SQL;
END CREATE_TARGET_TABLE;
PROCEDURE CREATE_TARGET_INDEXES(P_TARGET_TABLE IN VARCHAR2, P_TARGET_SUFFIX IN VARCHAR2) IS
V_INDEX_NAME_1 VARCHAR2(128);
V_INDEX_NAME_2 VARCHAR2(128);
BEGIN
V_INDEX_NAME_1 := 'IDX_BDPV_' || P_TARGET_SUFFIX || '_PCD';
V_INDEX_NAME_2 := 'IDX_BDPV_' || P_TARGET_SUFFIX || '_DCT';
IF OBJECT_EXISTS(V_INDEX_NAME_1, 'INDEX') = 0 THEN
EXECUTE IMMEDIATE 'CREATE INDEX ' || V_INDEX_NAME_1 ||
' ON ' || P_TARGET_TABLE || ' (PARAM_NAME, COLLECT_TIME, DEVICE_CODE, RECORD_ID)';
END IF;
IF OBJECT_EXISTS(V_INDEX_NAME_2, 'INDEX') = 0 THEN
EXECUTE IMMEDIATE 'CREATE INDEX ' || V_INDEX_NAME_2 ||
' ON ' || P_TARGET_TABLE || ' (DEVICE_CODE, COLLECT_TIME)';
END IF;
END CREATE_TARGET_INDEXES;
PROCEDURE CREATE_TARGET_TRIGGER(P_TARGET_TABLE IN VARCHAR2, P_TARGET_SUFFIX IN VARCHAR2) IS
V_TRIGGER_NAME VARCHAR2(128);
V_SQL CLOB;
BEGIN
V_TRIGGER_NAME := C_TRIGGER_PREFIX || P_TARGET_SUFFIX || '_RT';
IF OBJECT_EXISTS(V_TRIGGER_NAME, 'TRIGGER') > 0 THEN
RETURN;
END IF;
V_SQL := 'CREATE OR REPLACE TRIGGER ' || V_TRIGGER_NAME || CHR(10) ||
'AFTER INSERT ON ' || P_TARGET_TABLE || CHR(10) ||
'FOR EACH ROW' || CHR(10) ||
'DECLARE' || CHR(10) ||
' V_PROD_DATE DATE;' || CHR(10) ||
' V_NEW_VAL NUMBER(18,4);' || CHR(10) ||
' V_LAST_VAL NUMBER(18,4);' || CHR(10) ||
' V_CUR_TOTAL NUMBER(18,4);' || CHR(10) ||
' V_RESET_COUNT NUMBER(10);' || CHR(10) ||
' V_DELTA NUMBER(18,4);' || CHR(10) ||
'BEGIN' || CHR(10) ||
' IF :NEW.PARAM_NAME <> ''机台状态-实际产出数量'' THEN' || CHR(10) ||
' RETURN;' || CHR(10) ||
' END IF;' || CHR(10) ||
' IF :NEW.DEVICE_CODE LIKE ''OLD-%'' THEN' || CHR(10) ||
' RETURN;' || CHR(10) ||
' END IF;' || CHR(10) ||
' BEGIN' || CHR(10) ||
' V_NEW_VAL := TO_NUMBER(:NEW.PARAM_VALUE);' || CHR(10) ||
' EXCEPTION' || CHR(10) ||
' WHEN OTHERS THEN' || CHR(10) ||
' RETURN;' || CHR(10) ||
' END;' || CHR(10) ||
' V_PROD_DATE := TRUNC(:NEW.COLLECT_TIME);' || CHR(10) ||
' BEGIN' || CHR(10) ||
' SELECT LAST_PARAM_VAL, CURRENT_TOTAL, RESET_COUNT' || CHR(10) ||
' INTO V_LAST_VAL, V_CUR_TOTAL, V_RESET_COUNT' || CHR(10) ||
' FROM RT_DAILY_PROD_STATE' || CHR(10) ||
' WHERE PROD_DATE = V_PROD_DATE' || CHR(10) ||
' AND DEVICE_CODE = :NEW.DEVICE_CODE' || CHR(10) ||
' AND PARAM_NAME = :NEW.PARAM_NAME' || CHR(10) ||
' FOR UPDATE;' || CHR(10) ||
' IF V_NEW_VAL > V_LAST_VAL THEN' || CHR(10) ||
' V_DELTA := V_NEW_VAL - V_LAST_VAL;' || CHR(10) ||
' ELSIF V_NEW_VAL < V_LAST_VAL THEN' || CHR(10) ||
' V_DELTA := V_NEW_VAL;' || CHR(10) ||
' V_RESET_COUNT := NVL(V_RESET_COUNT, 0) + 1;' || CHR(10) ||
' ELSE' || CHR(10) ||
' V_DELTA := 0;' || CHR(10) ||
' END IF;' || CHR(10) ||
' UPDATE RT_DAILY_PROD_STATE' || CHR(10) ||
' SET LAST_PARAM_VAL = V_NEW_VAL,' || CHR(10) ||
' CURRENT_TOTAL = NVL(V_CUR_TOTAL, 0) + NVL(V_DELTA, 0),' || CHR(10) ||
' RESET_COUNT = NVL(V_RESET_COUNT, 0),' || CHR(10) ||
' LAST_COLLECT_TIME = :NEW.COLLECT_TIME,' || CHR(10) ||
' UPDATE_TIME = SYSDATE' || CHR(10) ||
' WHERE PROD_DATE = V_PROD_DATE' || CHR(10) ||
' AND DEVICE_CODE = :NEW.DEVICE_CODE' || CHR(10) ||
' AND PARAM_NAME = :NEW.PARAM_NAME;' || CHR(10) ||
' EXCEPTION' || CHR(10) ||
' WHEN NO_DATA_FOUND THEN' || CHR(10) ||
' INSERT INTO RT_DAILY_PROD_STATE (' || CHR(10) ||
' PROD_DATE, DEVICE_CODE, PARAM_NAME, LAST_PARAM_VAL,' || CHR(10) ||
' CURRENT_TOTAL, RESET_COUNT, DIRTY_FLAG, LAST_COLLECT_TIME, UPDATE_TIME' || CHR(10) ||
' ) VALUES (' || CHR(10) ||
' V_PROD_DATE, :NEW.DEVICE_CODE, :NEW.PARAM_NAME, V_NEW_VAL,' || CHR(10) ||
' 0, 0, 0, :NEW.COLLECT_TIME, SYSDATE' || CHR(10) ||
' );' || CHR(10) ||
' END;' || CHR(10) ||
'END;';
EXECUTE_DDL(V_SQL);
END CREATE_TARGET_TRIGGER;
PROCEDURE ENSURE_MONTH_TABLE_AND_TRIGGER(
P_TARGET_SUFFIX IN VARCHAR2 DEFAULT TO_CHAR(ADD_MONTHS(TRUNC(SYSDATE, 'MM'), 1), 'YYYYMM'),
P_SOURCE_SUFFIX IN VARCHAR2 DEFAULT TO_CHAR(TRUNC(SYSDATE, 'MM'), 'YYYYMM')
) IS
V_SOURCE_TABLE VARCHAR2(128);
V_TARGET_TABLE VARCHAR2(128);
BEGIN
VALIDATE_SUFFIX(P_TARGET_SUFFIX);
VALIDATE_SUFFIX(P_SOURCE_SUFFIX);
V_SOURCE_TABLE := C_TABLE_PREFIX || P_SOURCE_SUFFIX;
V_TARGET_TABLE := C_TABLE_PREFIX || P_TARGET_SUFFIX;
IF TABLE_EXISTS(V_SOURCE_TABLE) = 0 THEN
RAISE_APPLICATION_ERROR(-20002, '源分表不存在: ' || V_SOURCE_TABLE);
END IF;
IF TABLE_EXISTS(V_TARGET_TABLE) = 0 THEN
CREATE_TARGET_TABLE(V_SOURCE_TABLE, V_TARGET_TABLE);
END IF;
CREATE_TARGET_INDEXES(V_TARGET_TABLE, P_TARGET_SUFFIX);
CREATE_TARGET_TRIGGER(V_TARGET_TABLE, P_TARGET_SUFFIX);
END ENSURE_MONTH_TABLE_AND_TRIGGER;
END PKG_DEVICE_PARAM_PARTITION;
/
```
---
### 9.7 `device_param_partition_auto_scheduler_job.sql`
文件位置:
```text
sql/device_param_partition_auto_scheduler_job.sql
```
```sql
-- 自动创建下月分表与触发器的数据库任务
-- 说明:
-- 1. 每月 28 号到 31 号 23:55 都会尝试执行一次。
-- 2. 由于 ENSURE_MONTH_TABLE_AND_TRIGGER 是幂等的,所以重复执行不会重复建表。
-- 3. 采用这种写法是为了兼容不同月份天数。
BEGIN
DBMS_SCHEDULER.DROP_JOB(
JOB_NAME => 'JOB_ENSURE_DEVICE_PARAM_PARTITION',
FORCE => TRUE
);
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -27475 THEN
RAISE;
END IF;
END;
/
BEGIN
DBMS_SCHEDULER.CREATE_JOB(
JOB_NAME => 'JOB_ENSURE_DEVICE_PARAM_PARTITION',
JOB_TYPE => 'PLSQL_BLOCK',
JOB_ACTION => 'BEGIN PKG_DEVICE_PARAM_PARTITION.ENSURE_MONTH_TABLE_AND_TRIGGER; END;',
START_DATE => SYSTIMESTAMP,
REPEAT_INTERVAL => 'FREQ=MONTHLY;BYMONTHDAY=28,29,30,31;BYHOUR=23;BYMINUTE=55;BYSECOND=0',
ENABLED => TRUE,
AUTO_DROP => FALSE,
COMMENTS => '自动创建下月设备参数分表与 RT 触发器'
);
END;
/
```
---
## 11. 推荐保留策略
建议:
1. 本文档长期保留在:
`zs_aucma-mes-back/aucma-base/src/main/resources`
2. 根目录 `sql/` 中的自动化 SQL 文件不要删除
3. 数据库重置、迁移、容灾恢复时,以本文档为准恢复对象
---
## 12. 最终结论
本次排查后,数据库侧已经完成:
1. 当前月自动采集分表 `BASE_DEVICE_PARAM_VAL_202603`
2. 当前月触发器 `TRG_BDPV_202603_RT`
3. 实时表 `RT_DAILY_PROD_STATE`
4. 日汇总表 `DEVICE_DAILY_PRODUCTION`
5. 夜间搬迁包 `PKG_DEVICE_PROD_CALC`
6. 夜间搬迁任务 `JOB_FLUSH_RT_TO_DAY`
7. 自动建下月分表包 `PKG_DEVICE_PARAM_PARTITION`
8. 自动建下月分表任务 `JOB_ENSURE_DEVICE_PARAM_PARTITION`
从架构上看,数据库已具备:
```text
当前月自动采集入 RT
+ 夜间自动 RT -> DAY
+ 月底自动准备下月分表与下月触发器
```
后续只需要常规运维检查 job 实际运行情况,不需要再每个月手工执行触发器模板。最自律帅气聪明的臧辰浩

@ -1,190 +0,0 @@
-- 自动创建设备参数月分表与对应触发器
-- 说明:
-- 1. 仅需部署一次本包,后续可由 DBMS_SCHEDULER 自动调用。
-- 2. 默认使用“当前月分表”克隆出“下月分表”,并自动创建下月 RT 触发器。
-- 3. 本包是幂等的:目标分表或目标触发器已存在时会自动跳过。
-- 4. 首次上线前,仍需人工确保“当前月分表”已经存在,例如 BASE_DEVICE_PARAM_VAL_202603。
CREATE OR REPLACE PACKAGE PKG_DEVICE_PARAM_PARTITION AS
PROCEDURE ENSURE_MONTH_TABLE_AND_TRIGGER(
P_TARGET_SUFFIX IN VARCHAR2 DEFAULT TO_CHAR(ADD_MONTHS(TRUNC(SYSDATE, 'MM'), 1), 'YYYYMM'),
P_SOURCE_SUFFIX IN VARCHAR2 DEFAULT TO_CHAR(TRUNC(SYSDATE, 'MM'), 'YYYYMM')
);
END PKG_DEVICE_PARAM_PARTITION;
/
CREATE OR REPLACE PACKAGE BODY PKG_DEVICE_PARAM_PARTITION AS
C_TABLE_PREFIX CONSTANT VARCHAR2(64) := 'BASE_DEVICE_PARAM_VAL_';
C_TRIGGER_PREFIX CONSTANT VARCHAR2(64) := 'TRG_BDPV_';
PROCEDURE VALIDATE_SUFFIX(P_SUFFIX IN VARCHAR2) IS
BEGIN
IF P_SUFFIX IS NULL OR NOT REGEXP_LIKE(P_SUFFIX, '^\d{6}$') THEN
RAISE_APPLICATION_ERROR(-20001, '非法分表后缀: ' || NVL(P_SUFFIX, 'NULL'));
END IF;
END VALIDATE_SUFFIX;
FUNCTION OBJECT_EXISTS(P_OBJECT_NAME IN VARCHAR2, P_OBJECT_TYPE IN VARCHAR2) RETURN NUMBER IS
V_COUNT NUMBER;
BEGIN
SELECT COUNT(1)
INTO V_COUNT
FROM USER_OBJECTS
WHERE OBJECT_NAME = UPPER(P_OBJECT_NAME)
AND OBJECT_TYPE = UPPER(P_OBJECT_TYPE);
RETURN V_COUNT;
END OBJECT_EXISTS;
FUNCTION TABLE_EXISTS(P_TABLE_NAME IN VARCHAR2) RETURN NUMBER IS
V_COUNT NUMBER;
BEGIN
SELECT COUNT(1)
INTO V_COUNT
FROM USER_TABLES
WHERE TABLE_NAME = UPPER(P_TABLE_NAME);
RETURN V_COUNT;
END TABLE_EXISTS;
PROCEDURE EXECUTE_DDL(P_SQL IN CLOB) IS
V_CURSOR INTEGER;
BEGIN
V_CURSOR := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(V_CURSOR, P_SQL, DBMS_SQL.NATIVE);
DBMS_SQL.CLOSE_CURSOR(V_CURSOR);
EXCEPTION
WHEN OTHERS THEN
IF DBMS_SQL.IS_OPEN(V_CURSOR) THEN
DBMS_SQL.CLOSE_CURSOR(V_CURSOR);
END IF;
RAISE;
END EXECUTE_DDL;
PROCEDURE CREATE_TARGET_TABLE(P_SOURCE_TABLE IN VARCHAR2, P_TARGET_TABLE IN VARCHAR2) IS
V_SQL CLOB;
BEGIN
-- 为什么这样做:用当前月物理表结构克隆下月表,能保证字段类型与现网保持一致。
V_SQL := 'CREATE TABLE ' || P_TARGET_TABLE || ' AS SELECT * FROM ' || P_SOURCE_TABLE || ' WHERE 1 = 0';
EXECUTE IMMEDIATE V_SQL;
END CREATE_TARGET_TABLE;
PROCEDURE CREATE_TARGET_INDEXES(P_TARGET_TABLE IN VARCHAR2, P_TARGET_SUFFIX IN VARCHAR2) IS
V_INDEX_NAME_1 VARCHAR2(128);
V_INDEX_NAME_2 VARCHAR2(128);
BEGIN
V_INDEX_NAME_1 := 'IDX_BDPV_' || P_TARGET_SUFFIX || '_PCD';
V_INDEX_NAME_2 := 'IDX_BDPV_' || P_TARGET_SUFFIX || '_DCT';
IF OBJECT_EXISTS(V_INDEX_NAME_1, 'INDEX') = 0 THEN
EXECUTE IMMEDIATE 'CREATE INDEX ' || V_INDEX_NAME_1 ||
' ON ' || P_TARGET_TABLE || ' (PARAM_NAME, COLLECT_TIME, DEVICE_CODE, RECORD_ID)';
END IF;
IF OBJECT_EXISTS(V_INDEX_NAME_2, 'INDEX') = 0 THEN
EXECUTE IMMEDIATE 'CREATE INDEX ' || V_INDEX_NAME_2 ||
' ON ' || P_TARGET_TABLE || ' (DEVICE_CODE, COLLECT_TIME)';
END IF;
END CREATE_TARGET_INDEXES;
PROCEDURE CREATE_TARGET_TRIGGER(P_TARGET_TABLE IN VARCHAR2, P_TARGET_SUFFIX IN VARCHAR2) IS
V_TRIGGER_NAME VARCHAR2(128);
V_SQL CLOB;
BEGIN
V_TRIGGER_NAME := C_TRIGGER_PREFIX || P_TARGET_SUFFIX || '_RT';
IF OBJECT_EXISTS(V_TRIGGER_NAME, 'TRIGGER') > 0 THEN
RETURN;
END IF;
V_SQL := 'CREATE OR REPLACE TRIGGER ' || V_TRIGGER_NAME || CHR(10) ||
'AFTER INSERT ON ' || P_TARGET_TABLE || CHR(10) ||
'FOR EACH ROW' || CHR(10) ||
'DECLARE' || CHR(10) ||
' V_PROD_DATE DATE;' || CHR(10) ||
' V_NEW_VAL NUMBER(18,4);' || CHR(10) ||
' V_LAST_VAL NUMBER(18,4);' || CHR(10) ||
' V_CUR_TOTAL NUMBER(18,4);' || CHR(10) ||
' V_RESET_COUNT NUMBER(10);' || CHR(10) ||
' V_DELTA NUMBER(18,4);' || CHR(10) ||
'BEGIN' || CHR(10) ||
' IF :NEW.PARAM_NAME <> ''机台状态-实际产出数量'' THEN' || CHR(10) ||
' RETURN;' || CHR(10) ||
' END IF;' || CHR(10) ||
' IF :NEW.DEVICE_CODE LIKE ''OLD-%'' THEN' || CHR(10) ||
' RETURN;' || CHR(10) ||
' END IF;' || CHR(10) ||
' BEGIN' || CHR(10) ||
' V_NEW_VAL := TO_NUMBER(:NEW.PARAM_VALUE);' || CHR(10) ||
' EXCEPTION' || CHR(10) ||
' WHEN OTHERS THEN' || CHR(10) ||
' RETURN;' || CHR(10) ||
' END;' || CHR(10) ||
' V_PROD_DATE := TRUNC(:NEW.COLLECT_TIME);' || CHR(10) ||
' BEGIN' || CHR(10) ||
' SELECT LAST_PARAM_VAL, CURRENT_TOTAL, RESET_COUNT' || CHR(10) ||
' INTO V_LAST_VAL, V_CUR_TOTAL, V_RESET_COUNT' || CHR(10) ||
' FROM RT_DAILY_PROD_STATE' || CHR(10) ||
' WHERE PROD_DATE = V_PROD_DATE' || CHR(10) ||
' AND DEVICE_CODE = :NEW.DEVICE_CODE' || CHR(10) ||
' AND PARAM_NAME = :NEW.PARAM_NAME' || CHR(10) ||
' FOR UPDATE;' || CHR(10) ||
' IF V_NEW_VAL > V_LAST_VAL THEN' || CHR(10) ||
' V_DELTA := V_NEW_VAL - V_LAST_VAL;' || CHR(10) ||
' ELSIF V_NEW_VAL < V_LAST_VAL THEN' || CHR(10) ||
' V_DELTA := V_NEW_VAL;' || CHR(10) ||
' V_RESET_COUNT := NVL(V_RESET_COUNT, 0) + 1;' || CHR(10) ||
' ELSE' || CHR(10) ||
' V_DELTA := 0;' || CHR(10) ||
' END IF;' || CHR(10) ||
' UPDATE RT_DAILY_PROD_STATE' || CHR(10) ||
' SET LAST_PARAM_VAL = V_NEW_VAL,' || CHR(10) ||
' CURRENT_TOTAL = NVL(V_CUR_TOTAL, 0) + NVL(V_DELTA, 0),' || CHR(10) ||
' RESET_COUNT = NVL(V_RESET_COUNT, 0),' || CHR(10) ||
' LAST_COLLECT_TIME = :NEW.COLLECT_TIME,' || CHR(10) ||
' UPDATE_TIME = SYSDATE' || CHR(10) ||
' WHERE PROD_DATE = V_PROD_DATE' || CHR(10) ||
' AND DEVICE_CODE = :NEW.DEVICE_CODE' || CHR(10) ||
' AND PARAM_NAME = :NEW.PARAM_NAME;' || CHR(10) ||
' EXCEPTION' || CHR(10) ||
' WHEN NO_DATA_FOUND THEN' || CHR(10) ||
' INSERT INTO RT_DAILY_PROD_STATE (' || CHR(10) ||
' PROD_DATE, DEVICE_CODE, PARAM_NAME, LAST_PARAM_VAL,' || CHR(10) ||
' CURRENT_TOTAL, RESET_COUNT, DIRTY_FLAG, LAST_COLLECT_TIME, UPDATE_TIME' || CHR(10) ||
' ) VALUES (' || CHR(10) ||
' V_PROD_DATE, :NEW.DEVICE_CODE, :NEW.PARAM_NAME, V_NEW_VAL,' || CHR(10) ||
' 0, 0, 0, :NEW.COLLECT_TIME, SYSDATE' || CHR(10) ||
' );' || CHR(10) ||
' END;' || CHR(10) ||
'END;';
EXECUTE_DDL(V_SQL);
END CREATE_TARGET_TRIGGER;
PROCEDURE ENSURE_MONTH_TABLE_AND_TRIGGER(
P_TARGET_SUFFIX IN VARCHAR2 DEFAULT TO_CHAR(ADD_MONTHS(TRUNC(SYSDATE, 'MM'), 1), 'YYYYMM'),
P_SOURCE_SUFFIX IN VARCHAR2 DEFAULT TO_CHAR(TRUNC(SYSDATE, 'MM'), 'YYYYMM')
) IS
V_SOURCE_TABLE VARCHAR2(128);
V_TARGET_TABLE VARCHAR2(128);
BEGIN
VALIDATE_SUFFIX(P_TARGET_SUFFIX);
VALIDATE_SUFFIX(P_SOURCE_SUFFIX);
V_SOURCE_TABLE := C_TABLE_PREFIX || P_SOURCE_SUFFIX;
V_TARGET_TABLE := C_TABLE_PREFIX || P_TARGET_SUFFIX;
IF TABLE_EXISTS(V_SOURCE_TABLE) = 0 THEN
RAISE_APPLICATION_ERROR(-20002, '源分表不存在: ' || V_SOURCE_TABLE);
END IF;
IF TABLE_EXISTS(V_TARGET_TABLE) = 0 THEN
CREATE_TARGET_TABLE(V_SOURCE_TABLE, V_TARGET_TABLE);
END IF;
CREATE_TARGET_INDEXES(V_TARGET_TABLE, P_TARGET_SUFFIX);
CREATE_TARGET_TRIGGER(V_TARGET_TABLE, P_TARGET_SUFFIX);
END ENSURE_MONTH_TABLE_AND_TRIGGER;
END PKG_DEVICE_PARAM_PARTITION;
/

@ -1,37 +0,0 @@
-- 自动创建下月分表与触发器的数据库任务
-- 说明:
-- 1. 每月 28 号到 31 号 23:55 都会尝试执行一次。
-- 2. 由于 ENSURE_MONTH_TABLE_AND_TRIGGER 是幂等的,所以重复执行不会重复建表。
-- 3. 采用这种写法是为了兼容不同月份天数。
BEGIN
DBMS_SCHEDULER.DROP_JOB(
JOB_NAME => 'JOB_ENSURE_DEVICE_PARAM_PARTITION',
FORCE => TRUE
);
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -27475 THEN
RAISE;
END IF;
END;
/
BEGIN
DBMS_SCHEDULER.CREATE_JOB(
JOB_NAME => 'JOB_ENSURE_DEVICE_PARAM_PARTITION',
JOB_TYPE => 'PLSQL_BLOCK',
JOB_ACTION => 'BEGIN PKG_DEVICE_PARAM_PARTITION.ENSURE_MONTH_TABLE_AND_TRIGGER; END;',
START_DATE => SYSTIMESTAMP,
REPEAT_INTERVAL => 'FREQ=MONTHLY;BYMONTHDAY=28,29,30,31;BYHOUR=23;BYMINUTE=55;BYSECOND=0',
ENABLED => TRUE,
AUTO_DROP => FALSE,
COMMENTS => '自动创建下月设备参数分表与 RT 触发器'
);
END;
/
-- 查看状态:
-- SELECT JOB_NAME, ENABLED, STATE, LAST_START_DATE, NEXT_RUN_DATE
-- FROM USER_SCHEDULER_JOBS
-- WHERE JOB_NAME = 'JOB_ENSURE_DEVICE_PARAM_PARTITION';
Loading…
Cancel
Save