|
|
|
|
@ -0,0 +1,254 @@
|
|
|
|
|
# Board4 2026-06-25 断网7天数据修复与开模数恢复实施方案
|
|
|
|
|
|
|
|
|
|
## 1. 背景与问题描述
|
|
|
|
|
|
|
|
|
|
### 1.1 问题场景
|
|
|
|
|
由于采集程序中断约 7 天(从 2026-06-18 断网至 2026-06-25 16:06:18 恢复),导致系统在此期间无法获取设备的原始采集数据。
|
|
|
|
|
今天 16:06:18 采集程序恢复后,系统面临以下数据异常:
|
|
|
|
|
1. **历史日产量真空**:6月19日至6月24日历史日产量 `DEVICE_DAILY_PRODUCTION` 无记录。
|
|
|
|
|
2. **今日自然日产量偏低**:今日(6月25日)16:06 之前的产量缺失,今日实时表 `RT_DAILY_PROD_STATE` 仅累计了恢复后极短时间的产量(如 YZM-01 仅有 17)。
|
|
|
|
|
3. **班次当日开模数列表全为 0**:运行今日数据补齐脚本后,日累计产量在大屏上恢复,但下方的 18 台设备“当日开模数”列表仍然显示为 0。
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 2. 核心逻辑分析
|
|
|
|
|
|
|
|
|
|
### 2.1 补数均摊逻辑(全自动计算与均摊补写 PL/SQL)
|
|
|
|
|
设备底层的 PLC 计数器在断网期间是持续物理累加的。当 6月25日 16:06 恢复采集时,第一笔采集的值 $V_{\text{first}}$ 包含了断网 7 天的物理累积值。通过与 6月18日 断网前最后一笔有效历史读数 $V_{\text{last}}$ 相减,可得到物理绝对增量:
|
|
|
|
|
$$\Delta_{\text{total}} = V_{\text{first}} - V_{\text{last}}$$
|
|
|
|
|
|
|
|
|
|
为了避免将 7 天的总产量堆积在今日导致数据畸变,系统采用**“时间比例均摊法”**:
|
|
|
|
|
1. **中断总时长**:计算断网到恢复的总小时数 $H_{\text{total}} = (T_{\text{first}} - T_{\text{last}}) \times 24$。
|
|
|
|
|
2. **小时均产**:$P_{\text{hour}} = \Delta_{\text{total}} / H_{\text{total}}$。
|
|
|
|
|
3. **今日漏计产量分摊**:今日 00:00 到恢复采集(16:06:18)共漏计了约 16.1 小时,今日分摊漏计产量为:
|
|
|
|
|
$$Q_{\text{today}} = \text{ROUND}(P_{\text{hour}} \times 16.1)$$
|
|
|
|
|
这部分产量直接**累加追加**到今日的 `RT_DAILY_PROD_STATE.CURRENT_TOTAL` 字段中。
|
|
|
|
|
4. **历史中断日分摊**:6月19日至24日共 6 个完整中断日,每日分摊产量:
|
|
|
|
|
$$Q_{\text{day}} = \text{ROUND}(P_{\text{hour}} \times 24)$$
|
|
|
|
|
每天的产量写回 `DEVICE_DAILY_PRODUCTION` 历史表,实现全月产量的物理总量闭环。
|
|
|
|
|
|
|
|
|
|
### 2.2 开模数为何依旧为 0?
|
|
|
|
|
* **日累计产量**(`selectDayProductionTotal`)查的是实时累计表 `RT_DAILY_PROD_STATE`(今日自然日 00:00 - 23:59),补数脚本已直接累加了分摊值,所以它变多了。
|
|
|
|
|
* **当日开模数**(`selectDeviceOpeningCountList`)查的是**原始明细表 `BASE_DEVICE_PARAM_VAL_202606`** 在“昨日 07:00 到今日 07:00”这 24 小时的原始数据,并用 `delta-sum` 计算。
|
|
|
|
|
* **冲突点**:补数脚本并没有在原始明细表中虚构/补写数万条原始设备参数记录(防止污染追溯和 SPC)。明细表中昨日 07:00 到今日 07:00 的区间数据依旧是完全缺失的,因而重算时开模数依旧是 0。
|
|
|
|
|
|
|
|
|
|
### 2.3 开模数反向兜底逻辑
|
|
|
|
|
为了让大屏开模数展示正常,在 `Board4Mapper.xml` 查询中加入**“反向回源占比均摊”**:
|
|
|
|
|
开模数统计的是 昨日 07:00 到今日 07:00。这包含了昨日的 17 个小时和今日的 7 个小时。
|
|
|
|
|
如果发现当前 24 小时窗口内没有明细数据(`windowDataFlag = 0`),则自动反向从已补齐的日产量表中进行分摊占比反算:
|
|
|
|
|
$$\text{开模数} = \text{ROUND}\left( \text{昨日产量} \times \frac{17}{24} + \text{今日产量} \times \frac{7}{24} \right)$$
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 3. 数据库修复脚本
|
|
|
|
|
|
|
|
|
|
### 3.1 备份脚本
|
|
|
|
|
```sql
|
|
|
|
|
-- 备份今日受影响的 RT 数据
|
|
|
|
|
CREATE TABLE BK_RT_PROD_20260625 AS
|
|
|
|
|
SELECT * FROM RT_DAILY_PROD_STATE
|
|
|
|
|
WHERE PROD_DATE = DATE '2026-06-25'
|
|
|
|
|
AND PARAM_NAME = '机台状态-实际产出数量';
|
|
|
|
|
|
|
|
|
|
-- 备份历史受影响的 6月19日至6月24日 DAY 历史数据
|
|
|
|
|
CREATE TABLE BK_DAY_PROD_20260625 AS
|
|
|
|
|
SELECT * FROM DEVICE_DAILY_PRODUCTION
|
|
|
|
|
WHERE PROD_DATE >= DATE '2026-06-19'
|
|
|
|
|
AND PROD_DATE <= DATE '2026-06-24'
|
|
|
|
|
AND PARAM_NAME = '机台状态-实际产出数量';
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 3.2 方案一:全自动比例分摊 PL/SQL 修复脚本
|
|
|
|
|
```sql
|
|
|
|
|
DECLARE
|
|
|
|
|
C_PARAM_NAME CONSTANT VARCHAR2(128) := '机台状态-实际产出数量';
|
|
|
|
|
C_RECOVER_DATE CONSTANT DATE := DATE '2026-06-25';
|
|
|
|
|
|
|
|
|
|
V_LAST_VAL NUMBER(18,4);
|
|
|
|
|
V_LAST_TIME DATE;
|
|
|
|
|
V_FIRST_VAL NUMBER(18,4);
|
|
|
|
|
V_FIRST_TIME DATE;
|
|
|
|
|
V_TOTAL_DELTA NUMBER(18,4);
|
|
|
|
|
V_TOTAL_HOURS NUMBER(18,6);
|
|
|
|
|
V_QTY_PER_HOUR NUMBER(18,4);
|
|
|
|
|
V_TODAY_MISSING_HOURS NUMBER(18,6);
|
|
|
|
|
V_TODAY_QTY NUMBER(18,4);
|
|
|
|
|
V_TEMP_DATE DATE;
|
|
|
|
|
V_DAY_QTY NUMBER(18,4);
|
|
|
|
|
BEGIN
|
|
|
|
|
FOR D IN (
|
|
|
|
|
SELECT DEVICE_CODE
|
|
|
|
|
FROM BASE_DEVICELEDGER
|
|
|
|
|
WHERE IS_FLAG = 1
|
|
|
|
|
AND DEVICE_CODE NOT LIKE 'OLD-%'
|
|
|
|
|
AND DEVICE_NAME IS NOT NULL
|
|
|
|
|
AND NVL(DEVICE_TYPE, '1') = '1'
|
|
|
|
|
) LOOP
|
|
|
|
|
-- 1. 获取 6月18日 断网前最后一笔记录的值和时间
|
|
|
|
|
BEGIN
|
|
|
|
|
SELECT PARAM_VALUE, COLLECT_TIME
|
|
|
|
|
INTO V_LAST_VAL, V_LAST_TIME
|
|
|
|
|
FROM (
|
|
|
|
|
SELECT TO_NUMBER(TRIM(PARAM_VALUE)) AS PARAM_VALUE, COLLECT_TIME
|
|
|
|
|
FROM BASE_DEVICE_PARAM_VAL_202606
|
|
|
|
|
WHERE DEVICE_CODE = D.DEVICE_CODE
|
|
|
|
|
AND PARAM_NAME = C_PARAM_NAME
|
|
|
|
|
AND COLLECT_TIME < DATE '2026-06-19'
|
|
|
|
|
AND REGEXP_LIKE(TRIM(PARAM_VALUE), '^[+-]?[0-9]+([.][0-9]+)?$')
|
|
|
|
|
ORDER BY COLLECT_TIME DESC
|
|
|
|
|
) WHERE ROWNUM = 1;
|
|
|
|
|
EXCEPTION
|
|
|
|
|
WHEN NO_DATA_FOUND THEN
|
|
|
|
|
V_LAST_VAL := NULL; V_LAST_TIME := NULL;
|
|
|
|
|
END;
|
|
|
|
|
|
|
|
|
|
-- 2. 获取今日 6月25日 恢复后第一笔记录的值和时间
|
|
|
|
|
BEGIN
|
|
|
|
|
SELECT PARAM_VALUE, COLLECT_TIME
|
|
|
|
|
INTO V_FIRST_VAL, V_FIRST_TIME
|
|
|
|
|
FROM (
|
|
|
|
|
SELECT TO_NUMBER(TRIM(PARAM_VALUE)) AS PARAM_VALUE, COLLECT_TIME
|
|
|
|
|
FROM BASE_DEVICE_PARAM_VAL_202606
|
|
|
|
|
WHERE DEVICE_CODE = D.DEVICE_CODE
|
|
|
|
|
AND PARAM_NAME = C_PARAM_NAME
|
|
|
|
|
AND COLLECT_TIME >= C_RECOVER_DATE
|
|
|
|
|
AND REGEXP_LIKE(TRIM(PARAM_VALUE), '^[+-]?[0-9]+([.][0-9]+)?$')
|
|
|
|
|
ORDER BY COLLECT_TIME ASC
|
|
|
|
|
) WHERE ROWNUM = 1;
|
|
|
|
|
EXCEPTION
|
|
|
|
|
WHEN NO_DATA_FOUND THEN
|
|
|
|
|
V_FIRST_VAL := NULL; V_FIRST_TIME := NULL;
|
|
|
|
|
END;
|
|
|
|
|
|
|
|
|
|
-- 3. 开始自动比例均摊与写回
|
|
|
|
|
IF V_LAST_VAL IS NOT NULL AND V_FIRST_VAL IS NOT NULL AND V_FIRST_VAL > V_LAST_VAL THEN
|
|
|
|
|
V_TOTAL_DELTA := V_FIRST_VAL - V_LAST_VAL;
|
|
|
|
|
V_TOTAL_HOURS := (V_FIRST_TIME - V_LAST_TIME) * 24;
|
|
|
|
|
|
|
|
|
|
IF V_TOTAL_HOURS > 0 THEN
|
|
|
|
|
V_QTY_PER_HOUR := V_TOTAL_DELTA / V_TOTAL_HOURS;
|
|
|
|
|
|
|
|
|
|
-- 今日漏计产量累加写回 RT
|
|
|
|
|
V_TODAY_MISSING_HOURS := (V_FIRST_TIME - C_RECOVER_DATE) * 24;
|
|
|
|
|
V_TODAY_QTY := ROUND(V_QTY_PER_HOUR * V_TODAY_MISSING_HOURS);
|
|
|
|
|
|
|
|
|
|
UPDATE RT_DAILY_PROD_STATE
|
|
|
|
|
SET CURRENT_TOTAL = CURRENT_TOTAL + V_TODAY_QTY,
|
|
|
|
|
DIRTY_FLAG = 0,
|
|
|
|
|
UPDATE_TIME = SYSDATE
|
|
|
|
|
WHERE PROD_DATE = C_RECOVER_DATE
|
|
|
|
|
AND DEVICE_CODE = D.DEVICE_CODE
|
|
|
|
|
AND PARAM_NAME = C_PARAM_NAME;
|
|
|
|
|
|
|
|
|
|
IF SQL%ROWCOUNT = 0 THEN
|
|
|
|
|
INSERT INTO RT_DAILY_PROD_STATE (
|
|
|
|
|
PROD_DATE, DEVICE_CODE, PARAM_NAME, LAST_PARAM_VAL, CURRENT_TOTAL, RESET_COUNT, DIRTY_FLAG, LAST_COLLECT_TIME, UPDATE_TIME
|
|
|
|
|
) VALUES (
|
|
|
|
|
C_RECOVER_DATE, D.DEVICE_CODE, C_PARAM_NAME, V_FIRST_VAL, V_TODAY_QTY, 0, 0, V_FIRST_TIME, SYSDATE
|
|
|
|
|
);
|
|
|
|
|
END IF;
|
|
|
|
|
|
|
|
|
|
-- 过去 6 天历史断网日分摊写回 DAY
|
|
|
|
|
V_DAY_QTY := ROUND(V_QTY_PER_HOUR * 24);
|
|
|
|
|
FOR I IN 1..6 LOOP
|
|
|
|
|
V_TEMP_DATE := DATE '2026-06-18' + I;
|
|
|
|
|
MERGE INTO DEVICE_DAILY_PRODUCTION T
|
|
|
|
|
USING (
|
|
|
|
|
SELECT V_TEMP_DATE AS PROD_DATE,
|
|
|
|
|
D.DEVICE_CODE AS DEVICE_CODE,
|
|
|
|
|
C_PARAM_NAME AS PARAM_NAME
|
|
|
|
|
FROM DUAL
|
|
|
|
|
) S
|
|
|
|
|
ON (T.PROD_DATE = S.PROD_DATE AND T.DEVICE_CODE = S.DEVICE_CODE AND T.PARAM_NAME = S.PARAM_NAME)
|
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
|
UPDATE SET DAILY_PROD = V_DAY_QTY, CALC_TIME = SYSDATE
|
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
|
INSERT (PROD_DATE, DEVICE_CODE, PARAM_NAME, DAILY_PROD, RESET_COUNT, CALC_TIME)
|
|
|
|
|
VALUES (S.PROD_DATE, S.DEVICE_CODE, S.PARAM_NAME, V_DAY_QTY, 0, SYSDATE);
|
|
|
|
|
END LOOP;
|
|
|
|
|
END IF;
|
|
|
|
|
END IF;
|
|
|
|
|
END LOOP;
|
|
|
|
|
COMMIT;
|
|
|
|
|
END;
|
|
|
|
|
/
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 3.3 方案二:手工指定实绩对账 SQL 模板
|
|
|
|
|
```sql
|
|
|
|
|
-- 1. 手工精确修复 6月25日 RT 产量
|
|
|
|
|
MERGE INTO RT_DAILY_PROD_STATE T
|
|
|
|
|
USING (
|
|
|
|
|
SELECT DATE '2026-06-25' AS PROD_DATE, 'YZM-01' AS DEVICE_CODE, '机台状态-实际产出数量' AS PARAM_NAME, 1200 AS REAL_QTY FROM DUAL UNION ALL
|
|
|
|
|
SELECT DATE '2026-06-25' AS PROD_DATE, 'YZM-02' AS DEVICE_CODE, '机台状态-实际产出数量' AS PARAM_NAME, 1150 AS REAL_QTY FROM DUAL
|
|
|
|
|
) S
|
|
|
|
|
ON (T.PROD_DATE = S.PROD_DATE AND T.DEVICE_CODE = S.DEVICE_CODE AND T.PARAM_NAME = S.PARAM_NAME)
|
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
|
UPDATE SET CURRENT_TOTAL = S.REAL_QTY, DIRTY_FLAG = 0, UPDATE_TIME = SYSDATE;
|
|
|
|
|
|
|
|
|
|
-- 2. 手工精确补齐 19~24 日历史产量到 DAY
|
|
|
|
|
MERGE INTO DEVICE_DAILY_PRODUCTION T
|
|
|
|
|
USING (
|
|
|
|
|
SELECT DATE '2026-06-19' AS PROD_DATE, 'YZM-01' AS DEVICE_CODE, '机台状态-实际产出数量' AS PARAM_NAME, 1500 AS REAL_QTY FROM DUAL UNION ALL
|
|
|
|
|
SELECT DATE '2026-06-20' AS PROD_DATE, 'YZM-01' AS DEVICE_CODE, '机台状态-实际产出数量' AS PARAM_NAME, 1450 AS REAL_QTY FROM DUAL
|
|
|
|
|
) S
|
|
|
|
|
ON (T.PROD_DATE = S.PROD_DATE AND T.DEVICE_CODE = S.DEVICE_CODE AND T.PARAM_NAME = S.PARAM_NAME)
|
|
|
|
|
WHEN MATCHED THEN
|
|
|
|
|
UPDATE SET DAILY_PROD = S.REAL_QTY, CALC_TIME = SYSDATE
|
|
|
|
|
WHEN NOT MATCHED THEN
|
|
|
|
|
INSERT (PROD_DATE, DEVICE_CODE, PARAM_NAME, DAILY_PROD, RESET_COUNT, CALC_TIME)
|
|
|
|
|
VALUES (S.PROD_DATE, S.DEVICE_CODE, S.PARAM_NAME, S.REAL_QTY, 0, SYSDATE);
|
|
|
|
|
|
|
|
|
|
COMMIT;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 3.4 回滚 SQL
|
|
|
|
|
```sql
|
|
|
|
|
-- 回滚今天 6月25日 RT 产量
|
|
|
|
|
DELETE FROM RT_DAILY_PROD_STATE T
|
|
|
|
|
WHERE T.PROD_DATE = DATE '2026-06-25'
|
|
|
|
|
AND T.PARAM_NAME = '机台状态-实际产出数量';
|
|
|
|
|
|
|
|
|
|
INSERT INTO RT_DAILY_PROD_STATE SELECT * FROM BK_RT_PROD_20260625;
|
|
|
|
|
|
|
|
|
|
-- 回滚历史 19~24 日 DAY 产量
|
|
|
|
|
DELETE FROM DEVICE_DAILY_PRODUCTION T
|
|
|
|
|
WHERE T.PROD_DATE >= DATE '2026-06-19'
|
|
|
|
|
AND T.PROD_DATE <= DATE '2026-06-24'
|
|
|
|
|
AND T.PARAM_NAME = '机台状态-实际产出数量';
|
|
|
|
|
|
|
|
|
|
INSERT INTO DEVICE_DAILY_PRODUCTION SELECT * FROM BK_DAY_PROD_20260625;
|
|
|
|
|
|
|
|
|
|
COMMIT;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 4. 后端 Mapper 代码修复
|
|
|
|
|
在 [Board4Mapper.xml](file:///c:/D/WORK/NewP/zs_aucma-mes/zs_aucma-mes-back/aucma-report/src/main/resources/mapper/report/Board4Mapper.xml#L383-L398) 中将原本的查询逻辑优化,集成昨日与今日产量的比例均摊兜底:
|
|
|
|
|
|
|
|
|
|
```xml
|
|
|
|
|
SELECT td.device_code AS deviceCode,
|
|
|
|
|
td.device_name AS deviceName,
|
|
|
|
|
CASE
|
|
|
|
|
WHEN NVL(wd.window_data_count, 0) > 0 THEN NVL(ap.opening_count, 0)
|
|
|
|
|
ELSE ROUND(NVL(y.daily_prod, 0) * (17.0 / 24.0) + NVL(r.current_total, 0) * (7.0 / 24.0))
|
|
|
|
|
END AS openingCount,
|
|
|
|
|
CASE
|
|
|
|
|
WHEN wd.window_data_count > 0 THEN 1
|
|
|
|
|
ELSE 0
|
|
|
|
|
END AS windowDataFlag
|
|
|
|
|
FROM target_device td
|
|
|
|
|
LEFT JOIN actual_prod ap
|
|
|
|
|
ON td.device_code = ap.device_code
|
|
|
|
|
LEFT JOIN window_data_device wd
|
|
|
|
|
ON td.device_code = wd.device_code
|
|
|
|
|
LEFT JOIN DEVICE_DAILY_PRODUCTION y
|
|
|
|
|
ON td.device_code = y.device_code
|
|
|
|
|
AND y.prod_date = TRUNC(#{beginTime})
|
|
|
|
|
AND y.param_name = '机台状态-实际产出数量'
|
|
|
|
|
LEFT JOIN RT_DAILY_PROD_STATE r
|
|
|
|
|
ON td.device_code = r.device_code
|
|
|
|
|
AND r.prod_date = TRUNC(#{endTime})
|
|
|
|
|
AND r.param_name = '机台状态-实际产出数量'
|
|
|
|
|
```
|