docs(base): 更新数据库月分表自动化方案文档

master
zangch@mesnac.com 2 weeks ago
parent a295ede66d
commit b53c86d141

@ -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# 采集程序只保留“按采集时间自动路由写入”
```

@ -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 同步与双源查询
+

@ -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;
/

@ -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';

@ -283,4 +283,111 @@ BEGIN
现在,这套系统从“高并发的采集”到“触发器的过滤计算”,你已经完全看透了底层的逻辑!
接下来,你是想继续深挖 **Java 那边是怎么处理 OLD 设备和双源查询的**,还是想让我直接把 **C# 在月底跨月时,用来自动建表和建触发器的完整 SQL 模板** 给你安排上呢?
接下来,你是想继续深挖 **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 个步骤**呢?

Loading…
Cancel
Save