FDE 落地手记 · 08
在把经营数据接给 AI 的过程中,我越来越重视一个看起来不那么“智能”的问题:模型查到的这个数,到底代表什么,它有没有资格被拿来比较和决策?
接口调用成功,只能说明拿到了响应。订单重复导入、售后归属错位、商品映射缺失、来源没有采全,都可能藏在一个格式正确的结果里。AI 甚至会把这些问题包装成一份很流畅的分析。
围绕 yuce_cube 的实践,我逐渐把工作重心从“把数据放进库里”,转向“让每个结果带着可解释的来源、口径和使用边界”。其中已经有数据清洗,也有分层建模、版本选择和质量状态管理。
这篇复盘依据实际数据库结构、视图定义与状态核验展开。文中会明确区分已经被专题使用的能力、新建的治理结构,以及还需要验收的环节;不把建表等同于上线,也不把有数据等同于数据完整。
1. 数据清洗的目标,是让业务事实保持一致
传统意义上的清洗,常常指去空格、转日期、删重复。这些操作必要,但经营分析里更难的问题是业务语义。
一个订单可能有多个商品、多次发货、多张售后单;同一张报表可能重复下载,也可能因为退款确认而发生修订。如果只用“订单号”去重,真实的商品明细会丢失。如果把所有历史导出直接相加,业务量又会被重复放大。
因此,我会先回答三个问题:一行代表什么事件;什么组合能唯一识别它;同一事实的新版本出现时,旧版本应该如何保留和退出当前统计。
| 问题 | 容易出现的错误 | 数据层应该承担的职责 |
|---|---|---|
| 同一文件重复导入 | 总量越来越大 | 识别采集批次、文件与业务记录 |
| 一笔订单包含多个商品 | 按订单号去重后丢明细 | 按业务粒度定义唯一键 |
| 同一天的数据重新导出 | 新旧版本一起参与汇总 | 明确当前有效批次 |
| 字段缺失或转换失败 | 被当成真实的零 | 保留 NULL 与异常状态 |
| ERP 与平台都叫“销量” | 不同口径被直接相加 | 明确来源、单位与日期归属 |
清洗不是尽可能把表变得整齐,而是避免处理过程改变事实的含义。
2. ODS、DWD、DIM、DWS、ADS,分别解决什么问题
我用下面这组分工理解数据底座。它是一种职责划分,不要求每个系统必须创建五个数据库。
| 层次 | 核心职责 | 对经营分析的价值 |
|---|---|---|
| ODS:原始数据层 | 留存来源响应、文件、导入批次及必要元数据 | 能追查“原来拿到了什么” |
| DWD:明细数据层 | 统一类型、粒度、业务键和事件含义 | 同一种事实可以按同一种方式查询 |
| DIM:维度层 | 管理店铺、商品、渠道、来源契约等解释信息 | 知道记录属于谁、按什么规则归类 |
| DWS:汇总服务层 | 沉淀可复用的渠道、商品、日期等汇总 | 多个应用复用一致的指标 |
| ADS:应用数据层 | 面向经营看板、专题和业务工具组织结果 | 输出适合使用场景的数据产品 |

