工单全渠道统计及字段优化需求文档
多维度、跑批定时任务、多角度分析
页面内容
需求概述
投诉数据对比报表
业务规则
报表名称:投诉数据对比报表
菜单路径:工单系统 → 报表系统 → 报表查询 → 投诉数据对比报表
功能描述:
- 支持按年度、季度、月度对远程银行各渠道的投诉数据进行对比分析。
报表权限:
- 总行消费者权益保护部工单用户和运营管理部(服务督查室)客户关注岗、值班经理、坐席组长:查询及导出全量数据。
- 分行工单用户:查询及导出“业务所在城市”为本分行的数据。
- 支行工单用户:无该报表任何权限,不展示该报表菜单。
数据范围(投诉渠道):
- 总行监管转办
- 信用卡中心监管转办
- 远程银行中心自收
- 信用卡中心自收
- 其他
投诉渠道与工单类型对应关系
| 投诉渠道 | 工单类型/数据来源 | 解释说明 |
|---|---|---|
| 总行监管转办 | 全渠道投诉报表中“总行监管转办投诉”的数据(总行监管转办工单+分行监管转办工单) | 总分行非信用卡条线受理的监管机构转办投诉 |
| 信用卡中心监管转办 | 全渠道投诉报表中,“投诉来源”为“信用卡中心监管转办投诉”的投诉数据 | 信用卡中心受理的监管机构转办投诉 |
| 远程银行中心自收 | 参见下文“自收投诉来源和工单类型对应关系表” | 信用卡中心自有渠道受理的客户投诉 |
| 其他 | 其他投诉工单(暂无) | 其他渠道受理的客户投诉 |
自收投诉来源和工单类型对应关系表
| 自收投诉来源 | 工单类型 | 解释说明 |
|---|---|---|
| 自有渠道 | 投诉工单 | 95577/4006695577 受理的客户投诉 |
| 12378呼转 | 投诉工单(12378) | 12378呼转至95577或4006695577的客户投诉 |
| 消保平台转办 | 消保平台转送投诉(总) | 消保平台转办至95577或4006695577的客户投诉 |
| 其他 | 其他(暂无) | 其他投诉来源的客户投诉 |
查询条件:
- 投诉渠道
- 自收投诉来源
- 对比时段类型
- 查询数据时段
- 对比条件
- 对比单位
- 业务条线
- 被投诉单位
查询条件说明:
- 查询条件【对比时段类型】选择“年度”时,【查询数据时段】仅支持选择某年份;季、月、自选同理,支持与之对应的具体时间范围。
- 特别说明:查询条件选择“年”时,没有同比数据,显示“/”。
对比条件释义:
- “全量”:对全量投诉数据进行对比
- “剔除重复”:统计对比时段内唯一来电号码的投诉数据,重复号码的多笔投诉仅做一笔
- “剔除无效”:在“剔除重复”数据基础上,同时剔除各经办单位提出并审核通过的无效投诉
被投诉单位:
- 按投诉工单中“业务所在城市”字段的数据取值
- 查询条件选项与投诉工单“业务所在城市”一致,支持单选和全选,默认“全选”
业务条线:
- 按投诉工单中“涉及条线”字段的数据取值
- 查询条件选项与投诉工单“涉及条线”一致,支持单选和全选,默认“全选”
同比、环比数据公式:
- 同比增量 = 本期 - 同期
- 同比增幅 = (本期 - 同期) / 同期 × 100%
- 环比增量 = 本期 - 上期(例如:3月-2月)
- 环比增幅 = (本期 - 上期) / 上期 × 100%
说明:
- 如果本期为本日、月、季,则同期指上一年同日、月、季;如无同日,则取上年同月最后一日。
其他说明:
- 反显“投诉人电话号码”,显示该号码近30个自然日内的咨询工单数量。
- 支持手动调整。
口径确认(已拍板)
1)“剔除重复”的去重键
- 去重键:仅使用 来电号/投诉人电话号码 去重(不叠加渠道/条线/部门等维度)。
- 去重范围:在“查询数据时段”内,同一来电号出现的多笔投诉仅计 1 笔。
- 建议实现:先对明细按
dedup_phone归一化(去空格、去+86、去分隔符),再做去重。
2)“剔除无效”的生效时间
- 生效时间:按“无效剔除申请”的 审批通过时间 生效。
- 统计规则:在“剔除重复”的集合上,再剔除 审批通过且已生效 的记录(以通过时间为准)。
- 审计要求:需保留审批人、审批时间、原因、撤销等变更轨迹,以便追溯。
3)“对比单位”的定义(你关心的重点)
“对比单位”用于控制报表输出的分组维度(GROUP BY),与“业务条线/被投诉单位”等筛选条件(WHERE)要区分开。
- 对比单位=业务条线:例如“零售业务部、法律合规部……”,取自工单字段“涉及条线”。
- 对比单位=投诉部门(分行/机构):例如“北京分行、南京分行……”,取自工单字段“投诉部门/经办单位/机构归属”(需在数据字典中明确唯一来源字段与枚举值)。
建议将
对比单位设计成枚举:LINE(业务条线) /DEPT(投诉部门/机构)。\n
研发设计分析:需要考虑的问题与解决方案
一、是否需要跑批?结论:建议必须跑批
原因:该报表包含多维筛选 + 去重(仅来电号)+ 剔除无效(审批通过时间生效)+ 同比/环比对照,属于典型 OLAP 统计场景。\n 推荐方式:\n
- T+1 日批汇总:每天产出“按天分区”的聚合表;查询按年/季/月/自选再二次汇总。
- 增量回补:当“无效剔除审批通过/撤销”发生时,触发对相关日期分区的回补(建议回补近 90 天窗口,或按审批涉及工单精确回补)。\n
二、维度设计:筛选维度(WHERE)与对比维度(GROUP BY)分离
1)筛选维度(WHERE)
- 投诉渠道(channel)
- 自收投诉来源(source)
- 查询数据时段(period range)
- 对比条件(全量 / 剔除重复 / 剔除无效)
- 被投诉单位(业务所在城市,city)【同时承载权限过滤】
- 业务条线(line)
- 投诉部门/机构(dept)【可选:当对比单位不是 dept 时也可作为过滤条件】
2)对比维度(GROUP BY)
- 当
对比单位=业务条线:按line分组输出 - 当
对比单位=投诉部门:按dept分组输出\n
这样可以同时满足:\n
- “看条线对比”(零售业务部 vs 法律合规部)\n
- “看分行/部门对比”(北京分行 vs 南京分行)\n
三、去重(仅来电号)如何落地
关键点:去重键只有来电号,但统计仍需按渠道/条线/部门/城市等维度输出。\n 建议采用“先去重、再归属维度”的策略:\n
- 在统计期内按
dedup_phone分组,选取代表记录(建议:最早创建时间的一笔;若有优先级规则则按优先级)。\n - 代表记录决定该“唯一投诉”的维度归属(channel/source/line/dept/city)。\n
注意:同一来电号跨多个条线/部门出现时,口径会归到“代表记录”的那一个维度,这是“仅来电号去重”的必然结果,需在报表口径说明中写清楚。\n
四、剔除无效(审批通过时间生效)如何落地
建议新增/明确一张“无效剔除”表(或在工单表冗余必要字段):\n
order_id\ndedup_phone\napproved_at(审批通过时间)\nstatus(通过/撤销/驳回)\napproved_by、reason\n
计算“剔除无效”时:\n
- 先得到“剔除重复”的代表记录集合\n
- 再过滤掉:
status=通过且approved_at已生效 的记录\n
五、同比/环比边界处理(必须写进验收标准)
- 分母为 0:增幅显示 “/”(不可计算),不要显示 0%\n
- 年维度:按需求无同比,显示“/”\n
- 自选区间:\n
- 同比:上年同区间(若无同日→取上年同月最后一日)\n
- 环比:上一段等长区间\n
六、号码反显与审计
- 建议默认脱敏展示;仅特定角色可见全号。\n
- 对“查看全号/导出全号/手动调整咨询量”等操作,记录审计日志(人、时间、IP、变更前后)。\n
全渠道投诉统计 - 简化设计文档
一、设计理念
采用单表聚合设计,通过时间序列编码实现多粒度数据存储,避免复杂的多层表结构。
二、时间序列编码设计
2.1 编码规则
格式:YYYY Q MM DD
位数:4 1 2 2
说明:
- YYYY:4位年份(必填)
- Q:1位季度(1-4,0表示汇总)
- MM:2位月份(01-12,00表示汇总)
- DD:2位日期(01-31,00表示汇总)
2.2 编码示例
| 时间粒度 | 编码示例 | 含义 |
|---|---|---|
| 日粒度 | 2026010311 | 2026年Q1季3月11日 |
| 月粒度 | 2026010300 | 2026年Q1季3月 |
| 季粒度 | 2026010000 | 2026年Q1季 |
| 年粒度 | 2026000000 | 2026年 |
三、最终统计表设计
CREATE TABLE ads_complaint_final_stats (
-- 时间维度
time_code STRING COMMENT '时间编码(YYYYQMMDD)',
year_code STRING COMMENT '年份(YYYY)',
quarter_code STRING COMMENT '季度编码(含汇总标识)',
month_code STRING COMMENT '月份编码(含汇总标识)',
day_code STRING COMMENT '日编码(含汇总标识)',
-- 维度类型
dimension_type STRING COMMENT '维度类型(LINE-条线/DEPT-部门)',
dimension_code STRING COMMENT '维度代码',
dimension_name STRING COMMENT '维度名称',
-- 渠道维度
channel_root STRING COMMENT '渠道根分类(电话/APP/网点等)',
complaint_source STRING COMMENT '投诉来源(二级分类)',
-- 状态维度
is_deleted_flag INT COMMENT '是否删除(0-正常/1-已删除)',
is_effective_flag INT COMMENT '是否生效(0-无效/1-有效)',
-- 统计指标
total_complaint_count BIGINT COMMENT '投诉总量',
distinct_phone_count BIGINT COMMENT '去重后总量(按电话去重)',
-- 元数据
data_granularity STRING COMMENT '数据粒度(Y/Q/M/D)',
etl_time TIMESTAMP COMMENT 'ETL时间',
dt STRING COMMENT '分区日期'
)
COMMENT '全渠道投诉最终统计表'
PARTITIONED BY (dt STRING)
STORED AS ORC
TBLPROPERTIES ('orc.compress'='SNAPPY');
四、数据写入示例
4.1 日粒度数据
-- 日粒度:2026年3月11日
INSERT INTO ads_complaint_final_stats
SELECT
'2026010311' as time_code,
'2026' as year_code,
'202601' as quarter_code, -- 2026年Q1
'202603' as month_code, -- 2026年3月
'11' as day_code,
dimension_type,
dimension_code,
dimension_name,
channel_root,
complaint_source,
is_deleted_flag,
is_effective_flag,
COUNT(*) as total_complaint_count,
COUNT(DISTINCT phone_no) as distinct_phone_count,
'D' as data_granularity,
CURRENT_TIMESTAMP as etl_time,
'20260311' as dt
FROM dwd_complaint_detail
WHERE dt = '20260311'
GROUP BY
dimension_type, dimension_code, dimension_name,
channel_root, complaint_source,
is_deleted_flag, is_effective_flag;
4.2 月粒度数据
-- 月粒度:2026年3月
INSERT INTO ads_complaint_final_stats
SELECT
'2026010300' as time_code, -- 日部分为00
'2026',
'202601',
'202603',
'00',
-- 其他维度同上
'M' as data_granularity,
CURRENT_TIMESTAMP,
'20260331' -- 用月末日期做分区
FROM ...
GROUP BY ...;
4.3 季粒度数据
-- 季粒度:2026年Q1
INSERT INTO ads_complaint_final_stats
SELECT
'2026010000' as time_code, -- 月、日均为00
'2026',
'202601',
'0000', -- 月为0000表示汇总
'00',
'Q' as data_granularity,
...
FROM ...
WHERE quarter_code = '202601';
4.4 年粒度数据
-- 年粒度:2026年
INSERT INTO ads_complaint_final_stats
SELECT
'2026000000' as time_code, -- 季、月、日均为0
'2026',
'0000', -- 季为0000表示汇总
'0000',
'00',
'Y' as data_granularity,
...
FROM ...
WHERE year_code = '2026';
五、查询示例
5.1 查询某日数据
-- 查询2026年3月11日,按条线统计
SELECT
dimension_name,
channel_root,
SUM(CASE WHEN is_effective_flag=1 THEN distinct_phone_count ELSE 0 END) as effective_count,
SUM(distinct_phone_count) as total_count
FROM ads_complaint_final_stats
WHERE time_code = '2026010311' -- 精确日粒度
AND dimension_type = 'LINE'
GROUP BY dimension_name, channel_root;
5.2 查询月度趋势
-- 查询2026年Q1各月趋势
SELECT
time_code,
month_code,
SUM(distinct_phone_count) as monthly_total
FROM ads_complaint_final_stats
WHERE time_code LIKE '20260103%' -- 匹配Q1所有日粒度
AND data_granularity = 'D'
AND dimension_type = 'DEPT'
AND dimension_code = 'BJ001' -- 北京分行
GROUP BY time_code, month_code
ORDER BY time_code;
5.3 同比环比查询
-- 2026年3月 vs 2025年3月(同比)
WITH current_month AS (
SELECT SUM(distinct_phone_count) as cnt
FROM ads_complaint_final_stats
WHERE time_code = '2026010300' -- 2026年3月
AND dimension_type = 'LINE'
AND dimension_code = 'RETAIL'
),
last_year_month AS (
SELECT SUM(distinct_phone_count) as cnt
FROM ads_complaint_final_stats
WHERE time_code = '2025010300' -- 2025年3月
AND dimension_type = 'LINE'
AND dimension_code = 'RETAIL'
)
SELECT
current_month.cnt as current_value,
last_year_month.cnt as last_year_value,
CASE
WHEN last_year_month.cnt = 0 OR last_year_month.cnt IS NULL THEN '/'
ELSE ROUND((current_month.cnt - last_year_month.cnt) * 100.0 / last_year_month.cnt, 2)
END as yoy_ratio
FROM current_month, last_year_month;
六、跑批简化方案
6.1 单表覆盖策略
-- 每日跑批:同时写入所有粒度
INSERT OVERWRITE TABLE ads_complaint_final_stats
PARTITION(dt='${bizdate}')
-- 日粒度
SELECT
CONCAT(year_code, quarter_code, month_code, day_code) as time_code,
-- ... 其他字段
'D' as data_granularity
FROM daily_agg
UNION ALL
-- 月粒度(月末执行)
SELECT
CONCAT(year_code, quarter_code, month_code, '00') as time_code,
'M' as data_granularity
FROM month_agg
WHERE day_code = 'LAST_DAY'
UNION ALL
-- 季粒度(季末执行)
SELECT
CONCAT(year_code, quarter_code, '0000') as time_code,
'Q' as data_granularity
FROM quarter_agg
WHERE month_code = 'LAST_MONTH_OF_QUARTER'
UNION ALL
-- 年粒度(年末执行)
SELECT
CONCAT(year_code, '000000') as time_code,
'Y' as data_granularity
FROM year_agg
WHERE quarter_code = 'Q4';
6.2 调度配置
调度任务:
- 名称: 每日增量统计
频率: 每天
执行: 写入当日粒度和汇总月/季/年(若月末/季末/年末)
- 名称: 月末全量重刷
频率: 每月1号
执行: 重刷上月所有日粒度和月粒度
- 名称: 季末全量重刷
频率: 每季度初
执行: 重刷上季度所有粒度
七、数据查询API
@RestController
@RequestMapping("/api/v1/complaint/stats")
public class ComplaintStatsApi {
@GetMapping("/query")
public Result queryStats(
@RequestParam String startTime, // 2026010100
@RequestParam String endTime, // 2026033100
@RequestParam String granularity, // D/M/Q/Y
@RequestParam String dimensionType,
@RequestParam(required=false) String dimensionCode,
@RequestParam(required=false) Boolean includeYoY,
@RequestParam(required=false) Boolean includeMoM
) {
// 1. 解析时间范围
String timePattern = buildTimePattern(startTime, endTime, granularity);
// 2. 查询数据
List<StatsVO> data = sqlSession.selectList(
"queryTimeSeries",
Map.of(
"timePattern", timePattern,
"dimensionType", dimensionType,
"dimensionCode", dimensionCode,
"dataGranularity", granularity
)
);
// 3. 计算同比环比(如需)
if (includeYoY || includeMoM) {
enrichWithYoYMoM(data, granularity);
}
return Result.success(data);
}
}
八、简化后的优势
- 表结构简单:只需维护1张最终统计表
- 查询高效:直接通过
time_code过滤,无需多层JOIN - 维护成本低:无复杂的层级表关系
- 扩展性好:新增维度只需添加字段
- 时序语义清晰:编码自带时间粒度信息
九、注意事项
- 编码唯一性:确保
time_code+维度组合唯一 - 数据冗余:不同粒度数据共存是设计使然
- 存储空间:相比分表会多存储,但简化了查询
- 索引设计:建议对
time_code、dimension_type建立索引
文档版本:v3.0(简化版)
更新日期:2024-01-xx
责任人:数据架构组
