7.5 KiB
7.5 KiB
数据库视图逻辑修复计划
问题分析
当前问题表现
- 视图查询结果大多为0
- 仅
DailyShouldReplaceCount= 3592 和CumulativeTotalReplaceCount= 3315 有数据 - 其他指标(新增数、完成数、到仓数等)全为0
根本原因
当前视图SQL存在以下关键问题:
-
硬编码NOW()比较问题 (第17、24、34、42、49、56、62、69、76、83、91、100行)
- 使用
CAST(CONVERT_TZ(NOW(), '+00:00', '-05:00') AS DATE)与NOW()进行比较 - 每次查询都与当前时间比较,导致查询特定历史日期时大部分条件都不满足
- 应该改为接受参数化的查询日期
- 使用
-
JOIN逻辑混乱(第109-115行)
- 对
label_scan_history的JOIN使用子查询且索引方式错误 - 对
arrival_handover_forms的JOIN条件不合理:r.BillOfLadingNumber = ahf.HandoverNumber OR r.MasterPackageNumber = ahf.HandoverNumber - 应该支持多种匹配方式,但需要明确的业务逻辑
- 对
-
时区转换过度 (多个CONVERT_TZ调用)
- 重复的CONVERT_TZ导致性能下降
- 应该在应用层处理时区转换,简化视图逻辑
-
指标计算与业务逻辑不一致
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:直接修复当前视图(临时方案)
思路:
- 在视图中使用
CURDATE()代替NOW() - 简化JOIN逻辑
- 修正指标计算
缺点:
- 只能查询当前日期数据
- 不符合长期需求
选择:方案2(存储过程)
原因
- 最符合实际业务需求
- 应用层已有
db.SqlQueryable<dynamic>(sql)的调用方式 - 易于与C#应用集成
实现步骤
步骤1:创建存储过程 sp_GetDailyMetricsSummary
存储过程需要接受一个 QueryDate 参数(YYYY-MM-DD格式),返回当天的所有指标。
核心逻辑:
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):
-
DailyShouldReplaceCount / BeforeNoonArrivedCount / AfternoonArrivedCount
- 获取当日接收的交接单(ReceiptTime在p_date这一天UTC-5)
- 计算每个交接单的标签率(标签订单数 / 总订单数)
- 只统计标签率≥80%的订单
- 按16点划分
-
DailySuccessCount / BeforeNoonPassedCount / AfternoonPassedCount
- 对于16点前到仓的订单:统计次日16:00前扫描成功的数量
- 对于16点后到仓的订单:统计本日16:00前扫描成功的数量
- Result = 0 表示扫描成功
-
CumulativeTotalReplaceCount
- 统计所有历史到仓记录(直到昨天)中,未扫描成功的订单
-
DailyFailureCount
- DailyShouldReplaceCount - DailySuccessCount
步骤2:修改应用层调用
在 MetricsCalculationService.GetDailySummaryAsync() 中:
string sql = $"CALL sp_GetDailyMetricsSummary('{date:yyyy-MM-dd}')";
var summaryList = await db.SqlQueryable<dynamic>(sql).ToListAsync();
步骤3:验证和测试
- 在数据库中执行
CALL sp_GetDailyMetricsSummary('2026-05-15') - 验证返回的各项指标是否大于0(符合实际数据)
- 对比原始的11个异步查询结果
- 性能测试:确保<1秒完成
风险评估
| 风险项 | 概率 | 影响 | 缓解方案 |
|---|---|---|---|
| 存储过程语法错误 | 中 | 中 | 保留原始视图作为备份,逐行测试 |
| 业务逻辑理解偏差 | 中 | 高 | 与用户确认每个指标的定义,对比原始结果 |
| 性能不达预期 | 低 | 中 | 添加必要索引,优化查询计划 |
| 时区转换错误 | 低 | 高 | 充分测试UTC-5转换逻辑,验证样本数据 |
实现时间表
- 编写存储过程SQL - 验证语法正确
- 在数据库创建存储过程 - 确保无错误
- 用样本数据测试 - 对比原始结果
- 修改应用层代码 - 调整GetDailySummaryAsync方法
- 集成测试 - 前端调用验证
- 性能验证 - 确保<1秒目标
- 部署 - 发布到生产环境
备选方案
如果存储过程方案遇到困难,改为直接优化视图:
- 移除所有
NOW()比较 - 在应用层计算日期范围(UTC-5)
- 使用
BETWEEN比较日期范围而非精确日期 - 简化JOIN逻辑,分离查询