DIM 是参与关联的维度,不是 DWD 处理完以后必须顺次经过的一道流水线。DWS 与 ADS 也可能由视图、服务或应用侧计算承担,不能只看表名前缀判断分层是否完善。
在 yuce_cube 中,已经能看到 ODS、DWD、DIM 以及应用相关结构。同时仍有历史业务表和专题视图。这种新旧并存,是渐进治理的真实状态。下一步应该围绕具体链路验收,而不是为了目录整齐而一次性重命名所有表。
3. 原始数据留存,让问题可以被重新解释
以 ERP 出库为例,ods_jst_out_orders_raw 保留接口标识、业务键、同步批次、接收时间、载荷哈希与原始 JSON。相应的 DWD 表再表达店铺、渠道、状态、金额、数量和业务日期等标准字段。
这让“来源给出的值”和“经过业务处理后的值”有机会被分开检查。发现一个渠道分类异常时,可以回到来源载荷核对,而不必只相信应用页面上的最终结果。
这里有一个容易夸大的地方:有 raw_json 和哈希,并不自动意味着已经建立了不可变的全量历史档案。 当前这类原始表还存在按接口与业务键设置的唯一约束。到底是覆盖当前版本,还是另外保存历史版本,需要结合采集程序和留存策略验证。
文件治理也有类似边界。ods_source_file_manifest 已有文件哈希、解析器版本、文件格式、字段块数量和有序字段块摘要,核验中也能看到 verified_fields 状态。这提供了文件与解析结果核对的基础,但“字段验证通过”不等于“所有业务数据已经采全”。
我会把四种验证分开:文件身份是否一致、解析是否完整、业务范围是否齐全、业务汇总是否对账。每一种验证都要有自己的证据。
4. 去重需要同时处理“批次内”和“批次间”
退货专题提供了一个比较具体的例子。
dwd_returns_facts 的唯一约束包含 batch_id + record_key。它能够约束同一批次里的同一个记录键不要重复出现,但不同批次仍然可以保存同一业务事实的不同版本。
如果查询把所有批次直接相加,唯一键并不能阻止重复统计。因此,还需要第二层选择:哪个批次是当前统计应当使用的版本。
dwd_returns_source_partition 按来源、业务日期和范围保存批次引用。专题库中的 v_return_current_facts 会关联有效分区,按来源、日期和批次取得事实明细。其实际定义使用分区中的来源、日期、批次组合进行关联;本次检查到的范围值为单一范围。未来扩展多范围时,还需要重新检验范围隔离,不能直接假定现有视图已支持所有场景。
可以用一个虚构的小例子理解两层机制:
- 第一次导入某日售后文件,产生批次 A。
- 再次处理 A 时,批次内唯一键约束重复记录。
- 来源修订后生成批次 B,保留新的事实版本。
- 有效分区指向 B,当前查询读取 B;A 可用于追溯。
其中,“B 通过哪些校验后才能替换 A”,必须由写入流程保证。数据库存在分区表,只能证明具备表达有效版本的结构;并不能证明切换前的质量闸门已经完整实现。
这也解释了为什么我不会把去重简单描述为一条 DISTINCT。去重的核心是记录身份和版本策略。
5. NULL 是一种业务状态,不能为了好看而抹掉
在退货专题的标准化视图中,数值字段会先检查是否符合数值格式,再转换为明确精度的 DECIMAL;不符合条件时保留 NULL。JSON 的空值也被单独处理,而不是直接转换成看似有效的字符串或数字。
这是一项很实用的清洗能力。它防止“未知”“空白”“无法解析”悄悄变成 0。
但仅保留 NULL 仍不够,因为 SUM 等聚合会忽略空值。即使部分记录没有有效数量,查询也可能得到一个看上去很正常的总数。
因此,统计结果至少需要同时保留:总记录数、有效数值记录数、缺失记录数和可用值合计。缺失率的分母也要说明,不能把记录覆盖率冒充金额覆盖率。
下面是使用现有专题字段的检查示例。它只能检查已返回行中的数值缺失,不能单独判断整日数据是否缺失:
SELECT
business_date,
COUNT(*) AS observed_rows,
COUNT(shipped_qty) AS valid_qty_rows,
SUM(shipped_qty IS NULL) AS missing_qty_rows,
SUM(shipped_qty) AS observed_shipped_qty
FROM jderp_return_bi.v_return_erp_sales_ship
WHERE business_date >= :start_date
AND business_date < :end_date_exclusive
GROUP BY business_date;
没有出现的日期,需要用预期日期集合与来源覆盖表再做检查。否则,“没有返回一行”和“返回一行且数量为零”仍然无法区分。
负数也不能一律清掉。财务冲销、退款或更正可能产生合法负值;是否允许负数,应由指标定义决定。清洗的任务是识别含义,而不是把所有“不好看”的值消灭。
6. 来源覆盖和观察窗口,决定结果能不能比较
inventory_bi_source_day_coverage 已经能够按来源和业务日期记录覆盖状态,并区分 data 与 unknown。这比仅保存一个“最后更新时间”更接近分析需要。
不过,data 表示观察到数据,并不自动意味着完整。某个来源的最早日期和最晚日期之间,也不一定每天都有记录。要验证完整性,应该建立“预期来源 × 预期范围 × 日期”的检查集合,再逐项比对。
退货分析还多一层时间问题。发货日期、售后申请日期、退货入库日期、退款到账日期,对应不同事件。把“本月退货数量”除以“本月发货数量”,得到的是期间事件比,不能直接解释成“本月发货订单最终有多少退了”。
如果要比较发货后的退货表现,我会先固定订单或件数口径,再把退货关联回对应发货批次,并设置统一观察窗口。例如按自然日定义“发货后 15 天内”,就需要明确是否包含第 0 天和第 15 天;最近发货、尚未走完窗口的批次,应标为未成熟。
对无法关联原发货记录的来源,仍可以分析期间事件,但需要单列。业务事实无法支持的比较,不应靠提示词补出来。
7. 来源契约,把“这份报表是什么”写进机器可读规则
dim_source_contract 的定义中,已经出现平台、报表、范围、过滤条件、日期区间、时区、日期依据和字段版本等信息。这比只保存文件名更有价值。
同样叫“商品表现”的两份报表,如果一家按成交日期、一家按结算日期;一家覆盖全部店铺、一家只覆盖筛选后的店铺,它们就不应该因为字段相似而被当成同一种数据。
契约的作用,是把这些前提保存下来,并让后续流程能够判断兼容性。不过本次检查到的契约样本中仍有 legacy_unknown 范围和未填写的字段版本。它准确地表达了历史数据尚待确认的部分,不能被包装成一份已经完备的来源规范。
我更愿意保留这种不确定性:先记录已知来源,再补齐范围和版本,然后决定是否允许进入正式指标。只有显式承认未知,后续补证才有位置可放。
8. 已经落地的能力,与需要继续补齐的能力
这次核验最有价值的结论,并不是“已经建立了一套完美数仓”,而是能够准确描述进展。
| 能力 | 本次实际看到的证据 | 可以得出的结论 |
|---|---|---|
| 批次内记录约束 | 退货明细有批次与记录键的唯一约束 | 已有防止同批次重复记录的结构 |
| 当前版本选择 | 退货分区已有数据,专题视图关联有效批次 | 专题已采用有效批次读取机制 |
| 类型与空值处理 | 视图包含数值格式检查、DECIMAL 转换、NULL 处理 | 已有明确的标准化逻辑 |
| 来源覆盖 | 覆盖表存在 data 与 unknown 状态 | 可以表达有数据和来源未知的区别 |
| 统一批次与消费机制 | 通用批次记录处于 shadow,通用分区和消费状态表未见记录 | 属于待进一步验证的治理结构 |
| 批次完整性 | 检查到的退货导入批次 complete 均未标为通过 | 不能宣称来源完整性已全部验收 |
这里的 complete = 0 不能直接翻译成“这一批数据一定错误”。更准确的解释是:完整性尚未被确认。能否用于参考、是否需要阻断正式比较,要由业务规则决定。
下一步我会优先补三件事:来源完整性与控制合计、有效批次切换前的质量闸门、历史重放和失败恢复验收。先让一条完整链路经得住检查,再把方法推广到更多来源。
9. 对 FDE 来说,交付的是一条可解释的数据链路
这项实践让我更清楚地理解了 FDE 工作方式:先进入业务现场,弄明白“销量”“退货”“利润”分别指什么,再把这些认识转成数据结构、检查规则和可复用的服务。
工程上的价值,既包括采集和清洗,也包括发现错误比较、保留缺失状态、解释口径差异,以及让业务知道一个数字适合做什么判断。
当这些基础逐渐稳定,同一份数据才能服务经营看板、专题分析和 AI 工具。MCP 可以让 Agent 更方便地调用它们,但可靠性的来源,仍然是事实、口径和验证过程。
下一篇继续讨论:从“能查数据”到“能信结果”:经营分析 MCP 的可靠性设计。
延伸阅读:企业 AI 的底座:Python、MySQL 与 MCP 的连接思路;MySQL 唯一约束与索引;MySQL 对 NULL 的处理。