一次周维度数据放大的排查与修复

📅 2026/7/8 2:18:41
一次周维度数据放大的排查与修复
2026070620260706 一次周维度数据放大的排查与修复最近处理了一个周维度报表异常的问题。现象并不复杂某几个周的数据突然变大了最先被注意到的是库存类指标尤其是 FBA 在库、FBA 在途、冗余库存量这类快照型字段。它们在周维度上的值明显高于按日明细回推出来的结果。而更麻烦的地方在于这套逻辑之前一直在正常跑没有任何改动痕迹问题却是“突然出现”的。这种场景很典型。真正难的不是写修复 SQL而是先判断问题到底出在什么层原始数据、明细层、周聚合层还是下游展示层。这篇文章记录一下这次排查的完整过程以及最后是怎么把问题彻底收敛掉的。背景模型为了方便描述这里把几张表做一下脱敏。最底层是一张日报明细源表可以理解为按“日期 站点 ASIN MSKU”产出的事实表里面包含销量、销售额、库存、在途等指标。记作DWD_DAILY_MSKU。在这之上有一张日粒度业务明细表记作ADS_REALTIME_DETAIL。它会把日报源表和标签宽表、月报、汇率等基础资料做关联补齐主推标签、利润标签、新老 listing、品线、运营小组等字段同时保留日粒度的库存与费用。再往上是一张周维度汇总表记作ADS_REALTIME_WEEKLY。它不是简单地对日表做group by 周数而是拆成两块一块是按周聚合销量、销售额、毛利、广告费等“周期累计”指标另一块是从日明细里挑选某一天作为该周库存快照再把这部分库存字段挂接回周汇总结果。这类设计本身没有问题。周累计指标和周末快照指标本来就不是一个口径分开算再合并是常见做法。真正的问题出在“如何定义一周”以及“如何把两边重新关联起来”。最初的怀疑是不是源表本身重复了问题刚出现时第一反应通常都是原始数据脏了。因为从经验上说逻辑跑了很久才突然出问题很多时候并不是 ETL 代码当天坏了而是源数据出现了之前没覆盖到的形态。比如某个基础资料重复维护、某个汇率月缺失、某个映射表突然出现一对多。所以第一步没有急着改周维度 ETL而是先做了两层校验。先检查日报源表在业务主键上的唯一性也就是类似下面这个粒度日期 站点 ASIN MSKU结果确实发现过少量重复记录但只集中在极少数商品和站点上而且重复量非常有限解释不了整周库存被成倍放大的现象。接着又把日报源表和日明细表做了同日总量比对。把某一天的在库 / 在途在源表汇总后与ADS_REALTIME_DETAIL里的FBA在库 / FBA在途做总量对账结果完全一致。这一步其实很关键。它至少说明两件事第一源表虽然存在个别重复但不是这次放大的主因。第二日明细层并没有在库存字段上继续放大日报源表到日明细表这段链路整体是稳定的。也就是说问题大概率出在ADS_REALTIME_DETAIL - ADS_REALTIME_WEEKLY这一段。第二步把问题缩到周维度 ETL周维度的 ETL 可以拆成三段来看。第一段把日明细聚合成周级别的销量、销售额、毛利等指标。第二段从日明细中选出每周一条库存快照。历史周通常取周四最新周则根据当前日期动态决定取周四还是“最大日期前一天”。第三段把“周累计指标”和“周库存快照”按照标签维度重新关联形成最终周报。这三段里真正危险的是第三段。因为只要左右两边的粒度定义不完全一致left join就会把右表快照重复挂到左表多行上导致库存被放大。所以我没有先看 SQL 长什么样而是先做了一件更机械但更可靠的事分别检查左右两边的唯一键。如果左边一组 join key 对应两行右边一组 join key 对应一行那么 join 后库存一定会被挂两次。这和业务字段含义无关纯粹是关系代数层面的重复匹配。真正的症状同一个周数下出现了两个日期范围很快就定位到异常点了。在周累计这一侧同一个周数 标签组合竟然出现了两条记录而且两条记录的日期范围不一样。一条是 2025 年对应的那一周另一条是 2026 年对应的那一周。两条数据周数一样但实际不是同一周。比如同样是第 27 周可能同时存在•2025/6/29 - 2025/7/5•2026/6/28 - 2026/7/4如果 ETL 在聚合或关联时只使用了周数那么这两条记录就会被当成同一周处理。更糟的是库存快照这一侧本身是按周数和标签汇总后唯一的所以它没有“自己重复”而是在 join 回去的时候被左表的两条周累计记录各挂了一次最终库存翻倍。这也解释了为什么最先暴露出来的是库存字段而不是销售额。销售额这类累计字段是在左表内部自己聚合出来的而库存字段是右表快照通过 join 挂回来的。只要 join key 不完整库存比销售额更容易出现“右值重复挂载”的问题。为什么之前一直没出问题这个问题之所以具有迷惑性是因为逻辑本身并不是“突然写错了”而是数据时间范围跨到了之前没有覆盖到的区间。只按周数汇总在单年度数据里通常是安全的。因为一年内第 27 周只会出现一次。但一旦报表覆盖跨年数据25 年第 27 周和 26 年第 27 周就同时存在了。这个时候“周数”就不再是唯一键而只是一个不完整标识。这类问题有一个典型特征它不是全量历史都错而是到了某个具体时间点才开始爆发。往前看一切正常往后看会越来越严重。这次就是这样。刚好报表时间范围跨过了去年的同周区间所以问题第一次被显性暴露出来。再往后每过一周同样的问题都会继续出现。修复思路不是“补丁”而是重定义周键这种问题不能靠distinct或者事后去重来压因为那只是把放大的结果遮住没有解决周定义本身不完整的问题。真正的修复只有一个方向把“周”的唯一键补全。原来的做法本质上把周键定义成了周数修复之后周键至少要扩展成周数 日期范围 周范围更稳妥一点直接把日期范围作为事实上的周标识。因为它天然区分了跨年场景不需要再猜这个周数属于哪个年份。这次周维度的修改核心就是围绕这个思路展开的。第一处修改周累计侧的聚合键原来的周累计聚合逻辑在某些地方虽然保留了日期范围字段但在唯一性上仍然把周数 标签当成主键使用。修复后周累计侧显式把以下字段都纳入聚合键•周数•日期范围•周范围• 标签字段本身同时对标签字段统一做了trim coalesce处理避免空字符串、空值、前后空格导致同一业务值被拆成多组。工程上这一步的意义很直接不是让 SQL 看起来更“严谨”而是让左表真的具备“这一行到底代表哪一周”的完整身份。第二处修改库存快照侧的快照键库存快照侧原本就比周累计侧更接近唯一但它对“最新周”的处理存在一个动态分支如果当前周还没到周四就取“最大日期前一天”否则取周四。这段逻辑本身没有错错的是选出快照之后周的标识没有完全带出来。修复后库存快照这边也保留完整的•周数•日期范围•周范围• 标签字段这样快照记录在 join 时不再只是“第 27 周 某标签”而是“2026/6/28-2026/7/4 这一周 某标签”。第三处修改最终 join key这是最关键的一步。原先库存挂接回周累计结果时join key 不完整导致右表快照被左表多行命中。修复后join 条件统一改成周字段用完整的周数 日期范围 周范围标签字段用标准化后的值左右两边完全按同一套键体系关联。一旦这一步成立库存就不可能再凭空放大。因为右表某一周某一标签的快照只会命中左表同一周同一标签的那一行。中间有一个容易误判的点是不是日明细也坏了排查过程中还有一个容易绕进去的点。在修周维度时很容易顺手怀疑日明细里的周数和日期范围计算是不是也有问题尤其是跨年周。因为如果日明细本身就把周定义算错了周维度当然也会错。所以当时又把日明细的周字段重新检查了一遍包括• 周数计算逻辑• 日期范围生成逻辑• 跨年周归属• 周结束日/周起始日口径最后确认日明细在修正之后总量对账已经稳定日报源表到日明细的库存与销量都没有放大。ETL 末段虽然新增了周起止日计算和周归属逻辑但行数和核心指标总量在改动前后是一致的。也就是说周维度的问题最终还是周维度自身的键设计问题而不是日明细层继续放大。这一步的结论非常重要因为它让修复范围收敛了。否则很容易一层层往上怀疑最后把整个链路都推翻重做。修完之后怎么验证修 ETL 最怕的是“看起来好了”但没有被系统验证过。所以这次没有只看某一个问题周而是做了几类 end-to-end 对账。第一类是周快照对账。把日明细按修复后的周快照选取逻辑单独算出每周的库存快照总量再和最终周维度表逐周比对。如果 ETL 正常那么两边的FBA在库 / FBA在途 / 冗余库存量 / 前三周销量都应该完全一致。第二类是异常周专项核验。重点检查之前出问题的周比如第 27 周、第 28 周确认修复后不再出现“同周数对应两个日期范围同时参与 join”的情况。第三类是全链路总量核验。把日明细经过周维度 ETL 后的最终结果再按相同维度回卷与原始输入进行逐字段对账。库存类指标、费用类指标、销量类指标都需要过一遍。最终结果是之前出现放大的周已经恢复正常库存快照与日明细回推结果一致周维度 end-to-end 对账也没有再出现差值。这次问题的本质如果只用一句话总结这次问题的本质其实很简单周维度 ETL 把“周数”当成了“周唯一键”但在跨年场景下周数并不唯一。后面所有症状——库存翻倍、只在部分周暴露、之前一直正常、越往后越严重——其实都只是这个根因的外在表现。这个问题和源数据重复、汇率缺失、空值处理都不是一类问题。它更像是一个建模层面的键设计缺陷在单年度看不出来一旦跨年度缺陷就会被数据自然放大。一个额外的工程结论这次排查还有一个很实用的经验。对于周、月这类时间周期不要把“数字编号”直接当事实键使用。周数、月份编号、季度编号这些字段适合作为展示字段但不适合作为唯一关联键。更稳的做法是• 周用日期范围或周起始日/周结束日• 月用月份首日• 季度用季度起始日编号字段可以保留但只适合排序、展示、筛选不适合作为跨层关联的唯一标识。在报表开发里这种问题很隐蔽因为它通常不是“今天就错”而是“某个时间点以后突然开始错”。一旦系统跑得足够久这类问题几乎一定会遇到。这次周维度的问题本质上就是把一个展示字段当成了事实主键来用。修复之后问题本身不复杂但这个教训值得记下来。