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

8.7 KiB
Raw Permalink Blame History

SQL 指标优化计划

一、当前SQL逻辑分析

现有指标计算

  1. 当日新增换单数:当天到货并且推送了标签数据的订单数量
  2. 累计要换的总单数:历史未完成换单数 + 当日新增换单数(通过滚动求和计算)
  3. 当日标签推送数:通过标签推送时间计算的当天标签推送数量
  4. 当日换单成功数当天完成的换单包裹数Result = 0
  5. 当天换单完成率:当日完成数/(当日新增换单数+累计要换的总单数)
  6. 24H换单率24小时内完成的包裹数/当日完成数

现有逻辑中的关键CTE

  • ArrivalFormsWithDate:获取所有到货交接单基础信息
  • ArrivalRequests:关联到货单与换单请求,计算考核时间(目前:取标签推送时间和到仓时间的较晚时间)
  • OverallScanStatus:获取每个订单的首次成功信息
  • DailyBase:每日基础统计

二、新需求分析与实现方案

需求1修改包裹考核时间逻辑

原逻辑:取标签推送时间与到仓时间的较晚时间

新逻辑:根据标签率判断

  • 标签率 >= 80%对客承诺90%

    • 到仓时间 < 16:00考核时间截止为次日16:00
    • 到仓时间 >= 16:00考核时间截止为次日23:59
  • 标签率 < 80%对客承诺90%

    • 考核时间 = 包裹换单完成时间

实现方案

  1. ArrivalRequests CTE 中需要:

    • 计算每个订单所属客户的标签率
    • 根据标签率和到仓时间计算新的考核时间
  2. 需要新增CTE计算客户的标签率

    CustomerLabelRate: 计算每个客户的标签率 = 有标签的订单数/总订单数
    
  3. 修改 ArrivalRequests 中的考核时间计算逻辑

需求2修改24小时换单完成率

原逻辑24H内完成数/当日完成数

新逻辑(包裹换单完成时间 - 包裹考核时间 <= 0 的包裹数) / 当天应该换单数

说明

  • 包裹换单完成时间 <= 考核时间 的包裹视为24小时内完成
  • 分母改为"当天应该换单数"而不是"当日完成数"

实现方案

  1. 创建新CTE计算每日24小时内完成的包裹数
  2. 修改分母为当天应该换单数(历史未完成数+当日新增数)

需求3新增指标 - 16:00前到仓的包裹数量

定义当日到仓时间在16:00之前的包裹数量

实现方案 在每日统计中新增计数:

COUNT(DISTINCT CASE 
    WHEN ar.到货日期 = dd.日期 AND HOUR(ar.到货时间) < 16
    THEN ar.RequestId 
END) AS 16点前到仓包裹数

需求4新增指标 - 16:00后到仓的包裹数量

定义当日到仓时间在16:00之后的包裹数量

实现方案 在每日统计中新增计数:

COUNT(DISTINCT CASE 
    WHEN ar.到货日期 = dd.日期 AND HOUR(ar.到货时间) >= 16
    THEN ar.RequestId 
END) AS 16点后到仓包裹数

二.五、实现方式评估SQL实现 vs 代码实现

方案对比

方案A直接在SQL中完整实现推荐

优点

  • 数据库层面完成所有计算,性能最优
  • 减少应用层数据传输和处理
  • 逻辑清晰,便于维护和调试
  • 数据一致性更好

缺点

  • SQL复杂度高维护难度大
  • 调试相对困难

方案BSQL + C#代码混合实现

优点

  • 分离关注点,部分逻辑在应用层更清晰
  • 便于测试和调试

缺点

  • 性能相对较差(多次数据传输)
  • 代码复杂度反而更高
  • 数据一致性难以保证

最终决策采用方案ASQL完整实现

理由

  1. 虽然SQL复杂但逻辑清晰且一次性完成
  2. 涉及大量的CASE WHEN计算在数据库层完成更高效
  3. 新增的标签率计算本质上是CTE级别的操作适合SQL实现

三、实现步骤

步骤1分析当前代码结构

  • 已分析 DailyLabelStatsChineseDto 类结构
  • 已确认SQL所在文件位置

