# 03 · 步骤二:数据现状与本体层构建 > 目标:摸清数据家底,建立从原始数据到指标体系的映射——本体层是FDE双向沉淀的核心载体。 `[Echo+Delta]` ## 1. 原始数据导入 ### 1.1 数据源 | 表名 | 数据来源 | 关键字段 | 数据量级 | |------|---------|---------|---------| | `bill_records` | 收银系统导出 | c003(门店) c005(账单号) c009(消费) c068(优惠) c114(实收) c175(下单时间) c176(结账时间) c191(收银员) | 千万级/月 | | `salary_detail_records` | 薪资系统导出 | org_level5(门店) salary_period(薪资周期) actual_attend(实际出勤) actual_hours(实际工时) | 万级/月 | | `attendance_records` | 考勤系统导出 | department(部门路径) position(岗位) day_01~day_31(每日打卡) | 万级/月 | ### 1.2 导入关键点 - **字段映射**:原始Excel列名(c001~c200)需建立映射文档,明确每列含义 - **日期格式**:`salary_period` 格式为中文"2026年4月",非"2026-04",查询时需用 `to_char($1::date, 'YYYY"年"FMMM"月"')` - **空值处理**:实收字段 c114 可能为空字符串,需 `COALESCE(NULLIF(c114,'')::numeric, 0)` - **数据去重**:导入时按 record_id + import_id 去重 ### 1.3 门店名映射 不同数据源门店名不一致,需维护 `store_name_mapping` 映射表: ```sql CREATE TABLE public.store_name_mapping ( salary_name TEXT, -- 薪资/考勤系统中的名称 bill_name TEXT -- 账单系统中的名称 ); ``` **典型映射**: | salary_name | bill_name | |-------------|-----------| | 双安店 | 双安总店 | | 百子湾店 | 百子湾路店 | | 安宁庄快手店 | 安宁庄快手 | | 海淀大街店 | 海淀大街 | | 哈马尔罕大钟寺店 | 大钟寺店 | | 阿里疆(温泉路店) | 温泉店 | **映射查找模式**(代码中复用): ```typescript // 构建查找表:同时用原名和映射名 const staffLookup: Record = {} for (const [store, data] of Object.entries(staffSummary)) { staffLookup[store] = data // 原名 const mapped = nameMap[store] if (mapped && mapped !== store) { staffLookup[mapped] = data // 映射名 } } ``` ## 2. 物化视图体系 ### 2.1 核心物化视图 | 视图名 | Schema | 用途 | 关键字段 | |--------|--------|------|---------| | `mv_store_risk_rating_monthly` | analytics | 门店风险评级 | store_code, store_name, risk_level, received, month_start | | `mv_store_platform_economics_monthly` | analytics | 平台经济性 | store_code, meituan_received, taobao_received, jd_received | | `mv_bill_hourly` | public | 小时客流 | store_name, hour, bills, avg_guests | | `mv_risk_anomaly` | public | 异常账单 | store_name, bill_no, consumption, anomaly_reason, month | | `mv_risk_zero` | public | 零实收 | store_name, bill_no, zero_received_type, month | | `mv_risk_cashier` | public | 收银员风险 | store_name, cashier, bill_count, anomaly_bills, month | | `mv_channel_daily` | public | 支付渠道 | business_date, cash, alipay, wechat, meituan, month | | `mv_time_hourly` | public | 时间维度汇总 | closing_hour, bill_count, received, month | ### 2.2 物化视图管理要点 1. **Schema前缀**:部分视图在 `analytics` schema,部分在 `public` schema,代码中需注意 2. **刷新机制**:物化视图需手动刷新 `REFRESH MATERIALIZED VIEW`,不自动更新 3. **索引**:重建物化视图后需重新创建索引 4. **阈值调优**:异常判断阈值需根据业务调整(如"消费-优惠与实收不平"阈值从0.05元提高到1元) ### 2.3 异常账单阈值设计 ```sql -- 异常类型判断(CASE WHEN顺序重要) CASE WHEN consumption > 0 AND received = 0 THEN '有消费无实收' WHEN discount > consumption THEN '优惠大于消费' WHEN abs(consumption - discount - received) > 1 THEN '消费-优惠与实收不平' ELSE NULL END ``` **关键教训**:阈值0.05元太严格,会将舍入差异标为异常。建议初始阈值设为1元,后续根据数据分布调整。 ## 3. 数据质量校验 ### 3.1 校验流程 1. **数据范围**:`SELECT DISTINCT month FROM bill_records ORDER BY month` 确认最新月份 2. **门店数**:`SELECT count(DISTINCT c003) FROM bill_records WHERE month = '2026-04'` 3. **金额一致性**:`SELECT sum(c009), sum(c068), sum(c114)` 对比前后端 4. **物化视图刷新状态**:对比物化视图和原始表的记录数 ### 3.2 常见数据问题 | 问题 | 症状 | 排查方法 | |------|------|---------| | 物化视图stale | API数据与数据库不一致 | 直接查原始表对比物化视图 | | 月份格式不匹配 | 查询返回0条 | 检查 salary_period 实际格式 | | 门店名不一致 | 部分门店无数据 | 对比不同表的门店名列表 | | Schema前缀错误 | `relation does not exist` | 检查 `\dn` 和 `pg_matviews` | | 列不存在 | `column does not exist` | 检查 `information_schema.columns` |