# SQL 指标优化实现总结（最终版）

## 实现完成时间
2026-05-16 (最终更新版本)

## 实现范围确认

### ✅ 已完成的任务

#### 1. DTO类修改（最终版本）
**文件**: `d:\EPproject\LabelReplaceServer\src\MDL\DTOs\DailyLabelStatsChineseDto.cs`

**字段调整**:
- ❌ **移除**: `LabelRate` (string) - 系统整体标签率已移除
- ✅ **保留**: `BeforeNoonArrivedCount` (int) - 16点前到仓的包裹数
- ✅ **保留**: `AfternoonArrivedCount` (int) - 16点后到仓的包裹数
- ✅ **保留**: `ShouldReplaceCount` (int) - 当天应该换单数（历史未完成+当日新增）
- ✅ **新增**: `BeforeNoonPassedCount` (int) - **16点前考核通过包裹数**
- ✅ **新增**: `AfternoonPassedCount` (int) - **16点后考核通过包裹数**

**字段映射正确性**: ✅ 所有字段都有 SugarColumn 注解

---

#### 2. SQL查询最终实现
**文件**: `d:\EPproject\LabelReplaceServer\src\DAL\Repositories\LabelReplaceRepository.cs`

##### 新增CTE：

1. ✅ **CustomerLabelRates** (步骤2)
   - 计算每个客户的标签率：有标签订单数 / 总订单数
   - 用途：在ArrivalRequests中判断是否应用固定考核时间

2. ✅ **DailyBeforeNoonPassed** (步骤14.5) - **新增**
   - 统计16点前到仓且**考核通过**的包裹数量
   - 条件：HOUR(ar.到货时间) < 16 AND 完成时间 <= 考核时间

3. ✅ **DailyAfternoonPassed** (步骤14.6) - **新增**
   - 统计16点后到仓且**考核通过**的包裹数量
   - 条件：HOUR(ar.到货时间) >= 16 AND 完成时间 <= 考核时间

##### 修改的CTE：

1. ✅ **ArrivalRequests** (步骤3)
   - 新增字段：`客户标签率`
   - 重新实现`考核时间`逻辑：
     - 标签率 >= 80%：到仓时间<16:00 → 次日16:00；≥16:00 → 次日23:59
     - 标签率 < 80%：考核时间设为NULL，后续使用完成时间
   - JOIN: 新增 LEFT JOIN CustomerLabelRates

2. ✅ **DailyBase** (步骤9)
   - 新增统计：`16点前到仓包裹数`
   - 新增统计：`16点后到仓包裹数`

3. ✅ **Daily24HCompletedOrders** (步骤14)
   - 修改分组依据：from `oss.首次成功日期` to `ar.到货日期`
   - 修改完成条件：新的二阶段判断逻辑
     - 标签率≥80%：完成时间 <= 考核时间
     - 标签率<80%：考核时间为NULL，直接算达标

4. ✅ **DailyStatsWithPrev** (步骤15)
   - 新增字段传递：`16点前到仓包裹数`, `16点后到仓包裹数`

##### 最终SELECT修改：

1. ✅ **新增输出列**:
   - `当天应该换单数`: 通过变量计算 = 累计要换的总单数 + 当日新增换单数
   - `16点前到仓包裹数`: 直接输出
   - `16点后到仓包裹数`: 直接输出
   - **`16点前考核通过包裹数`**: ✨ 新增 - 16点前到仓的通过考核包裹数
   - **`16点后考核通过包裹数`**: ✨ 新增 - 16点后到仓的通过考核包裹数

2. ✅ **修改24小时换单率计算**:
   - 分母：从 `当日完成数` 改为 `当天应该换单数`
   - 分子：保持 `24H内完成数`
   - 新公式: `24H内完成数 / 当天应该换单数 * 100`

3. ✅ **移除系统标签率**:
   - 不再在SELECT中计算和输出系统标签率
   - 精简了最终输出，提高查询性能

---

#### 3. C#代码映射（最终版本）
**文件**: `d:\EPproject\LabelReplaceServer\src\DAL\Repositories\LabelReplaceRepository.cs` (L1084-1105)

**映射调整**:
```csharp
// 保留
ShouldReplaceCount = reader["当天应该换单数"] != DBNull.Value ? Convert.ToInt32(reader["当天应该换单数"]) : 0,
BeforeNoonArrivedCount = reader["16点前到仓包裹数"] != DBNull.Value ? Convert.ToInt32(reader["16点前到仓包裹数"]) : 0,
AfternoonArrivedCount = reader["16点后到仓包裹数"] != DBNull.Value ? Convert.ToInt32(reader["16点后到仓包裹数"]) : 0,

// 新增
BeforeNoonPassedCount = reader["16点前考核通过包裹数"] != DBNull.Value ? Convert.ToInt32(reader["16点前考核通过包裹数"]) : 0,
AfternoonPassedCount = reader["16点后考核通过包裹数"] != DBNull.Value ? Convert.ToInt32(reader["16点后考核通过包裹数"]) : 0,

// 移除
// LabelRate = reader["系统标签率"] as string ?? "0.00%",
```

---

## 核心指标定义

### 16点前到仓 vs 16点前考核通过的区别

| 指标 | 定义 | SQL条件 | 用途 |
|------|------|--------|------|
| 16点前到仓包裹数 | 当日16:00前到仓的所有包裹 | `HOUR(到货时间) < 16` | 统计到仓分布 |
| 16点前考核通过包裹数 | 当日16:00前到仓**且考核通过**的包裹 | `HOUR(到货时间) < 16 AND 完成时间 <= 考核时间` | 统计实际达成率 |

