Files
LabelChange-server/.trae/documents/view_logic_fix_plan.md
2026-06-01 16:30:29 +08:00

222 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 数据库视图逻辑修复计划
## 问题分析
### 当前问题表现
- 视图查询结果大多为0
-`DailyShouldReplaceCount` = 3592 和 `CumulativeTotalReplaceCount` = 3315 有数据
- 其他指标新增数、完成数、到仓数等全为0
### 根本原因
当前视图SQL存在以下关键问题
1. **硬编码NOW()比较问题** 第17、24、34、42、49、56、62、69、76、83、91、100行
- 使用 `CAST(CONVERT_TZ(NOW(), '+00:00', '-05:00') AS DATE)``NOW()` 进行比较
- 每次查询都与当前时间比较,导致查询特定历史日期时大部分条件都不满足
- 应该改为接受参数化的查询日期
2. **JOIN逻辑混乱**第109-115行
-`label_scan_history`的JOIN使用子查询且索引方式错误
-`arrival_handover_forms`的JOIN条件不合理`r.BillOfLadingNumber = ahf.HandoverNumber OR r.MasterPackageNumber = ahf.HandoverNumber`
- 应该支持多种匹配方式,但需要明确的业务逻辑
3. **时区转换过度** 多个CONVERT_TZ调用
- 重复的CONVERT_TZ导致性能下降
- 应该在应用层处理时区转换,简化视图逻辑
4. **指标计算与业务逻辑不一致**
- `DailyShouldReplaceCount` 应该基于 `arrival_handover_forms` 表的接收时间
- 当前逻辑:基于 `label_replace_requests` 的创建时间判断
- 业务逻辑源自MetricsCalculationService
- 先获取当日到仓的交接单按ReceiptTime
- 计算每个交接单的标签率≥80%时才计为应换单)
- 统计满足条件的订单数
## 修复方案
### 方案1修改为参数化查询的视图推荐
**思路**:不在视图中使用 `NOW()`,改为在应用层传入查询日期,从而支持灵活的日期范围查询
**优点**
- 支持查询任意日期的指标
- 简化SQL逻辑提高性能
- 易于调试和验证
**缺点**
- 不能直接在SQL中使用 `SELECT * FROM view WHERE date = '2026-05-15'`
- 需要使用存储过程或改为表函数
### 方案2创建存储过程更优
**思路**:使用存储过程接受 `QueryDate` 参数,动态生成查询语句
**优点**
- 完全灵活,支持任意日期查询
- 保持SQL优化和性能
- 易于维护和扩展
**缺点**
- 需要修改应用层调用方式
### 方案3直接修复当前视图临时方案
**思路**
1. 在视图中使用 `CURDATE()` 代替 `NOW()`
2. 简化JOIN逻辑
3. 修正指标计算
**缺点**
- 只能查询当前日期数据
- 不符合长期需求
## 选择方案2存储过程
### 原因
1. 最符合实际业务需求
2. 应用层已有 `db.SqlQueryable<dynamic>(sql)` 的调用方式
3. 易于与C#应用集成
### 实现步骤
#### 步骤1创建存储过程 `sp_GetDailyMetricsSummary`
存储过程需要接受一个 `QueryDate` 参数YYYY-MM-DD格式返回当天的所有指标。
**核心逻辑**
```sql
DELIMITER //
CREATE PROCEDURE sp_GetDailyMetricsSummary(IN p_date DATE)
BEGIN
-- 时间范围定义UTC-5时区
-- 开始时间:查询日期 00:00 (UTC-5)
-- 结束时间:查询日期 23:59:59 (UTC-5)
SELECT
p_date AS MetricsDate,
-- 1. DailyNewReplaceCount当天新增应换单数
-- 来源当日到仓的交接单中标签率≥80%的订单数
-- 2. DailyShouldReplaceCount应该换单数累计
-- 来源所有到仓的交接单直到今天标签率≥80%的订单数
-- 3. DailySuccessCount当天完成数
-- 来源:当日扫描成功(Result=0)的订单数
-- 4. CumulativeTotalReplaceCount累计未完成数
-- 来源:历史到仓记录中,未扫描成功的订单数
-- 5. DailyStopCount当日冻结数
-- 来源:当日扫描结果中包含"STOP"的订单数
-- 6. DailyLabelPushCount当日标签推送数
-- 来源:当日新增标签的订单数(Label不为空)
-- 7. DailyScanCount当日扫描总数
-- 8. BeforeNoonArrivedCount16点前到仓数
-- 来源:当日到仓时间(ReceiptTime) < 16:00 (UTC-5)的订单数
-- 9. AfternoonArrivedCount16点后到仓数
-- 10. BeforeNoonPassedCount16点前完成数
-- 来源16点前到仓的订单中在次日16:00前扫描成功的订单数
-- 11. AfternoonPassedCount16点后完成数
-- 12. DailyFailureCount当日失败数
NOW() AS DataFetchTime
FROM (
-- 基础数据集:当日及历史到仓记录
SELECT
r.Id AS OrderId,
r.NeutralWaybillNumber,
r.Label,
ahf.HandoverNumber,
ahf.ReceiptTime,
s.Id AS ScanId,
s.Result,
s.Description,
s.CreatedAt AS ScanCreatedAt
FROM label_replace_requests r
LEFT JOIN arrival_handover_forms ahf ON
r.BillOfLadingNumber = ahf.HandoverNumber
OR r.MasterPackageNumber = ahf.HandoverNumber
LEFT JOIN (
SELECT * FROM label_scan_history
WHERE CAST(DATE(CONVERT_TZ(CreatedAt, '+00:00', '-05:00'))) AS DATE) >= DATE_SUB(p_date, INTERVAL 90 DAY)
) s ON r.NeutralWaybillNumber = s.NeutralWaybillNumber
WHERE r.Label IS NOT NULL
) base_data
GROUP BY p_date;
END //
DELIMITER ;
```
**关键业务规则**基于MetricsCalculationService
1. **DailyShouldReplaceCount / BeforeNoonArrivedCount / AfternoonArrivedCount**
- 获取当日接收的交接单ReceiptTime在p_date这一天UTC-5
- 计算每个交接单的标签率(标签订单数 / 总订单数)
- 只统计标签率≥80%的订单
- 按16点划分
2. **DailySuccessCount / BeforeNoonPassedCount / AfternoonPassedCount**
- 对于16点前到仓的订单统计次日16:00前扫描成功的数量
- 对于16点后到仓的订单统计本日16:00前扫描成功的数量
- Result = 0 表示扫描成功
3. **CumulativeTotalReplaceCount**
- 统计所有历史到仓记录(直到昨天)中,未扫描成功的订单
4. **DailyFailureCount**
- DailyShouldReplaceCount - DailySuccessCount
#### 步骤2修改应用层调用
`MetricsCalculationService.GetDailySummaryAsync()` 中:
```csharp
string sql = $"CALL sp_GetDailyMetricsSummary('{date:yyyy-MM-dd}')";
var summaryList = await db.SqlQueryable<dynamic>(sql).ToListAsync();
```
#### 步骤3验证和测试
1. 在数据库中执行 `CALL sp_GetDailyMetricsSummary('2026-05-15')`
2. 验证返回的各项指标是否大于0符合实际数据
3. 对比原始的11个异步查询结果
4. 性能测试:确保<1秒完成
## 风险评估
| 风险项 | 概率 | 影响 | 缓解方案 |
|--------|------|------|---------|
| 存储过程语法错误 | | | 保留原始视图作为备份逐行测试 |
| 业务逻辑理解偏差 | | | 与用户确认每个指标的定义对比原始结果 |
| 性能不达预期 | | | 添加必要索引优化查询计划 |
| 时区转换错误 | | | 充分测试UTC-5转换逻辑验证样本数据 |
## 实现时间表
1. **编写存储过程SQL** - 验证语法正确
2. **在数据库创建存储过程** - 确保无错误
3. **用样本数据测试** - 对比原始结果
4. **修改应用层代码** - 调整GetDailySummaryAsync方法
5. **集成测试** - 前端调用验证
6. **性能验证** - 确保<1秒目标
7. **部署** - 发布到生产环境
## 备选方案
如果存储过程方案遇到困难改为直接优化视图
1. 移除所有 `NOW()` 比较
2. 在应用层计算日期范围UTC-5
3. 使用 `BETWEEN` 比较日期范围而非精确日期
4. 简化JOIN逻辑分离查询