From b53c86d141d161e0a639022bf583009d105b16e1 Mon Sep 17 00:00:00 2001 From: "zangch@mesnac.com" Date: Tue, 17 Mar 2026 09:58:49 +0800 Subject: [PATCH] =?UTF-8?q?docs(base):=20=E6=9B=B4=E6=96=B0=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E6=9C=88=E5=88=86=E8=A1=A8=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=8C=96=E6=96=B9=E6=A1=88=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 | 98 +++++---- ...d4_end_to_end_full_implementation_guide.md | 64 ++++-- .../device_param_monthly_auto_pkg.sql | 193 ++++++++++++++++++ ...evice_param_monthly_auto_scheduler_job.sql | 57 ++++++ aucma-base/src/main/resources/梳理.md | 109 +++++++++- 5 files changed, 460 insertions(+), 61 deletions(-) create mode 100644 aucma-base/src/main/resources/device_param_monthly_auto_pkg.sql create mode 100644 aucma-base/src/main/resources/device_param_monthly_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 f830bc1..6ae93b3 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 @@ -9,11 +9,14 @@ 1. 数据库负责: - `RT_DAILY_PROD_STATE` - `DEVICE_DAILY_PRODUCTION` - - 当前月分表触发器 + - 月分表 `BASE_DEVICE_PARAM_VAL_YYYYMM` + - 月分表触发器 `TRG_BDPV_YYYYMM_RT` + - `PKG_DEVICE_PARAM_MONTHLY` - `PKG_DEVICE_PROD_CALC` + - `JOB_AUTO_PREPARE_MONTHLY` - `JOB_FLUSH_RT_TO_DAY` -2. **月分表新建职责不再由数据库自动化完成** -3. **新建月分表由 C# 采集程序负责实现** +2. **数据库在每月 26 日提前创建下个月分表与触发器** +3. **C# 采集程序只负责按采集时间自动路由写入** --- @@ -23,18 +26,18 @@ 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 +3. 在每月 26 日提前创建下个月分表 `BASE_DEVICE_PARAM_VAL_YYYYMM` +4. 在每月 26 日提前创建下个月触发器 `TRG_BDPV_YYYYMM_RT` +5. 通过月分表触发器把自动采集明细同步到 RT +6. 通过 `PKG_DEVICE_PROD_CALC` 把昨日 RT 搬迁到 DAY +7. 通过 `JOB_FLUSH_RT_TO_DAY` 每天 00:10 自动执行 RT -> DAY ### 2.2 C# 采集程序负责的内容 -1. 创建当前月/后续月份的采集分表: - - `BASE_DEVICE_PARAM_VAL_202603` - - `BASE_DEVICE_PARAM_VAL_202604` - - ... -2. 保证采集入库时目标月分表已经存在 -3. 新建月分表对应触发器的部署流程由业务发布过程配套完成 +1. 使用 sqlSugar 按 `collect_time` 自动路由到目标月分表 +2. 仅负责向已存在的 `BASE_DEVICE_PARAM_VAL_YYYYMM` 写入采集数据 +3. 不再负责建表 +4. 不再负责补触发器 ### 2.3 Java 负责的内容 @@ -72,20 +75,20 @@ 3. RT / DAY 基础表已经存在 4. 夜间 RT->DAY 搬迁包和 job 已经存在 -### 3.3 自动建分表方案废弃 +### 3.3 自动建分表方案最终确认 -本次过程中曾经设计过数据库自动创建下月分表/触发器的方案,但当前最终决策为: +本次最终决策为: -1. 删除数据库自动建月分表脚本 -2. 取消数据库侧月底自动建分表/触发器方案 -3. 后续月份新建分表改为由 **C# 采集程序** 负责 +1. 由数据库在每月 26 日提前创建下个月分表 +2. 由数据库在每月 26 日提前创建下个月触发器 +3. C# 采集程序仅按采集时间自动路由到目标月分表写入 原因: -1. 分表本身是采集入库前提 -2. 由采集程序自己保障分表存在更符合职责边界 -3. 可避免数据库额外 DDL 权限复杂度 -4. 可避免“数据库月底 job 未执行,但采集程序已切月写入”的风险 +1. 分表和触发器是自动采集链路的数据库基础设施,应由数据库统一托管 +2. 月底前预建下个月对象,能规避月初切月瞬间的建表/补触发器空窗 +3. sqlSugar 已经能按 `collect_time` 自动路由,C# 没必要再承担 DDL 职责 +4. 统一在数据库侧建表和建触发器,职责边界更稳定 --- @@ -105,10 +108,13 @@ 1. `HAIWEI.PKG_DEVICE_PROD_CALC` 2. `HAIWEI.PKG_DEVICE_PROD_CALC BODY` +3. `HAIWEI.PKG_DEVICE_PARAM_MONTHLY` +4. `HAIWEI.PKG_DEVICE_PARAM_MONTHLY BODY` ### 4.4 调度任务 -1. `HAIWEI.JOB_FLUSH_RT_TO_DAY` +1. `HAIWEI.JOB_AUTO_PREPARE_MONTHLY` +2. `HAIWEI.JOB_FLUSH_RT_TO_DAY` --- @@ -118,7 +124,7 @@ ```text C# 采集程序 - -> 确保 BASE_DEVICE_PARAM_VAL_202603 已存在 + -> sqlSugar 按 collect_time 自动路由 -> 插入 BASE_DEVICE_PARAM_VAL_202603 -> TRG_BDPV_202603_RT -> RT_DAILY_PROD_STATE @@ -164,18 +170,22 @@ Board4.deviceProductionList 3. `device_prod_calc_pkg.sql` -### 第三步:确保当前月分表存在 +### 第三步:创建月分表自动化包 -4. 由 **C# 采集程序** 或其配套建表脚本创建当前月分表,例如: - `BASE_DEVICE_PARAM_VAL_202603` +4. `device_param_monthly_auto_pkg.sql` -### 第四步:部署当前月触发器 +### 第四步:首次补齐当前月对象 -5. `base_device_param_val_202603_trigger.sql` +5. 手工执行: + `BEGIN PKG_DEVICE_PARAM_MONTHLY.PREPARE_MONTH(TO_CHAR(TRUNC(SYSDATE, 'MM'), 'YYYYMM')); END; /` -### 第五步:创建夜间 RT->DAY job +### 第五步:创建月分表自动化 job -6. `device_prod_calc_scheduler_job.sql` +6. `device_param_monthly_auto_scheduler_job.sql` + +### 第六步:创建夜间 RT->DAY job + +7. `device_prod_calc_scheduler_job.sql` --- @@ -187,19 +197,22 @@ Board4.deviceProductionList zs_aucma-mes-back/aucma-base/src/main/resources ``` -的数据库脚本有 5 个: +的数据库脚本有 7 个: 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` +4. `device_param_monthly_auto_pkg.sql` +5. `device_param_monthly_auto_scheduler_job.sql` +6. `base_device_param_val_202603_trigger.sql` +7. `device_prod_calc_scheduler_job.sql` 说明: 1. 这些脚本是数据库恢复必需的脚本 -2. 不再保留“自动建下月分表/触发器”的数据库脚本 -3. 后续月份分表由 C# 采集程序负责 +2. 月分表与触发器改为由数据库自动化统一托管 +3. C# 采集程序通过 sqlSugar 按 `collect_time` 自动路由写入 +4. 数据库在每月 26 日提前创建下个月分表与触发器 --- @@ -475,15 +488,16 @@ END; 当前最终数据库方案是: -1. 当前月分表由 C# 采集程序创建 -2. 当前月分表触发器由数据库脚本部署 -3. 自动采集明细通过触发器实时进入 RT -4. OLD 数据由 Java 同步进入 RT -5. 夜间由 `JOB_FLUSH_RT_TO_DAY` 驱动 `PKG_DEVICE_PROD_CALC` 搬迁到 DAY +1. 数据库在每月 26 日提前创建下个月分表 `BASE_DEVICE_PARAM_VAL_YYYYMM` +2. 数据库在每月 26 日提前创建下个月触发器 `TRG_BDPV_YYYYMM_RT` +3. C# 采集程序通过 sqlSugar 按 `collect_time` 自动路由写入目标月分表 +4. 自动采集明细通过触发器实时进入 RT +5. OLD 数据由 Java 同步进入 RT +6. 夜间由 `JOB_FLUSH_RT_TO_DAY` 驱动 `PKG_DEVICE_PROD_CALC` 搬迁到 DAY 换句话说: ```text -数据库侧保留“实时累计 + 夜间汇总” -分表创建职责回归 C# 采集程序 +数据库侧保留“月分表预建 + 实时累计 + 夜间汇总” +C# 采集程序只保留“按采集时间自动路由写入” ``` 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 b2e4aa0..c7c8031 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 @@ -6,9 +6,12 @@ 本文档重点强调的最终职责边界为: -1. **月分表新建由 C# 采集程序负责** +1. **月分表与触发器由数据库在每月 26 日提前创建** 2. 数据库负责: - - 当前月分表触发器 + - 月分表 `BASE_DEVICE_PARAM_VAL_YYYYMM` + - 月分表触发器 `TRG_BDPV_YYYYMM_RT` + - `PKG_DEVICE_PARAM_MONTHLY` + - `JOB_AUTO_PREPARE_MONTHLY` - `RT_DAILY_PROD_STATE` - `DEVICE_DAILY_PRODUCTION` - 夜间 `RT -> DAY` @@ -17,6 +20,7 @@ - OLD 写入后同步 RT - 双源查询 4. 前端保持原有接口不变,通过后端新口径取数 +5. C# 采集程序通过 sqlSugar 按 `collect_time` 自动路由写入,不再负责建表或补触发器 --- @@ -82,8 +86,10 @@ 5. `trace / SPC / latest` 查询兼容 OLD 单表 + 自动采集月分表 6. 自动采集分表采用: `BASE_DEVICE_PARAM_VAL_YYYYMM` -7. 月分表创建由 **C# 采集程序** 保证 -8. 数据库不再承担月底自动建月分表职责 +7. 数据库在每月 26 日提前创建下个月: + `BASE_DEVICE_PARAM_VAL_YYYYMM` + `TRG_BDPV_YYYYMM_RT` +8. C# 采集程序通过 sqlSugar 按 `collect_time` 自动路由写入 --- @@ -117,8 +123,8 @@ 职责归属: -1. **分表创建由 C# 采集程序负责** -2. 当前数据库不再保留自动建分表包 +1. **数据库在每月 26 日提前创建下个月物理分表** +2. **C# 采集程序通过 sqlSugar 按 `collect_time` 自动路由写入** #### 3. 实时累计层 `RT_DAILY_PROD_STATE` @@ -249,11 +255,31 @@ 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` -### 5.6 数据库不再负责的对象 +### 5.6 `PKG_DEVICE_PARAM_MONTHLY` -当前已经明确取消数据库自动创建月分表/触发器方案。 +文件位置: -月分表新建职责改为由 C# 采集程序负责。 +```text +zs_aucma-mes-back/aucma-base/src/main/resources/device_param_monthly_auto_pkg.sql +``` + +作用: + +1. 在每月 26 日提前创建下个月分表 `BASE_DEVICE_PARAM_VAL_YYYYMM` +2. 在每月 26 日提前创建下个月触发器 `TRG_BDPV_YYYYMM_RT` +3. 提供手工补齐当前月对象的入口 + +### 5.7 `JOB_AUTO_PREPARE_MONTHLY` + +文件位置: + +```text +zs_aucma-mes-back/aucma-base/src/main/resources/device_param_monthly_auto_scheduler_job.sql +``` + +作用: + +1. 每月 26 日 02:00 自动执行 `PKG_DEVICE_PARAM_MONTHLY.AUTO_PREPARE_NEXT_MONTH` --- @@ -261,7 +287,7 @@ zs_aucma-mes-back/aucma-base/src/main/resources/device_prod_calc_scheduler_job.s ```text C# 采集程序 - -> 创建/确保当前月分表 BASE_DEVICE_PARAM_VAL_YYYYMM 存在 + -> sqlSugar 按 collect_time 自动路由到 BASE_DEVICE_PARAM_VAL_YYYYMM -> 写入 BASE_DEVICE_PARAM_VAL_202603 -> 触发 TRG_BDPV_202603_RT -> 更新 RT_DAILY_PROD_STATE @@ -273,8 +299,8 @@ C# 采集程序 说明: -1. C# 采集程序负责月分表存在性 -2. 数据库负责分表触发器与 RT / DAY 累计 +1. 数据库在每月 26 日提前创建下个月分表与触发器 +2. C# 采集程序通过 sqlSugar 按 `collect_time` 自动路由写入 --- @@ -424,14 +450,16 @@ SPC 页面 1. `rt_daily_prod_state.sql` 2. `device_daily_production.sql` 3. `device_prod_calc_pkg.sql` -4. 由 C# 采集程序确保当前月分表存在,例如 `BASE_DEVICE_PARAM_VAL_202603` -5. `base_device_param_val_202603_trigger.sql` -6. `device_prod_calc_scheduler_job.sql` +4. `device_param_monthly_auto_pkg.sql` +5. 手工执行 `PKG_DEVICE_PARAM_MONTHLY.PREPARE_MONTH(TO_CHAR(TRUNC(SYSDATE, 'MM'), 'YYYYMM'))` +6. `device_param_monthly_auto_scheduler_job.sql` +7. `device_prod_calc_scheduler_job.sql` 说明: -1. 后续月份分表由 C# 采集程序负责创建 -2. 数据库不再负责自动建月分表 +1. 后续月份分表与触发器由数据库在每月 26 日提前创建 +2. C# 采集程序通过 sqlSugar 按 `collect_time` 自动路由写入 +3. 数据库继续负责自动建月分表 --- @@ -473,7 +501,7 @@ SPC 页面 ```text C# 采集程序负责分表创建 + -数据库负责当前月触发器、RT、DAY、夜间汇总 +数据库负责当前月触发器、新月触发器自动补建、RT、DAY、夜间汇总 + Java 负责 OLD -> RT 同步与双源查询 + diff --git a/aucma-base/src/main/resources/device_param_monthly_auto_pkg.sql b/aucma-base/src/main/resources/device_param_monthly_auto_pkg.sql new file mode 100644 index 0000000..f9b0bd8 --- /dev/null +++ b/aucma-base/src/main/resources/device_param_monthly_auto_pkg.sql @@ -0,0 +1,193 @@ +-- 自动创建设备参数月分表与 RT 触发器 +-- 兼容:Oracle 11 / Oracle 19 +-- 说明: +-- 1. 数据库在每月 26 日提前创建下个月物理分表与触发器,避免跨月首日出现写入空窗。 +-- 2. C# 采集程序使用 sqlSugar 按 collect_time 自动路由到对应月分表,不再负责建表/补触发器。 +-- 3. 自动采集月分表结构必须与 BASE_DEVICE_PARAM_VAL_202603.sql 保持一致,所以这里显式按该表结构建表。 + +CREATE OR REPLACE PACKAGE PKG_DEVICE_PARAM_MONTHLY AS + + PROCEDURE PREPARE_MONTH( + P_SUFFIX IN VARCHAR2 + ); + + PROCEDURE AUTO_PREPARE_NEXT_MONTH; + +END PKG_DEVICE_PARAM_MONTHLY; +/ + +CREATE OR REPLACE PACKAGE BODY PKG_DEVICE_PARAM_MONTHLY 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(-20021, '非法月份后缀: ' || NVL(P_SUFFIX, 'NULL')); + END IF; + END VALIDATE_SUFFIX; + + 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 ENSURE_MONTH_TABLE( + P_SUFFIX IN VARCHAR2 + ) IS + V_TABLE_NAME VARCHAR2(128); + V_PK_NAME VARCHAR2(128); + V_SQL CLOB; + BEGIN + V_TABLE_NAME := C_TABLE_PREFIX || P_SUFFIX; + V_PK_NAME := 'PK_' || V_TABLE_NAME || '_RECORD_ID'; + + IF TABLE_EXISTS(V_TABLE_NAME) = 0 THEN + -- 为什么显式按 202603 表建: + -- BASE_DEVICE_PARAM_VAL 主表的 RECORD_ID 是 NUMBER, + -- 但自动采集月分表使用 C# GUID,必须与现有 BASE_DEVICE_PARAM_VAL_202603 保持同构。 + V_SQL := 'CREATE TABLE ' || V_TABLE_NAME || ' (' || CHR(10) || + ' RECORD_ID VARCHAR2(32 BYTE) NOT NULL,' || CHR(10) || + ' PARAM_CODE VARCHAR2(255 BYTE) NOT NULL,' || CHR(10) || + ' DEVICE_CODE VARCHAR2(255 BYTE) NOT NULL,' || CHR(10) || + ' DEVICE_ID NUMBER NOT NULL,' || CHR(10) || + ' PARAM_NAME VARCHAR2(255 BYTE) NOT NULL,' || CHR(10) || + ' PARAM_VALUE VARCHAR2(255 BYTE) NOT NULL,' || CHR(10) || + ' COLLECT_TIME DATE NOT NULL,' || CHR(10) || + ' RECORD_TIME DATE NOT NULL,' || CHR(10) || + ' PARAM_TYPE VARCHAR2(255 BYTE) NOT NULL' || CHR(10) || + ')' || CHR(10) || + 'LOGGING' || CHR(10) || + 'NOCOMPRESS' || CHR(10) || + 'PCTFREE 10' || CHR(10) || + 'INITRANS 1' || CHR(10) || + 'STORAGE (' || CHR(10) || + ' INITIAL 65536 ' || CHR(10) || + ' NEXT 1048576 ' || CHR(10) || + ' MINEXTENTS 1' || CHR(10) || + ' MAXEXTENTS 2147483645' || CHR(10) || + ' BUFFER_POOL DEFAULT' || CHR(10) || + ')' || CHR(10) || + 'PARALLEL 1' || CHR(10) || + 'NOCACHE' || CHR(10) || + 'DISABLE ROW MOVEMENT'; + EXECUTE_DDL(V_SQL); + EXECUTE IMMEDIATE 'ALTER TABLE ' || V_TABLE_NAME || + ' ADD CONSTRAINT ' || V_PK_NAME || + ' PRIMARY KEY (RECORD_ID)'; + END IF; + END ENSURE_MONTH_TABLE; + + PROCEDURE CREATE_OR_REPLACE_TRIGGER( + P_SUFFIX IN VARCHAR2 + ) IS + V_TABLE_NAME VARCHAR2(128); + V_TRIGGER_NAME VARCHAR2(128); + V_SQL CLOB; + BEGIN + V_TABLE_NAME := C_TABLE_PREFIX || P_SUFFIX; + V_TRIGGER_NAME := C_TRIGGER_PREFIX || P_SUFFIX || '_RT'; + + V_SQL := 'CREATE OR REPLACE TRIGGER ' || V_TRIGGER_NAME || CHR(10) || + 'AFTER INSERT ON ' || V_TABLE_NAME || 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_OR_REPLACE_TRIGGER; + + PROCEDURE PREPARE_MONTH( + P_SUFFIX IN VARCHAR2 + ) IS + BEGIN + VALIDATE_SUFFIX(P_SUFFIX); + ENSURE_MONTH_TABLE(P_SUFFIX); + CREATE_OR_REPLACE_TRIGGER(P_SUFFIX); + END PREPARE_MONTH; + + PROCEDURE AUTO_PREPARE_NEXT_MONTH IS + V_NEXT_SUFFIX VARCHAR2(6); + BEGIN + -- 为什么取“下个月月初再格式化”: + -- 这样语义上明确是“目标月份后缀”,不会受当前日号影响。 + V_NEXT_SUFFIX := TO_CHAR(ADD_MONTHS(TRUNC(SYSDATE, 'MM'), 1), 'YYYYMM'); + PREPARE_MONTH(V_NEXT_SUFFIX); + END AUTO_PREPARE_NEXT_MONTH; + +END PKG_DEVICE_PARAM_MONTHLY; +/ diff --git a/aucma-base/src/main/resources/device_param_monthly_auto_scheduler_job.sql b/aucma-base/src/main/resources/device_param_monthly_auto_scheduler_job.sql new file mode 100644 index 0000000..db8d9b5 --- /dev/null +++ b/aucma-base/src/main/resources/device_param_monthly_auto_scheduler_job.sql @@ -0,0 +1,57 @@ +-- 自动创建“下个月”设备参数月分表与 RT 触发器的数据库任务 +-- 兼容:Oracle 11 / Oracle 19 +-- 说明: +-- 1. 数据库在每月 26 日凌晨 02:00 提前创建下个月物理分表和触发器。 +-- 2. C# 采集程序使用 sqlSugar 按 collect_time 自动路由写入,不再负责建表或补触发器。 +-- 3. 本脚本会顺带清理旧的“每月 1 日 00:15 补触发器”任务,避免两个 job 并存造成职责冲突。 + +BEGIN + DBMS_SCHEDULER.DROP_JOB( + JOB_NAME => 'JOB_ENSURE_DEVICE_PARAM_TRIGGER', + FORCE => TRUE + ); +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE != -27475 THEN + RAISE; + END IF; +END; +/ + +BEGIN + DBMS_SCHEDULER.DROP_JOB( + JOB_NAME => 'JOB_AUTO_PREPARE_MONTHLY', + FORCE => TRUE + ); +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE != -27475 THEN + RAISE; + END IF; +END; +/ + +BEGIN + DBMS_SCHEDULER.CREATE_JOB( + JOB_NAME => 'JOB_AUTO_PREPARE_MONTHLY', + JOB_TYPE => 'PLSQL_BLOCK', + JOB_ACTION => 'BEGIN PKG_DEVICE_PARAM_MONTHLY.AUTO_PREPARE_NEXT_MONTH; END;', + START_DATE => SYSTIMESTAMP, + REPEAT_INTERVAL => 'FREQ=MONTHLY;BYMONTHDAY=26;BYHOUR=2;BYMINUTE=0;BYSECOND=0', + ENABLED => TRUE, + AUTO_DROP => FALSE, + COMMENTS => '每月26日提前创建下个月设备参数分表与RT触发器' + ); +END; +/ + +-- 首次部署后如需立即补齐当前月,可手工执行: +-- BEGIN +-- PKG_DEVICE_PARAM_MONTHLY.PREPARE_MONTH(TO_CHAR(TRUNC(SYSDATE, ''MM''), ''YYYYMM'')); +-- END; +-- / + +-- 查看状态: +-- SELECT JOB_NAME, ENABLED, STATE, LAST_START_DATE, NEXT_RUN_DATE +-- FROM USER_SCHEDULER_JOBS +-- WHERE JOB_NAME = 'JOB_AUTO_PREPARE_MONTHLY'; diff --git a/aucma-base/src/main/resources/梳理.md b/aucma-base/src/main/resources/梳理.md index 2c132aa..b8165a7 100644 --- a/aucma-base/src/main/resources/梳理.md +++ b/aucma-base/src/main/resources/梳理.md @@ -283,4 +283,111 @@ BEGIN 现在,这套系统从“高并发的采集”到“触发器的过滤计算”,你已经完全看透了底层的逻辑! -接下来,你是想继续深挖 **Java 那边是怎么处理 OLD 设备和双源查询的**,还是想让我直接把 **C# 在月底跨月时,用来自动建表和建触发器的完整 SQL 模板** 给你安排上呢? \ No newline at end of file +接下来,你是想继续深挖 **Java 那边是怎么处理 OLD 设备和双源查询的**,还是想让我直接把 **C# 在月底跨月时,用来自动建表和建触发器的完整 SQL 模板** 给你安排上呢? + + + + + + + + + + + + + +你好!面对这么多专业的数据库表、代码和复杂的触发器,刚开始觉得像看天书一样是再正常不过的了。千万别有压力! + +如果我们把这段干巴巴的代码世界,想象成一个井然有序的**“现代数据加工厂”** ,一切就会变得异常清晰。作为菜鸟小白,我们今天不钻代码牛角尖,而是换上厂长的视角,去巡视一下这个工厂的“完整流水线”。 + +我帮你把这份长篇大论重新“嚼碎”,梳理成一份**最全面、最易懂、最接地气**的全流程大白话指南! + +--- + +### 第一阶段:建厂初衷 —— 为什么要搞这么复杂? + +以前咱们查机器的产量,简单粗暴:直接去数据库翻“最新的一条记录”。但随着工厂里设备越来越多,这招彻底不灵了,遇到了两个致命痛点: + +1. **“机器会撒谎”:** 工厂的机器计数器很不听话。有时候跨天了它不清零,有时候工人不小心按错了或者断电,它又突然清零了。这导致产量算得乱七八糟。 +2. **“大屏被挤爆”:** Board4 是一个需要高频刷新的大屏。如果你每次刷新,都让数据库去海量的原始数据里现做加减法,数据库的 CPU 会直接拉满,甚至死机卡瘫痪。 + +为了彻底解决“算不准”和“查得慢”的问题,咱们把系统进行了全面升级,划分出了 **“四大仓库”**。 + +--- + +### 第二阶段:工厂基建 —— 四大核心数据仓库 + +整个系统的运转,就是数据在这四个仓库里流转的过程 : + +1. **人工录入层(`BASE_DEVICE_PARAM_VAL`):** + 这是给车间里那 5 台不支持自动联网的“老古董设备(OLD设备)”准备的。工人拿着 PDA 扫码录入的数据,全都堆在这一个大仓库里。 +2. **自动采集层(`BASE_DEVICE_PARAM_VAL_YYYYMM`):** + 这是给新一代自动设备准备的。因为它们每时每刻都在发数据,数据量极其庞大,所以采用了**“按月建表(分表)”**的策略(比如 3 月的数据存在 202603 表里)。 + * **✨ 自动化黑科技:** 咱们不需要人工去建表。每个月的 **26 号凌晨 2:00**,数据库会自动派出机器人(定时任务),把下个月的空表和配套设施提前建好,铺好路等着数据进来。 +3. **实时累计层(`RT_DAILY_PROD_STATE`):** + 这是咱们的**“今日计分板”**。它不关心明细,只记录每台设备**今天此时此刻**的总产量是多少。 +4. **日汇总层(`DEVICE_DAILY_PRODUCTION`):** + 这是咱们的**“历史档案馆”**。它保存过去每一天已经盖棺定论的权威总产量。 + +--- + +### 第三阶段:数据入库 —— 两种设备的“计分”路线 + +数据是怎么流向“今日计分板”的呢?新老设备有两套不同的流水线: + +#### 路线 A:老设备(人工录入) +老设备的流程很简单。工人用 PDA 录入数据后,后端的 **Java 程序**就像个贴心的记账员,在把数据存进老表的同时,会顺手算出最新产量,直接写在“今日计分板”(RT 表)上。 + +#### 路线 B:新设备(全自动采集)—— 核心性能护城河! +自动设备的数据像洪水一样涌来,这里的架构设计堪称精妙: +1. **C# 程序的“10 分钟拦截”:** + C# 采集程序并没有傻乎乎地收到一条数据就往数据库写一条。它采用了**“节流”**策略,在内存里把数据攒够 **10 分钟**,然后再打包一次性发给数据库。这完美避开了高频写入导致的数据库锁死问题,性能拉满!然后,C# 只需要根据采集时间,将数据精准投递到对应的“当月分表”里即可。 +2. **数据库触发器的“安检与计分”:** + 当月分表里装了一个叫“触发器”的暗哨(比如 `TRG_BDPV_202603_RT`)。新数据一落库,触发器就开始干活。 + * **严查通行证:** 它首先化身“保安”,只对名为 `机台状态-实际产出数量` 的参数放行,温度、压力等无关数据一律无视,避免浪费计算资源。同时,它也会过滤掉以 `OLD-` 开头的老设备数据(因为 Java 已经算过了,防止重复算)。 + * **自动算分:** 安检通过后,它会自动用一套叫做 `delta-sum` 的算法,算出这 10 分钟内真实增加了多少产量,并写到“今日计分板”上。 + +--- + +### 第四阶段:防错黑科技 —— Delta-Sum 算法到底是个啥? + +机器抽风清零了怎么办?这就是触发器里 `delta-sum`(差值求和)算法大显身手的时候了 。 + +假设机器就像个一直往上涨的**水表**,触发器每次都会拿“新数字”和计分板上的“老数字”做对比: + +1. **水表正常涨(比如 100 变成 120):** + 说明一切正常,**真实增量就是差值**(120 - 100 = 20),把这 20 加到总数里。 +2. **水表突然变小了(比如 120 突然变成 5):** + 机器清零了!此时触发器极其聪明,它绝不扣减产量,而是**直接把清零后的新数字(5)当作实打实的新增产量**加进去,并且在后台拿小本本记下:这台机器今天又清零重置了 1 次。 +3. **水表没动静(比如还是 120):** + 说明停机没干活,增量记为 0。 + +**一句话总结算法:涨了就算差值,跌了就把跌完的新值当增量,最后统统累加到总数上!** 这样算,不管机器怎么清零抽风,产量绝对一分不少! + +--- + +### 第五阶段:夜间大扫除 —— 午夜搬运工 + +“今日计分板”(RT 表)每天被更新无数次,但它只管今天的事。到了明天怎么办? + +每天夜里 **00:10**,系统会自动派出一个定时搬运工(`JOB_FLUSH_RT_TO_DAY`)。 +他会把“今日计分板”里属于**昨天**的最终比分,郑重其事地抄写到“历史档案馆”(DAY 汇总表)中,永久保存。抄写完毕后,他会把计分板里昨天的数据擦得干干净净,迎接新一天的产量计分。 + +--- + +### 第六阶段:终端展现 —— 大屏快如闪电 + +经历过前面如此严密的流水线加工,现在 Board4 大屏想要展示数据,那简直是探囊取物: + +* **看今日实时产量:** 别去明细表里费劲扒拉了,直接看那张“今日计分板”(RT 表),要多快有多快。 +* **看本月总产量:** 去“历史档案馆”(DAY 表)把本月之前的汇总拿出来,加上“今日计分板”里今天的临时数据,两者一合并,瞬间出结果。 + +--- + +### 🌟 终极一句话总复习 + +**数据库月底自动盖好新仓库 ➡️ C# 收集好数据每 10 分钟存一次 ➡️ 触发器负责过滤和防错计算 ➡️ 算出今日产量存入计分板 ➡️ 每天半夜存入历史档案库 ➡️ 大屏直接拿结果展示!** + +怎么样,是不是感觉原本杂乱无章的代码瞬间变成了一个井然有序的工厂?如果你对这里面的某个环节感兴趣,你想让我给你展开讲讲**如何应对前端 trace/SPC 历史追溯页面的查询**,还是想看看**如果系统崩溃了,实施人员照着文档重装数据库的 7 个步骤**呢? +