步骤2修改DTO类添加新字段

  • DailyLabelStatsChineseDto 类中添加:
    • LabelRate(标签率 %- 用于下推到前端展示整体标签率
    • BeforeNoonArrivedCount16:00前到仓包裹数
    • AfternoonArrivedCount16:00后到仓包裹数
    • ShouldReplaceCount(当天应该换单数 = 历史未完成+当日新增)
    • Rate24HourModified修改后的24小时换单率分母为当天应该换单数

步骤3修改SQL实现新逻辑

SQL文件位置d:\EPproject\LabelReplaceServer\src\DAL\Repositories\LabelReplaceRepository.cs (L709-1040)

3.1 新增 CustomerLabelRate CTE

  • 计算每个客户的标签率 = (有标签的订单数) / (总订单数)

3.2 修改 ArrivalRequests CTE

  • 添加客户标签率信息
  • 根据标签率和到仓时间重新计算考核时间:
    CASE
      WHEN 标签率 >= 0.8 THEN
        CASE
          WHEN HOUR(到仓时间) < 16 THEN DATE_ADD(DATE(到仓时间), INTERVAL 1 DAY) 16:00:00
          ELSE DATE_ADD(DATE(到仓时间), INTERVAL 1 DAY) 23:59:59
        END
      ELSE
        包裹换单完成时间
    END AS 考核时间
    

3.3 修改 DailyBase CTE

  • 添加16:00前和16:00后到仓包裹数的统计

3.4 新增/修改24小时完成数计算CTE

  • 重新计算基于新考核时间的24小时内完成数

3.5 修改最终SELECT语句

  • 添加新的指标列输出
  • 修改24小时换单率的分母

步骤4修改C#代码映射新字段

  • GetDailyLabelStatsChineseAsync() 方法中添加新列的映射

步骤5验证和测试

  • 检查SQL语法
  • 验证数据准确性
  • 测试边界情况

四、新增DTO字段映射与标签率计算

SQL输出新列

  1. 标签率 → 系统整体的标签率(有标签的订单数/总订单数)
  2. 16点前到仓包裹数BeforeNoonArrivedCount
  3. 16点后到仓包裹数AfternoonArrivedCount
  4. 当天应该换单数ShouldReplaceCount
  5. 修改后的24小时换单率Rate24HourModified 或保持原 Rate24Hour 字段

标签率计算方式

概念澄清

  • 交接单(Handover):到货交接单,由 HandoverNumber 标识
  • 订单(Request):换单请求记录(label_replace_requests)
  • 关系:一个交接单可以关联多个订单(通过 BillOfLadingNumber 或 MasterPackageNumber 匹配)
  • 总订单数:一个交接单关联的所有 label_replace_requests 的总数

客户标签率计算逻辑

  • 每个交接单所属一个客户
  • 客户标签率 = 该客户下有标签的订单总数 / 该客户下的总订单数
  • 系统标签率 = 全系统有标签的订单总数 / 全系统总订单数

在SQL中计算系统标签率在最终SELECT时新增

CONCAT(ROUND(
  (SELECT COUNT(DISTINCT l.Id) FROM label_replace_requests l 
   INNER JOIN arrival_handover_forms a 
   ON l.BillOfLadingNumber = a.HandoverNumber OR l.MasterPackageNumber = a.HandoverNumber
   WHERE l.Label IS NOT NULL AND l.Label != '') 
  /
  (SELECT COUNT(DISTINCT l.Id) FROM label_replace_requests l 
   INNER JOIN arrival_handover_forms a 
   ON l.BillOfLadingNumber = a.HandoverNumber OR l.MasterPackageNumber = a.HandoverNumber) 
  * 100, 2
), '%') AS 系统标签率

C#映射代码

GetDailyLabelStatsChineseAsync() 方法的reader映射中添加新字段

// 注意:系统标签率是常数(不随日期变化),在每行数据中值相同
LabelRate = reader["系统标签率"] as string ?? "0.00%",
BeforeNoonArrivedCount = reader["16点前到仓包裹数"] != DBNull.Value ? Convert.ToInt32(reader["16点前到仓包裹数"]) : 0,
AfternoonArrivedCount = reader["16点后到仓包裹数"] != DBNull.Value ? Convert.ToInt32(reader["16点后到仓包裹数"]) : 0,
ShouldReplaceCount = reader["当天应该换单数"] != DBNull.Value ? Convert.ToInt32(reader["当天应该换单数"]) : 0,

五、关键数据表结构回顾

  • arrival_handover_forms: 到货交接单表

    • ReceiptTime: 到仓时间UTC-5
    • HandoverNumber: 交接单号
  • label_replace_requests: 换单请求表

    • NeutralWaybillNumber: 中性运单号
    • Label: 标签
    • LabelRetrievedAt: 标签推送时间
    • CustomerId: 客户ID
  • label_scan_history: 扫描历史表

    • CreatedAt: 扫描时间UTC
    • Result: 扫描结果0=成功)

六、实现注意事项

  1. 时区处理确保所有时间比较统一使用UTC-5时区
  2. 标签率计算:需要明确如何定义"总订单数"(所有订单还是特定条件的订单)
  3. 考核时间精确性:新逻辑涉及时间戳的精确比较,需谨慎处理
  4. 向后兼容性可能需要在Excel导出功能中也添加新列
  5. 性能考虑大量的CASE WHEN和时间转换可能影响性能需监控