From c3fedce7d5955c13b10196bdfd53aff3abd47fea Mon Sep 17 00:00:00 2001 From: "zangch@mesnac.com" Date: Mon, 16 Mar 2026 17:37:25 +0800 Subject: [PATCH] =?UTF-8?q?docs(board4=E5=88=86=E8=A1=A8):=20=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=95=B0=E6=8D=AE=E5=BA=93=E6=8E=92=E6=9F=A5=E4=B8=8E?= =?UTF-8?q?=E9=87=8D=E5=BB=BA=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...abase_troubleshooting_and_rebuild_guide.md | 847 +++---------- ...d4_end_to_end_full_implementation_guide.md | 1097 ++++------------- .../device_param_partition_auto_pkg.sql | 190 --- ...ice_param_partition_auto_scheduler_job.sql | 37 - 4 files changed, 372 insertions(+), 1799 deletions(-) delete mode 100644 aucma-base/src/main/resources/device_param_partition_auto_pkg.sql delete mode 100644 aucma-base/src/main/resources/device_param_partition_auto_scheduler_job.sql diff --git a/aucma-base/src/main/resources/board4_database_troubleshooting_and_rebuild_guide.md b/aucma-base/src/main/resources/board4_database_troubleshooting_and_rebuild_guide.md index a4b4ff0..f830bc1 100644 --- a/aucma-base/src/main/resources/board4_database_troubleshooting_and_rebuild_guide.md +++ b/aucma-base/src/main/resources/board4_database_troubleshooting_and_rebuild_guide.md @@ -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 实际运行情况,不需要再每个月手工执行触发器模板。最自律帅气聪明的臧辰浩 diff --git a/aucma-base/src/main/resources/board4_end_to_end_full_implementation_guide.md b/aucma-base/src/main/resources/board4_end_to_end_full_implementation_guide.md index 0e70464..b2e4aa0 100644 --- a/aucma-base/src/main/resources/board4_end_to_end_full_implementation_guide.md +++ b/aucma-base/src/main/resources/board4_end_to_end_full_implementation_guide.md @@ -2,335 +2,94 @@ ## 1. 文档目的 -本文档是本次 `Board4` 产量统计改造的**完整端到端说明文档**,目标是把以下内容一次性讲清楚: +本文档用于完整说明当前 `Board4` 产量统计方案从数据库对象到后端查询再到前端展示的端到端实现。 -1. 为什么要改 -2. 改造后的总体架构 -3. 数据库对象分别做什么 -4. 自动采集数据从入库到 `Board4` 展示的完整链路 -5. OLD 设备 PDA 数据从入库到 `Board4` 展示的完整链路 -6. 夜间 `RT -> DAY` 搬迁的完整链路 -7. `board1 / board4 / trace / SPC / 参数监控` 这些查询是怎么走的 -8. 本次所有代码文件、SQL 文件、文件位置、关键代码与关键 SQL -9. 数据库部署顺序 -10. 常见问题与排查方法 +本文档重点强调的最终职责边界为: -本文档位置: - -```text -zs_aucma-mes-back/aucma-base/src/main/resources/board4_end_to_end_full_implementation_guide.md -``` +1. **月分表新建由 C# 采集程序负责** +2. 数据库负责: + - 当前月分表触发器 + - `RT_DAILY_PROD_STATE` + - `DEVICE_DAILY_PRODUCTION` + - 夜间 `RT -> DAY` +3. Java 负责: + - OLD 设备写单表 + - OLD 写入后同步 RT + - 双源查询 +4. 前端保持原有接口不变,通过后端新口径取数 --- -## 2. 改造背景 +## 2. 当前理解、需求与业务定义 -### 2.1 原始问题 +### 2.1 当前理解 -`Board4` 原始产量统计存在以下问题: +当前场景并不是简单改一条 SQL,而是涉及以下多条链路: -1. 日产量、月产量原本依赖“取最新值”或“按天取最后值”之类逻辑 -2. 这种逻辑无法正确处理: - - 计数器回退 - - 误操作清零 - - `生产计数-当前日期生产总数` 跨天不清零 -3. 自动采集即将切换到月分表,原来的单表 SQL 无法直接兼容 -4. OLD 设备仍然由 PDA 人工录入,不能走同一条自动采集链路 +1. `Board4` 日产量、月产量、设备总产量 +2. `board1` 设备状态与开机时间 +3. `val/index.vue` 最新参数监控 +4. `trace/index.vue` 参数追溯 +5. SPC 参数历史分析 +6. OLD 设备 PDA 写入 +7. 自动采集设备月分表写入 -### 2.2 这次改造要解决的核心目标 +因此,本次改造必须同时保证: -1. `Board4.dayTotal` 改为读取实时累计表 `RT_DAILY_PROD_STATE` -2. `Board4.monthTotal` 改为读取 `DEVICE_DAILY_PRODUCTION + RT_DAILY_PROD_STATE` -3. `Board4.deviceProductionList` 改为读取 `RT_DAILY_PROD_STATE` -4. 自动采集设备写月分表 `BASE_DEVICE_PARAM_VAL_YYYYMM` -5. OLD 设备继续写 `BASE_DEVICE_PARAM_VAL` -6. OLD 设备写入成功后,Java 同步更新 `RT_DAILY_PROD_STATE` -7. 自动采集设备写入成功后,由月分表触发器更新 `RT_DAILY_PROD_STATE` -8. 夜间将 `RT_DAILY_PROD_STATE` 搬迁到 `DEVICE_DAILY_PRODUCTION` -9. 让 `board1 / board4 / trace / SPC / latest 参数监控` 都兼容“双源查询” +1. 入库路径正确 +2. 中间层累计正确 +3. 汇总层可沉淀历史 +4. 查询层口径统一 ---- +### 2.2 业务事实 -### 2.3 当前理解与代码现状 +当前已确认的业务事实如下: -本次改造并不是从零开始设计,而是在既有代码和既有业务约束上逐步演进。 - -改造前的主要代码现状如下: - -1. `Board4` 的日累计、月累计、设备产量列表位于: - `zs_aucma-mes-back/aucma-report/src/main/resources/mapper/report/Board4Mapper.xml` -2. 原 SQL 仍采用“取最新值”或“按天取最后值”的口径,不具备完整的增量求和能力 -3. `BaseDeviceParamVal` 原本把 `recordId` 当成 `Long` 使用,默认绑定旧单表序列 -4. `BaseDeviceParamValMapper.xml` 中大量查询默认指向旧单表 `BASE_DEVICE_PARAM_VAL` -5. `DmsMobileController` 是 OLD 设备 PDA 参数写入和三色灯写入的重要入口 -6. `board1/index.vue` 与 `board4/index.vue` 都依赖设备状态统计接口,但原来没有统一双源口径 -7. `val/index.vue`、`trace/index.vue`、SPC 查询都和 `BaseDeviceParamValMapper.xml` 的查询能力直接相关 - -这意味着: - -1. 不能只改 `Board4Mapper.xml` -2. 必须同时改造数据落库路径、状态统计路径、追溯路径、SPC 路径 -3. 必须保证“OLD 单表 + 自动采集月分表 + RT 实时表 + DAY 日汇总表”四层协同一致 - -### 2.4 业务事实与已确认定义 - -业务上已经确认的事实与定义如下: - -1. 产量相关参数有两个: +1. 与产量相关的参数主要有两个: - `机台状态-实际产出数量` - `生产计数-当前日期生产总数` -2. 已知异常包括: - - `生产计数-当前日期生产总数` 可能跨天不清零 - - `机台状态-实际产出数量` 可能被误操作清零 - - 采样频率不能完全覆盖硬件瞬时变化,可能丢失峰值 - - 不同设备发生异常的频率与时间不一致 -3. 当前 5 台 OLD 设备无法自动采集,只能由 PDA 手工录入 -4. OLD 设备识别规则固定为: +2. 当前 5 台 OLD 设备不能自动采集,只能人工录入 +3. OLD 设备识别规则固定为: `device_code` 以 `OLD-` 开头 -5. 设备去掉 `OLD-` 前缀后,视为进入自动采集链路 -6. `Board4.yearTotal` 固定使用 `BASE_ORDERINFO`,统计周期固定为“上一自然年” -7. `Board4.selectDeviceProductionAnalysis` 固定展示“今日增量” -8. `BASE_DEVICE_PARAM_VAL` 固定作为 OLD 设备人工录入单表 -9. `BASE_DEVICE_PARAM_VAL_YYYYMM` 固定作为自动采集月分表 -10. 两类表长期并存 -11. OLD 设备转自动采集后: - - 旧表历史保留 - - 统计查询不再回看旧表历史 -12. C# 采集程序直连数据库,Java 不承接其入库 -13. `board1`、`val/index.vue`、`trace/index.vue`、SPC、设备状态接口都要适配双源 -14. 实时表命名固定为 `RT_DAILY_PROD_STATE` -15. 日汇总表命名固定为 `DEVICE_DAILY_PRODUCTION` -16. 有效设备口径固定为 `BASE_DEVICELEDGER.IS_FLAG = 1` - -### 2.5 样本数据与实际情况 - -在 `val.sql` 和相关分析文档中,可以归纳出以下“实际运行情况”: - -1. 数据采集存在明显时间序列特征,设备会以近似固定频率写入参数值 -2. 两个产量参数并非语义完全一致: - - `机台状态-实际产出数量` 更接近累积计数器 - - `生产计数-当前日期生产总数` 更接近理论上的“日计数器” -3. 在样本区间中,按增量求和计算时,两种参数在局部样本上得到的总量可能相同 -4. 但从长期稳定性看,`机台状态-实际产出数量` 更适合做主口径 -5. 自动采集数据是大批量、持续写入的;PDA/OLD 数据是稀疏、人工驱动的 -6. 因采集频率限制,任何方案都无法完全恢复“采样间隔内瞬时增长又瞬时清零”的峰值 - -因此,本方案追求的是: - -1. 在现有信息条件下给出稳健、可解释、可持续维护的产量统计 -2. 尽量避免高估 -3. 接受极端丢峰导致的少量低估 - -### 2.6 改造前 SQL 逻辑的问题 - -改造前以“最新值 / 当天最后值 / 本月每天最后值”为主的统计方式,存在以下问题: - -1. 对 `生产计数-当前日期生产总数`: - - 跨天不清零会直接导致日累计虚高 -2. 对 `机台状态-实际产出数量`: - - 中途清零会导致之前已产出部分被覆盖掉 -3. 如果一天内多次清零,只取最终值会丢失中间已产出量 -4. 按月分表后,旧 SQL 直接查 `BASE_DEVICE_PARAM_VAL`,无法兼容 `BASE_DEVICE_PARAM_VAL_YYYYMM` -5. 原 `Board4` 高刷大屏如果每次都扫明细做窗口计算,性能与维护成本都不可接受 -6. 原 `board1` 和 `board4` 状态统计没有统一到双源三色灯口径,存在页面口径漂移风险 - -### 2.7 详细需求清单 - -本次从业务到技术,实际要满足的需求可以拆成 6 组: - -#### 2.7.1 Board4 统计需求 - -1. `yearTotal` 继续来自 `BASE_ORDERINFO` -2. `monthTotal` 输出当月累计产量 -3. `dayTotal` 输出当日累计产量 -4. `selectDeviceProductionAnalysis` 输出“今日增量” -5. `deviceProductionList` 的合计要与 `dayTotal` 口径一致 - -#### 2.7.2 双源数据需求 - -1. 自动采集数据进入 `BASE_DEVICE_PARAM_VAL_YYYYMM` -2. OLD 人工录入数据进入 `BASE_DEVICE_PARAM_VAL` -3. 查询层必须同时兼容: - - OLD 单表 - - 自动采集月分表 -4. `board1` 的状态、开机时间、设备列表必须兼容双源 - -#### 2.7.3 主键与记录标识需求 - -1. 自动采集月分表 `record_id` 是字符串业务唯一标识 -2. 自动采集月分表 `record_id` 不承担数值主键语义 -3. OLD 单表继续保留数值序列能力 -4. Java 领域模型需要统一主键语义,避免双字段歧义 - -#### 2.7.4 跨月查询需求 - -1. `trace` 允许自定义时间范围,可能跨 2~N 个月 -2. SPC 允许自定义时间范围,也可能跨月 -3. 月初、跨月边界必须能正确拿到边界前值 - -#### 2.7.5 自动化需求 - -1. 当前月自动采集写入时,能够自动维护 RT -2. 夜间自动完成 RT -> DAY 搬迁 -3. 后续月份自动创建下月分表与下月触发器 -4. 尽量避免每个月手工运维 - -#### 2.7.6 可维护性需求 - -1. 方案要兼容 Oracle 11 / Oracle 19 -2. 要避免动态表名注入风险 -3. 要尽量复用 SQL 片段,减少重复维护 -4. 要让排障时能清晰区分: - - 明细层问题 - - 触发器/Java RT 问题 - - 夜间汇总问题 - - 查询层问题 - -### 2.8 需求分析与技术细节 - -#### 2.8.1 为什么主口径选 `机台状态-实际产出数量` - -原因: - -1. 它更接近累积计数器语义 -2. 对“跨天不清零”问题天然免疫 -3. 只要处理回退/清零,就能稳定用 delta-sum 计算 -4. 比 `生产计数-当前日期生产总数` 少一层“理论应清零但现实未清零”的业务解释负担 - -#### 2.8.2 为什么算法选 delta-sum - -delta-sum 是监控系统处理 counter 类时间序列的通用做法。 - -规则: - -1. `prev_val IS NULL`:记 0 -2. `val >= prev_val`:记差值 -3. `val < prev_val`:视为重置,记 `val` - -优点: - -1. 能处理正常递增 -2. 能处理异常清零 -3. 能处理一天内多次回退 -4. 逻辑简单,可在 Java、触发器、存储过程三处统一实现 - -#### 2.8.3 边界记录问题 - -无论是日累计还是月累计,delta-sum 都依赖前一条记录。 - -这带来一个客观问题: - -1. 时间窗口内第一条记录没有前值时,默认 delta = 0 -2. 如果窗口开始前到第一条记录之间设备已有产量,这部分会丢失 - -处理方式分两类: - -1. 精确计算场景: - - 在明细层引入边界前一条记录 - - 用 `LAG()` 做跨边界求差 -2. 实时累计场景: - - RT 表只从当天首条开始做实时累计 - - 历史日的最终权威值由夜间搬迁/汇总沉淀 - -这也是为什么当前最终方案选择: - -1. Board4 实时查询读 RT/DAY -2. 不让高刷页面直接扫明细做全量窗口计算 - -#### 2.8.4 按月分表的跨月查询问题 - -手动月分表会带来两个直接后果: - -1. 表名不能用 `#{}` 绑定,只能用 `${}` 拼接 -2. 跨月查询必须先在 Java 层计算涉及的月份后缀列表 - -这就是 `DeviceParamTableRouter.resolveReadTableSuffixes(...)` 存在的原因。 - -关键点: - -1. 后缀由服务端计算 -2. 后缀必须强校验为 6 位数字 -3. 不能接受前端直接传表名 - -#### 2.8.5 Oracle 11 / 19 兼容性 - -本方案默认兼容: - -1. 测试环境 Oracle 11 -2. 生产环境 Oracle 19 - -因此设计上遵循: - -1. MyBatis XML 和触发器尽量使用 Oracle 11 能运行的语法 -2. 动态 SQL 与夜间包不要依赖过新的语法糖 -3. `DBMS_SCHEDULER` 用标准方式创建 job - -#### 2.8.6 `recordId` 为什么统一改成 `String` - -原因: - -1. 自动采集月分表的 `record_id` 本身就是字符串业务标识 -2. 如果 OLD 单表继续在 Java 里保留 `Long recordId`,会出现双语义主键 -3. 为了统一领域模型,Java 侧改成 `String recordId` -4. OLD 单表仍可在数据库层用数值序列,Java 侧通过 `TO_CHAR` 接收 - -#### 2.8.7 为什么 Board4 读 RT/DAY 而不是读明细 - -原因: - -1. `board4` 是高刷大屏 -2. 频繁刷明细 + 窗口函数对数据库压力大 -3. 读 RT/DAY 的复杂度更低 -4. 把计算压力前移到写入时和夜间批处理,更符合这个场景 - -#### 2.8.8 为什么 Board4 状态统计要复用 board1 - -原因: - -1. `board4/index.vue` 的 `runningData` 实际来自 `aucma-base` 的状态接口 -2. `board1/index.vue` 也依赖同一组状态接口 -3. 如果两边口径不统一,会出现: - - `board1` 显示运行 - - `board4` 显示为空/停机 -4. 所以状态统计必须统一复用双源三色灯查询 - -#### 2.8.9 为什么切换后的 OLD 历史不再回看 - -这是业务定义,不是技术限制。 - -确定规则后,系统才有清晰的一致性边界: - -1. OLD 阶段查旧表 -2. 自动采集阶段查月分表 -3. 切换后不做跨源拼接历史补算 - -好处: - -1. 实现简单 -2. 口径清晰 -3. 性能与维护压力更低 - -代价: - -1. 某些跨切换周期报表不连续 - -业务已经接受这个代价,所以方案按此执行。 - -### 2.9 方案的边界与局限 - -即使当前方案已经是可运行的最优实现,仍要明确几个边界: - -1. 触发器只对新插入数据生效,不会自动回补历史数据 -2. 如果当日数据是在触发器建立前写入的,RT 当天会出现缺口 -3. 夜间 job 没跑之前,DAY 表不会有历史沉淀 -4. 当前“后台普通 CRUD 按 recordId 精准反查月分表”的能力并未作为本轮主闭环重点 -5. 自动建分表包依赖数据库具备对应 DDL 权限与表空间配额 +4. 设备去掉 `OLD-` 前缀后,视为已进入自动采集链路 +5. `yearTotal` 固定沿用 `BASE_ORDERINFO` 工单口径 +6. `selectDeviceProductionAnalysis` 固定展示“今日增量” +7. `BASE_DEVICE_PARAM_VAL` 固定承载 OLD 设备人工录入 +8. `BASE_DEVICE_PARAM_VAL_YYYYMM` 固定承载自动采集数据 +9. 两类表长期并存 +10. 切换后的 OLD 历史数据保留,但后续查询不再回看 +11. 有效设备口径固定为 `BASE_DEVICELEDGER.IS_FLAG = 1` + +### 2.3 实际情况与异常 + +真实运行时存在以下异常与边界: + +1. `生产计数-当前日期生产总数` 可能跨天不清零 +2. `机台状态-实际产出数量` 可能被误操作清零 +3. 采样频率不能完整捕捉“瞬时+1后又清零”的峰值 +4. 不同设备的异常发生频率和时间点不同 +5. 自动采集是高频写入 +6. PDA/OLD 写入是低频、人工驱动 + +### 2.4 本次需求 + +本次最终需求可归纳为: + +1. `Board4.dayTotal` 输出当日累计产量 +2. `Board4.monthTotal` 输出当月累计产量 +3. `Board4.deviceProductionList` 输出每台设备今日增量 +4. `board1` 与 `board4` 状态统计口径一致 +5. `trace / SPC / latest` 查询兼容 OLD 单表 + 自动采集月分表 +6. 自动采集分表采用: + `BASE_DEVICE_PARAM_VAL_YYYYMM` +7. 月分表创建由 **C# 采集程序** 保证 +8. 数据库不再承担月底自动建月分表职责 --- ## 3. 总体架构 -### 3.1 改造后的四层结构 +### 3.1 四层结构 ```text 人工录入层:BASE_DEVICE_PARAM_VAL @@ -346,54 +105,40 @@ delta-sum 是监控系统处理 counter 类时间序列的通用做法。 职责: 1. OLD 设备 PDA 手工录入 -2. OLD 设备三色灯状态写入 -3. OLD 设备参数历史追溯原始明细 - -特点: - -1. 单表 -2. 继续使用序列主键 -3. Java 侧 `recordId` 统一按 `String` 接收 +2. OLD 设备三色灯参数写入 +3. OLD 参数历史明细保存 #### 2. 自动采集层 `BASE_DEVICE_PARAM_VAL_YYYYMM` 职责: -1. 自动采集设备的原始采集明细 +1. 保存自动采集设备原始明细 2. 按月物理分表 -特点: +职责归属: -1. 表名规则为: - -```text -BASE_DEVICE_PARAM_VAL_202603 -BASE_DEVICE_PARAM_VAL_202604 -... -``` - -2. `record_id` 由自动采集侧传入字符串业务唯一标识 -3. 每月一个物理表 +1. **分表创建由 C# 采集程序负责** +2. 当前数据库不再保留自动建分表包 #### 3. 实时累计层 `RT_DAILY_PROD_STATE` 职责: -1. 保存“每台设备当天当前已累计的产量” +1. 保存每台设备当天当前累计产量 2. 承担 `Board4.dayTotal` 3. 承担 `Board4.deviceProductionList` -4. 承担 `Board4.monthTotal` 中“当天/未入DAY部分” +4. 承担 `Board4.monthTotal` 中未入 DAY 的部分 #### 4. 日汇总层 `DEVICE_DAILY_PRODUCTION` 职责: -1. 保存“每台设备每天最终日产量” -2. 承担 `Board4.monthTotal` 中“历史天数据” +1. 保存历史日产量权威结果 +2. 承担 `Board4.monthTotal` 的历史部分 --- -## 4. 主统计口径与核心算法 +## 4. 主统计口径与算法 ### 4.1 主统计口径 @@ -405,9 +150,9 @@ PARAM_NAME = '机台状态-实际产出数量' 原因: -1. 它更接近累积计数器 -2. 比 `生产计数-当前日期生产总数` 更适合做 delta-sum -3. 对跨天不清零问题天然更稳 +1. 更接近累积计数器 +2. 更适合 `delta-sum` +3. 比 `生产计数-当前日期生产总数` 少一层“应清零但未清零”的业务歧义 ### 4.2 核心算法:delta-sum @@ -417,17 +162,28 @@ PARAM_NAME = '机台状态-实际产出数量' 2. `newVal = lastVal`:`delta = 0` 3. `newVal < lastVal`:`delta = newVal`,并 `reset_count + 1` -业务解释: +解释: -1. 计数器正常递增时,新增量就是差值 -2. 相同值表示没有新增产量 -3. 计数器回退时,当前值代表重置后的新增量 +1. 正常递增时,新增量就是差值 +2. 相同值说明没有新增产量 +3. 回退说明计数器重置,当前值视为重置后的新增量 + +### 4.3 为什么不用高频实时扫明细 + +原因: + +1. `board4` 是高刷大屏 +2. 如果每次刷新都扫明细做窗口函数计算,数据库压力会显著上升 +3. 更合适的方式是: + - 写入时增量维护 RT + - 夜间把 RT 搬迁到 DAY + - 页面查 RT / DAY --- -## 5. 数据库对象说明 +## 5. 数据库对象 -## 5.1 `RT_DAILY_PROD_STATE` +### 5.1 `RT_DAILY_PROD_STATE` 文件位置: @@ -437,43 +193,12 @@ zs_aucma-mes-back/aucma-base/src/main/resources/rt_daily_prod_state.sql 作用: -1. 保存设备当日实时产量状态 -2. 为 `Board4.dayTotal` -3. 为 `Board4.deviceProductionList` -4. 为 `Board4.monthTotal` 的未汇总部分提供来源 +1. 保存设备当日实时累计产量 +2. 供 `Board4.dayTotal` +3. 供 `Board4.deviceProductionList` +4. 供 `Board4.monthTotal` 当天部分 -关键表结构: - -```sql -CREATE TABLE RT_DAILY_PROD_STATE ( - PROD_DATE DATE NOT NULL, - DEVICE_CODE VARCHAR2(64) NOT NULL, - PARAM_NAME VARCHAR2(128) NOT NULL, - LAST_PARAM_VAL NUMBER(18,4) DEFAULT 0 NOT NULL, - CURRENT_TOTAL NUMBER(18,4) DEFAULT 0 NOT NULL, - RESET_COUNT NUMBER(10) DEFAULT 0 NOT NULL, - DIRTY_FLAG NUMBER(1) DEFAULT 0 NOT NULL, - LAST_COLLECT_TIME DATE, - UPDATE_TIME DATE DEFAULT SYSDATE NOT NULL -); -``` - -主键: - -```sql -PRIMARY KEY (PROD_DATE, DEVICE_CODE, PARAM_NAME) -``` - -说明: - -1. 一天内每设备每参数只保留一行状态 -2. `LAST_PARAM_VAL` 是最近一次采集值 -3. `CURRENT_TOTAL` 是当天累计值 -4. `RESET_COUNT` 记录当日回退次数 - ---- - -## 5.2 `DEVICE_DAILY_PRODUCTION` +### 5.2 `DEVICE_DAILY_PRODUCTION` 文件位置: @@ -483,25 +208,10 @@ zs_aucma-mes-back/aucma-base/src/main/resources/device_daily_production.sql 作用: -1. 保存设备的日产量权威结果 -2. 让 `Board4.monthTotal` 不需要全月扫明细 +1. 保存设备历史日产量 +2. 供 `Board4.monthTotal` 的历史部分 -关键表结构: - -```sql -CREATE TABLE DEVICE_DAILY_PRODUCTION ( - PROD_DATE DATE NOT NULL, - DEVICE_CODE VARCHAR2(64) NOT NULL, - PARAM_NAME VARCHAR2(128) NOT NULL, - DAILY_PROD NUMBER(18,4) DEFAULT 0 NOT NULL, - RESET_COUNT NUMBER(10) DEFAULT 0 NOT NULL, - CALC_TIME DATE DEFAULT SYSDATE NOT NULL -); -``` - ---- - -## 5.3 `PKG_DEVICE_PROD_CALC` +### 5.3 `PKG_DEVICE_PROD_CALC` 文件位置: @@ -512,37 +222,9 @@ zs_aucma-mes-back/aucma-base/src/main/resources/device_prod_calc_pkg.sql 作用: 1. 夜间把昨日 RT 数据搬迁到 DAY -2. 搬迁后清除昨日 RT +2. 搬迁后清理昨日 RT -核心过程: - -```sql -PROCEDURE FLUSH_RT_TO_DAY( - P_PROD_DATE IN DATE DEFAULT TRUNC(SYSDATE) - 1, - P_PARAM_NAME IN VARCHAR2 DEFAULT '机台状态-实际产出数量' -) -``` - -核心逻辑: - -```sql -MERGE INTO DEVICE_DAILY_PRODUCTION D -USING ( - SELECT PROD_DATE, DEVICE_CODE, PARAM_NAME, CURRENT_TOTAL, RESET_COUNT - FROM RT_DAILY_PROD_STATE - WHERE PROD_DATE = V_TARGET_DATE - AND PARAM_NAME = P_PARAM_NAME -) S -... - -DELETE FROM RT_DAILY_PROD_STATE - WHERE PROD_DATE = V_TARGET_DATE - AND PARAM_NAME = P_PARAM_NAME; -``` - ---- - -## 5.4 `TRG_BDPV_202603_RT` +### 5.4 `TRG_BDPV_202603_RT` 文件位置: @@ -552,43 +234,10 @@ zs_aucma-mes-back/aucma-base/src/main/resources/base_device_param_val_202603_tri 作用: -1. 自动采集分表新增数据后,自动更新 `RT_DAILY_PROD_STATE` +1. 对 `BASE_DEVICE_PARAM_VAL_202603` 新插入的自动采集数据执行 delta-sum +2. 自动更新 `RT_DAILY_PROD_STATE` -当前版本针对: - -```text -BASE_DEVICE_PARAM_VAL_202603 -``` - -核心逻辑: - -```sql -IF :NEW.PARAM_NAME <> '机台状态-实际产出数量' THEN - RETURN; -END IF; - -IF :NEW.DEVICE_CODE LIKE 'OLD-%' THEN - RETURN; -END IF; - -V_NEW_VAL := TO_NUMBER(:NEW.PARAM_VALUE); -V_PROD_DATE := TRUNC(:NEW.COLLECT_TIME); -``` - -然后: - -1. 先查 RT 是否已有当日行 -2. 有则更新 -3. 无则插入基准行 - -注意: - -1. 触发器只处理**新插入**的数据 -2. 对触发器创建前已存在的历史数据**不会自动回补** - ---- - -## 5.5 `JOB_FLUSH_RT_TO_DAY` +### 5.5 `JOB_FLUSH_RT_TO_DAY` 文件位置: @@ -600,199 +249,36 @@ zs_aucma-mes-back/aucma-base/src/main/resources/device_prod_calc_scheduler_job.s 1. 每天 00:10 自动执行 `PKG_DEVICE_PROD_CALC.FLUSH_RT_TO_DAY` -关键 SQL: +### 5.6 数据库不再负责的对象 -```sql -DBMS_SCHEDULER.CREATE_JOB( - JOB_NAME => 'JOB_FLUSH_RT_TO_DAY', - JOB_TYPE => 'PLSQL_BLOCK', - JOB_ACTION => 'BEGIN PKG_DEVICE_PROD_CALC.FLUSH_RT_TO_DAY(TRUNC(SYSDATE) - 1, ''机台状态-实际产出数量''); END;', - REPEAT_INTERVAL => 'FREQ=DAILY;BYHOUR=0;BYMINUTE=10;BYSECOND=0', - ENABLED => FALSE -); -``` +当前已经明确取消数据库自动创建月分表/触发器方案。 + +月分表新建职责改为由 C# 采集程序负责。 --- -## 5.6 `PKG_DEVICE_PARAM_PARTITION` - -文件位置: +## 6. 自动采集设备端到端链路 ```text -zs_aucma-mes-back/aucma-base/src/main/resources/device_param_partition_auto_pkg.sql -``` - -作用: - -1. 自动创建下月分表 -2. 自动创建下月索引 -3. 自动创建下月触发器 - -核心过程: - -```sql -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') -) -``` - -核心逻辑: - -1. 校验后缀必须是 6 位数字 -2. 用当前月表克隆下月表 -3. 为下月表创建索引 -4. 为下月表创建 RT 触发器 - ---- - -## 5.7 `JOB_ENSURE_DEVICE_PARAM_PARTITION` - -文件位置: - -```text -zs_aucma-mes-back/aucma-base/src/main/resources/device_param_partition_auto_scheduler_job.sql -``` - -作用: - -1. 每月月底自动执行 `PKG_DEVICE_PARAM_PARTITION.ENSURE_MONTH_TABLE_AND_TRIGGER` - -关键 SQL: - -```sql -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;', - REPEAT_INTERVAL => 'FREQ=MONTHLY;BYMONTHDAY=28,29,30,31;BYHOUR=23;BYMINUTE=55;BYSECOND=0', - ENABLED => TRUE -); +C# 采集程序 + -> 创建/确保当前月分表 BASE_DEVICE_PARAM_VAL_YYYYMM 存在 + -> 写入 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 + -> Board4 查询层读取 ``` 说明: -1. 28/29/30/31 都尝试跑 -2. 包本身幂等,所以重复跑不会重复建 +1. C# 采集程序负责月分表存在性 +2. 数据库负责分表触发器与 RT / DAY 累计 --- -## 6. 自动采集设备完整链路 - -完整链路如下: - -```text -自动采集 - -> BASE_DEVICE_PARAM_VAL_202603 - -> TRG_BDPV_202603_RT - -> RT_DAILY_PROD_STATE - -> JOB_FLUSH_RT_TO_DAY - -> DEVICE_DAILY_PRODUCTION - -> Board4Mapper.xml - -> Board4ServiceImpl.java - -> Board4Controller.java - -> board4/index.vue -``` - -### 6.1 自动采集入月分表 - -月分表规则由: - -文件: - -```text -zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/support/DeviceParamTableRouter.java -``` - -关键代码: - -```java -private static final DateTimeFormatter YM_FORMATTER = DateTimeFormatter.ofPattern("yyyyMM"); -private static final Pattern SUFFIX_PATTERN = Pattern.compile("^\\d{6}$"); -``` - -写入表名计算: - -```java -String tableName = deviceParamTableRouter.resolveWriteTable( - baseDeviceParamVal.getDeviceCode(), baseDeviceParamVal.getCollectTime()); -``` - -如果是自动采集设备,结果会是: - -```text -BASE_DEVICE_PARAM_VAL_202603 -``` - -### 6.2 触发器更新 RT - -自动采集数据写入后: - -1. `TRG_BDPV_202603_RT` 自动触发 -2. 只处理: - -```text -PARAM_NAME = '机台状态-实际产出数量' -``` - -3. 通过 delta-sum 更新: - -```text -RT_DAILY_PROD_STATE -``` - -### 6.3 Board4 读实时表 - -文件: - -```text -zs_aucma-mes-back/aucma-report/src/main/resources/mapper/report/Board4Mapper.xml -``` - -#### `dayTotal` - -```xml - -``` - -#### `deviceProductionList` - -```xml - -``` - -#### `monthTotal` - -```xml - -``` - ---- - -## 7. OLD 设备完整链路 - -完整链路如下: +## 7. OLD 设备端到端链路 ```text PDA @@ -802,243 +288,105 @@ PDA -> syncRtStateIfNeeded() -> RtDailyProdStateServiceImpl -> RT_DAILY_PROD_STATE - -> Board4Mapper.xml - -> board4/index.vue + -> Board4 查询层读取 ``` -### 7.1 OLD 判断统一入口 +说明: -文件: +1. OLD 不走月分表触发器 +2. OLD 的 RT 同步由 Java 在写入后直接完成 + +--- + +## 8. Board4 查询链路 + +### 8.1 `dayTotal` ```text -zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/support/DeviceParamTableRouter.java +Board4Controller + -> Board4ServiceImpl.getProductionTotal() + -> Board4Mapper.selectDayProductionTotal() + -> RT_DAILY_PROD_STATE(当天) ``` -关键代码: - -```java -public boolean isOldDevice(String deviceCode) { - return deviceCode != null && deviceCode.startsWith("OLD-"); -} -``` - -### 7.2 DMS 入口 - -文件: +### 8.2 `monthTotal` ```text -zs_aucma-mes-back/aucma-dms/src/main/java/com/aucma/dms/controller/DmsMobileController.java +Board4Controller + -> Board4ServiceImpl.getProductionTotal() + -> Board4Mapper.selectMonthProductionTotal() + -> DEVICE_DAILY_PRODUCTION(本月历史已汇总部分) + + RT_DAILY_PROD_STATE(本月未入DAY部分) ``` -当 OLD 设备走 PDA 更新当日产量时,最终进入: - -```java -baseDeviceParamValService.upsertTodayParamValue(baseDeviceParamVal); -``` - -### 7.3 Java 同步更新 RT - -文件: +### 8.3 `deviceProductionList` ```text -zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/service/impl/BaseDeviceParamValServiceImpl.java -``` - -关键代码: - -```java -if ("BASE_DEVICE_PARAM_VAL".equals(tableName)) { - int rows = baseDeviceParamValMapper.insertBaseDeviceParamVal(baseDeviceParamVal); - syncRtStateIfNeeded(baseDeviceParamVal, rows); - return rows; -} -``` - -同步 RT 的核心: - -```java -if (!deviceParamTableRouter.isOldDevice(entity.getDeviceCode())) { - return; -} -if (!DAILY_OUTPUT_PARAM_NAME.equals(entity.getParamName())) { - return; -} -rtDailyProdStateService.incrementProduction( - entity.getDeviceCode(), entity.getParamName(), newVal, entity.getCollectTime()); -``` - -### 7.4 RT 累加逻辑 - -文件: - -```text -zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/service/impl/RtDailyProdStateServiceImpl.java -``` - -关键逻辑: - -```java -if (state == null) { - insertState.setLastParamVal(newVal); - insertState.setCurrentTotal(BigDecimal.ZERO); - ... -} - -if (newVal.compareTo(lastParamVal) > 0) { - delta = newVal.subtract(lastParamVal); -} else if (newVal.compareTo(lastParamVal) < 0) { - delta = newVal; - resetCount += 1; -} +Board4Controller + -> Board4ServiceImpl.getDeviceProductionList() + -> Board4Mapper.selectDeviceProductionAnalysis() + -> RT_DAILY_PROD_STATE(当天 current_total) ``` --- -## 8. 夜间搬迁完整链路 +## 9. board1 / board4 状态统计链路 + +当前状态统计统一走 `aucma-base` 双源三色灯查询。 ```text -RT_DAILY_PROD_STATE(昨日) - -> PKG_DEVICE_PROD_CALC.FLUSH_RT_TO_DAY - -> DEVICE_DAILY_PRODUCTION - -> 删除昨日 RT +BASE_DEVICE_PARAM_VAL (OLD) ++ BASE_DEVICE_PARAM_VAL_YYYYMM (自动采集) + -> BaseDeviceParamValMapper.xml 双源状态统计 + -> /baseDeviceParamVal/val/statistics + -> board1 / board4 共用 ``` -作用: +说明: -1. 把历史天数据沉淀到 DAY -2. 保持 RT 只关注最近实时状态 -3. 让 `monthTotal` 可以高效统计 +1. `board4` 状态统计不再维护独立台账口径 +2. `board1` 和 `board4` 的运行/停机/报警/未开机数据保持一致 --- -## 9. board1 / board4 状态统计完整链路 +## 10. trace / SPC / latest 链路 -这里和产量不是同一条链路,但和 `board4` 一样重要。 - -### 9.1 状态查询统一来源 - -`board1/index.vue` 和 `board4/index.vue` 的设备状态统计统一复用: - -文件: +### 10.1 latest 参数监控 ```text -zs_aucma-mes-back/aucma-base/src/main/resources/mapper/base/BaseDeviceParamValMapper.xml +val/index.vue + -> /baseDeviceParamVal/val/latest + -> BaseDeviceParamValServiceImpl.selectLatestBaseDeviceParamValList(...) + -> DeviceParamTableRouter.resolveReadTableSuffixes(...) + -> BaseDeviceParamValMapper.xml 双源合并 ``` -对应接口: +### 10.2 trace 参数追溯 ```text -/baseDeviceParamVal/val/statistics -/baseDeviceParamVal/val/deviceStatus -/baseDeviceParamVal/val/deviceStartTime +trace/index.vue + -> /baseDeviceParamVal/val/trace/list + -> BaseDeviceParamValServiceImpl.selectTraceList(...) + -> resolveReadTableSuffixes(...) + -> 多月分表 UNION ALL ``` -### 9.2 双源状态统计 SQL +### 10.3 SPC -关键片段: - -```xml -WITH merged_param AS ( - SELECT ... - FROM BASE_DEVICE_PARAM_VAL - WHERE device_code LIKE 'OLD-%' - UNION ALL - SELECT ... - FROM BASE_DEVICE_PARAM_VAL_${suffix} - WHERE device_code NOT LIKE 'OLD-%' -) +```text +SPC 页面 + -> /baseDeviceParamVal/val/spcData + -> BaseDeviceParamValServiceImpl.getSPCData(...) + -> resolveReadTableSuffixes(...) + -> 多月分表 UNION ALL + -> Java 计算 mean/stdDev/cpk/ucl/lcl ``` -这表示: - -1. OLD 状态数据取旧单表 -2. 自动采集状态数据取月分表 -3. board1 / board4 状态口径一致 - --- -## 10. trace / SPC / latest 参数监控完整链路 +## 11. 关键代码文件 -## 10.1 latest 参数监控 - -前端: - -```text -zs_aucma-mes-ui/src/views/baseDeviceParamVal/val/index.vue -``` - -调用: - -```text -/baseDeviceParamVal/val/latest -``` - -服务端: - -```text -BaseDeviceParamValServiceImpl.selectLatestBaseDeviceParamValList(...) -``` - -流程: - -1. 默认给最近 24 小时时间范围 -2. 通过 `resolveReadTableSuffixes(beginTime, endTime)` 计算跨月表后缀 -3. `BaseDeviceParamValMapper.xml` 通过 `dualSourceFrom` 合并 OLD + 月分表 - -## 10.2 trace 参数追溯 - -入口: - -```text -/baseDeviceParamVal/val/trace/list -``` - -服务端: - -```java -Map params = buildStringRangeParams(deviceCode, paramCode, startTime, endTime); -List list = baseDeviceParamValMapper.selectTraceList(params); -``` - -SQL: - -```xml - - SELECT ... - FROM BASE_DEVICE_PARAM_VAL_${suffix} - -``` - -## 10.3 SPC - -入口: - -```text -/baseDeviceParamVal/val/spcData -``` - -服务端: - -```java -List values = baseDeviceParamValMapper.selectParamHistoryValues(params); -``` - -流程: - -1. 先双源查询参数名称 -2. 再双源查询参数历史值 -3. 最后在 Java 里计算: - - mean - - stdDev - - cpk - - ucl - - lcl - ---- - -## 11. 本次所有核心代码文件与文件位置 - -### 11.1 后端 Java 文件 +### 11.1 Java 文件 1. `zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/domain/BaseDeviceParamVal.java` 2. `zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/support/DeviceParamTableRouter.java` @@ -1066,34 +414,30 @@ List values = baseDeviceParamValMapper.selectParamHistoryValues(params); 3. `zs_aucma-mes-back/aucma-base/src/main/resources/device_prod_calc_pkg.sql` 4. `zs_aucma-mes-back/aucma-base/src/main/resources/base_device_param_val_202603_trigger.sql` 5. `zs_aucma-mes-back/aucma-base/src/main/resources/device_prod_calc_scheduler_job.sql` -6. `zs_aucma-mes-back/aucma-base/src/main/resources/device_param_partition_auto_pkg.sql` -7. `zs_aucma-mes-back/aucma-base/src/main/resources/device_param_partition_auto_scheduler_job.sql` --- -## 12. 脚本执行顺序 +## 12. 数据库执行顺序 -按数据库首次部署/重建顺序,必须执行: +当前最终顺序是: 1. `rt_daily_prod_state.sql` 2. `device_daily_production.sql` 3. `device_prod_calc_pkg.sql` -4. 确保当前月分表 `BASE_DEVICE_PARAM_VAL_202603` 存在 +4. 由 C# 采集程序确保当前月分表存在,例如 `BASE_DEVICE_PARAM_VAL_202603` 5. `base_device_param_val_202603_trigger.sql` 6. `device_prod_calc_scheduler_job.sql` -7. `device_param_partition_auto_pkg.sql` -8. `device_param_partition_auto_scheduler_job.sql` 说明: -1. 当前月分表本身必须存在,自动建分表包不会替你补“当前月” -2. 自动建分表包只负责“用当前月去准备下月” +1. 后续月份分表由 C# 采集程序负责创建 +2. 数据库不再负责自动建月分表 --- ## 13. 当前已知问题与排查点 -## 13.1 Board4 产量为空的典型原因 +### 13.1 Board4 产量为空 如果出现: @@ -1106,52 +450,37 @@ List values = baseDeviceParamValMapper.selectParamHistoryValues(params); 1. `BASE_DEVICE_PARAM_VAL_202603` 今日是否有 `机台状态-实际产出数量` 2. `TRG_BDPV_202603_RT` 是否启用 3. `RT_DAILY_PROD_STATE` 今日是否有数据 -4. 触发器是否是在今日数据写入之后才建的 +4. 当前月触发器是否是在今日数据写入之后才建的 -### 13.2 触发器建晚了的现象 +### 13.2 触发器建晚了 -即使: +即使当前月触发器已经启用,如果今天的数据是在触发器创建前插入的: -1. 当前月触发器已经 `ENABLED` -2. 自动采集今天也已经写了主参数 +1. 历史数据不会自动补进 RT +2. 只有后续新插入的数据才会开始累计 -如果这些数据是在触发器创建前插入的,那么: +因此可能出现: -1. 历史数据不会自动补到 `RT_DAILY_PROD_STATE` -2. 只有后续新增插入才会触发 RT 累计 - -所以会出现: - -1. `board4` 先为空 -2. 等下一批采集数据来了后开始有值 -3. 但今天的数据会偏小,因为缺了触发器创建前的那一段 +1. 等一段时间后 Board4 开始有值 +2. 但今天数据偏小,因为缺失触发器创建前那一段 --- ## 14. 当前最终结论 -本次改造从数据库存储过程到程序查询的完整链路已经打通: +当前最终架构闭环是: -1. 自动采集设备: - - 写 `BASE_DEVICE_PARAM_VAL_YYYYMM` - - 由分表触发器更新 `RT_DAILY_PROD_STATE` -2. OLD 设备: - - 写 `BASE_DEVICE_PARAM_VAL` - - 由 Java 服务同步更新 `RT_DAILY_PROD_STATE` -3. 夜间: - - `JOB_FLUSH_RT_TO_DAY` - - 调用 `PKG_DEVICE_PROD_CALC` - - 将 RT 搬迁到 DAY -4. 月底: - - `JOB_ENSURE_DEVICE_PARAM_PARTITION` - - 调用 `PKG_DEVICE_PARAM_PARTITION` - - 自动创建下月分表与下月触发器 -5. 程序查询层: - - `Board4` 查 RT / DAY - - `board1` 状态查询走双源三色灯 - - `trace / SPC / latest` 走双源跨月分表查询 +```text +C# 采集程序负责分表创建 + + +数据库负责当前月触发器、RT、DAY、夜间汇总 + + +Java 负责 OLD -> RT 同步与双源查询 + + +Board4 / board1 / trace / SPC / latest 统一走新口径 +``` -换句话说,当前系统已经不是“单表直接统计”,而是: +也就是说,当前系统已经不是“单表直接查产量”,而是: ```text 原始明细层 @@ -1161,4 +490,4 @@ List values = baseDeviceParamValMapper.selectParamHistoryValues(params); -> 前端展示层 ``` -这就是本次从数据库存储过程到程序查询的完整闭环。最自律帅气聪明的臧辰浩 +这就是当前最终版的完整实现。最自律帅气聪明的臧辰浩 diff --git a/aucma-base/src/main/resources/device_param_partition_auto_pkg.sql b/aucma-base/src/main/resources/device_param_partition_auto_pkg.sql deleted file mode 100644 index e2a9192..0000000 --- a/aucma-base/src/main/resources/device_param_partition_auto_pkg.sql +++ /dev/null @@ -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; -/ diff --git a/aucma-base/src/main/resources/device_param_partition_auto_scheduler_job.sql b/aucma-base/src/main/resources/device_param_partition_auto_scheduler_job.sql deleted file mode 100644 index 94dd10e..0000000 --- a/aucma-base/src/main/resources/device_param_partition_auto_scheduler_job.sql +++ /dev/null @@ -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';