# 数据库视图逻辑修复计划 ## 问题分析 ### 当前问题表现 - 视图查询结果大多为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(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. BeforeNoonArrivedCount:16点前到仓数 -- 来源:当日到仓时间(ReceiptTime) < 16:00 (UTC-5)的订单数 -- 9. AfternoonArrivedCount:16点后到仓数 -- 10. BeforeNoonPassedCount:16点前完成数 -- 来源:16点前到仓的订单中,在次日16:00前扫描成功的订单数 -- 11. AfternoonPassedCount:16点后完成数 -- 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(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逻辑,分离查询