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

252 lines
8.7 KiB
Markdown
Raw Permalink 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.

# 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之前的包裹数量
**实现方案**
在每日统计中新增计数:
```sql
COUNT(DISTINCT CASE
WHEN ar.到货日期 = dd.日期 AND HOUR(ar.到货时间) < 16
THEN ar.RequestId
END) AS 16点前到仓包裹数
```
### 需求4新增指标 - 16:00后到仓的包裹数量
**定义**当日到仓时间在16:00之后的包裹数量
**实现方案**
在每日统计中新增计数:
```sql
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分析当前代码结构
- [x] 已分析 `DailyLabelStatsChineseDto` 类结构
- [x] 已确认SQL所在文件位置
### 步骤2修改DTO类添加新字段
- 在 `DailyLabelStatsChineseDto` 类中添加:
- `LabelRate`(标签率 %- 用于下推到前端展示整体标签率
- `BeforeNoonArrivedCount`16:00前到仓包裹数
- `AfternoonArrivedCount`16: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时新增
```sql
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映射中添加新字段
```csharp
// 注意:系统标签率是常数(不随日期变化),在每行数据中值相同
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和时间转换可能影响性能需监控