### 16点后到仓 vs 16点后考核通过的区别

| 指标 | 定义 | SQL条件 | 用途 |
|------|------|--------|------|
| 16点后到仓包裹数 | 当日16:00后到仓的所有包裹 | `HOUR(到货时间) >= 16` | 统计到仓分布 |
| 16点后考核通过包裹数 | 当日16:00后到仓**且考核通过**的包裹 | `HOUR(到货时间) >= 16 AND 完成时间 <= 考核时间` | 统计实际达成率 |

---

## 考核时间逻辑（基于客户标签率）

### 标签率 >= 80%
- **到仓时间 < 16:00**：考核时间 = 次日16:00
- **到仓时间 >= 16:00**：考核时间 = 次日23:59

### 标签率 < 80%
- 考核时间 = 包裹实际换单完成时间（即完成就过关）

---

## 24小时换单完成率（已修改）

**定义**：通过24小时内完成考核的包裹数 / 当天应该换单数

**计算逻辑**：
```
24H完成率 = (
  COUNT(DISTINCT 
    WHERE 完成时间 <= 考核时间
  )
) / (累计要换的总单数 + 当日新增换单数) * 100%
```

**说明**：
- 分子：不受到仓时间影响，直接判断是否在考核时间内完成
- 分母：改为"当天应该完成的总包裹数"而不是"完成的包裹数"

---

## 编译检查结果

✅ **DailyLabelStatsChineseDto.cs**: 无新增诊断错误
✅ **LabelReplaceRepository.cs**: 无新增诊断错误

---

## SQL逻辑关键验证点

### ✅ 时区处理
- 所有到仓时间：使用 `a.到货时间`（已是UTC-5）
- 完成时间比较：使用 `CONVERT_TZ(oss.首次成功时间, '+00:00', '-05:00')` 转为UTC-5

### ✅ 标签率判断
- 清晰的二阶段逻辑：高标签率用固定时间，低标签率用完成时间
- 两个新增CTE独立处理16点前后的考核通过统计

### ✅ 16点分段统计
- 16点前：`HOUR(ar.到货时间) < 16`
- 16点后：`HOUR(ar.到货时间) >= 16`
- 两个条件互补，无重叠无遗漏

### ✅ 考核通过判断
- 标签率≥80%：`CONVERT_TZ(oss.首次成功时间, '+00:00', '-05:00') <= ar.考核时间`
- 标签率<80%：`ar.考核时间 IS NULL` 时直接判定为达标
- 两个条件通过OR连接，完全覆盖所有情况

---

## 字段映射关系

### 输入（SQL列） → 输出（DTO属性）

| SQL列名 | DTO属性 | 数据类型 | 说明 |
|--------|--------|---------|------|
| 日期 | Date | string | yyyy-MM-dd格式 |
| 当日新增换单数 | DailyNewReplaceCount | int | 当日新增 |
| 累计要换的总单数 | CumulativeTotalReplaceCount | int | 历史累计 |
| 当天应该换单数 | ShouldReplaceCount | int | 应该完成的总数 |
| 换单失败未完结订单 | UnfinishedFailureCount | int | 未完结 |
| 当日换单失败 | DailyFailureCount | int | 当日失败 |
| 当日换单成功数 | DailySuccessCount | int | 当日成功 |
| 当日STOP数 | DailyStopCount | int | STOP标签数 |
| 16点前到仓包裹数 | BeforeNoonArrivedCount | int | 到仓分布 |
| 16点后到仓包裹数 | AfternoonArrivedCount | int | 到仓分布 |
| 16点前考核通过包裹数 | **BeforeNoonPassedCount** | int | **✨ 新增** |
| 16点后考核通过包裹数 | **AfternoonPassedCount** | int | **✨ 新增** |
| 24H换单率 | Rate24Hour | string | "xx.xx%" |
| 当天换单完成率 | DailyCompletionRate | string | "xx.xx%" |
| 当日标签推送数 | DailyLabelPushCount | int | 推送数 |
| 当日扫描数 | DailyScanCount | int | 扫描数 |
| 数据拉取时间（UTC_5） | DataFetchTime | DateTime | 查询时间 |

---

## 性能影响分析

### 正面影响
✅ **性能优化**：
- 移除了系统标签率的复杂子查询
- 减少了每行数据的计算复杂度
- 最终输出只包含必要的字段

### 新增操作
⚠️ **两个新的CTE**：
- DailyBeforeNoonPassed（16点前考核通过）
- DailyAfternoonPassed（16点后考核通过）
- 影响：轻微，因为逻辑与Daily24HCompletedOrders类似

### 建议优化
- 添加索引：`CREATE INDEX idx_lrr_custom_label ON label_replace_requests(CustomerId, Label);`
- 添加索引：`CREATE INDEX idx_lsh_result_createdat ON label_scan_history(Result, CreatedAt);`

---

## 实现完成度
✅ **100% 完成**

- ✅ DTO类修改（移除LabelRate，新增两个16点考核通过字段）
- ✅ SQL查询重写（新增DailyBeforeNoonPassed/DailyAfternoonPassed，移除系统标签率）
- ✅ C#映射更新（移除LabelRate映射，新增两个16点考核映射）
- ✅ 编译无错误
- ✅ 所有新增需求指标已实现
- ✅ 代码符合现有风格

