“这个月库存是多少?”至少可能指月末余额、日均余额、期间最高余额,或累计入库量。一个能自动生成 SQL 的系统,如果没有先区分这些含义,就可能返回语法正确、数字整齐而业务含义错误的答案。本文聚焦库存日快照,提出一套从指标定义到异常验收的工程方法;算例均为构造数据,不代表壹典客户或实际测试。
先固定余额的粒度和可加方向
余额描述某个时点的状态,入库和出库描述一段期间内的变化。Kimball 将只能沿部分维度求和的事实称为半可加事实,余额是典型例子;比率则通常需要先汇总组成部分,再计算结果。[1] 对库存而言,同一商品、相同单位、互不重叠的仓库在同一时点可以合计,但连续几天的余额相加并不等于期间库存。
先写清日快照的一行代表什么。例如“业务日期、仓库、商品、批次、库存状态”共同确定一行,记录统一截止时点的现存数量。可用量、冻结量和在途量另有定义,不能随意相加;批次是否属于互斥集合、仓间调拨是否重复占有,也要由业务规则确认。不同商品的件数即使能做算术相加,也不一定有解释价值。
企业 Text-to-SQL 的业务口径需要先于查询生成确定。库存场景还应增加时间聚合规则:期末值、日均值、最高日末值和流量分别对应不同算子。用户没有说明时,交互式问数应澄清;自动报表则使用事先批准的指标名称和定义,并在结果中显示,不能让模型临时猜一个最像的意思。
四天数据足以暴露平均值分母的问题
构造示例:同一商品、同一仓库,第 1 日末库存 100 件,第 2 日快照缺失,第 3 日末 60 件,第 4 日末 80 件。已知记录求和得到 240,没有“这四天库存”的直接含义;三个已知日末值平均为 80,但它仅代表三个观测日,不能自动标成四日日均。
把缺失日补零,会得到 60;把第 1 日值沿用到第 2 日,会得到 85。这两个数字都依赖额外假设。缺行可能来自采集失败,也可能来自系统只在变化时记录;它本身既不证明库存为零,也不证明没有变化。若无法证明第 2 日状态,完整四日日均应标为不可确定,同时报告已观测 3 天、应有 4 天。
PostgreSQL 18 文档说明 AVG 计算非空输入的平均值,SUM 在没有输入行时返回空值而非零。[3] 因此,仅把缺失日期扩展成空值,再调用 AVG,仍会自动缩小分母。补齐日期与规定缺失处理是两件事:前者暴露缺口,后者决定是否允许计算。使用 COALESCE 转成零之前,必须有业务依据。
期末库存要先选时点,再做空间汇总
精确日末口径应先按请求的业务日期,确认每个应纳入的仓库和商品都有有效快照,再汇总。不能在整个表上取最大日期后求和:某个仓库更新较快时,其他仓库可能因没有当天行而消失。也不能分别取各实体最新记录就称为同一天的准确库存,因为这些记录可能来自不同日期。
如果业务接受“截至指定时点的最近可用值”,应另命名为估计或最近观测口径。按实体选择不晚于截止时点的最近有效记录,保留源日期和陈旧时长,超过批准时限则标为缺失。陈旧阈值由业务容忍度决定,不能直接套一个所谓行业标准。汇总页同时展示哪些实体被排除,避免只看上报成功的仓库。
dbt 的度量定义文档提供 non_additive_dimension、window_choice 和 window_groupings,用于表达不应沿某维度直接聚合的度量及选择窗口。[2] 它说明语义层可以承载这类规则,但配置字段不是完整性保证。使用前应核对部署版本,并检查生成查询究竟按整个集合还是按实体选择日期;“取日期最大值”也不是“取库存最大值”。
日均与时间加权平均不能混用
若指标定义为完整期间每日末库存的算术平均,先得到每天同一范围的有效总库存,再按应有天数计算。对前述四天,若后来核实第 2 日末为 120,则日均为(100+120+60+80)÷4=90。若只保留期初和期末,二者平均得到的另一个数只是简化口径,不能冒充每日观测的平均值。
若输入是每次变动后的状态记录,记录间隔不等,按记录条数求平均会偏向变化频繁的时段。构造示例:在一个 24 小时区间内,库存 100 件持续 18 小时、40 件持续 6 小时;时间加权平均为(100×18+40×6)÷24=85 件,两个记录值的算术平均却是 70 件。两者回答的是不同问题。
时间加权要求知道区间起点的有效状态、完整的变更序列、时间边界及持续时长;任何一项缺失,都不能通过向前填充自动得到可靠结果。日末采样也不能还原白天的全部波动。业务只需要日末平均时无需为小时级精度付出额外成本;若用于缺货暴露时长,则应选择足以支持该目标的事件或更细粒度记录。
把查询拆成可以检查的步骤
第一步,解析请求为固定指标、期间、业务时区、实体范围、单位和缺失策略。库存截止点可能不是自然日午夜,应该来自已批准规则。第二步,从有效的商品和仓库清单建立应有实体与日期集合,考虑开仓、停用和商品生效区间;不应要求尚未存在的实体在过去产生快照。
第三步,对原始快照按业务主键查重,处理更正版本并保留选择依据。第四步,将快照对齐到应有集合,分别标记已知零值、有效非零值、缺失和超时。第五步,执行经过批准的期末或平均规则,再关联展示维度。维度有历史版本时,要按适用时间唯一匹配,防止一条库存记录因关联多行而被放大。
第六步,输出数值时附带口径、截止时间、覆盖范围和异常数。对于只覆盖部分仓库的结果,应明确叫“已上报仓库小计”,不要标成企业总库存。模型负责把这些字段解释给读者,不应在解释阶段自行补零、改分母或把最近观测重新描述为精确期末值。
若没有可靠快照,另一条路线是用可信期初余额加完整入出库及调整流水重建状态。它的成本是处理迟到、撤销、更正和去重,并定期与盘点或权威账面值核对。两种路线都需要可追溯证据;选哪一种,应取决于源系统实际保证,而非模型更容易生成哪段查询。
周转类比率先统一期间和计量基础
如果进一步计算库存周转,先确定业务采用哪一套定义。例如采用期间出库成本除以同期平均库存价值时,分子与分母必须使用兼容的成本口径、币种、实体范围和期间。这里讨论的是工程一致性,不给出会计政策或统一行业阈值。销售金额与库存件数相除得到的数字不能解释为同一个指标。
跨仓库汇总时,应先汇总兼容口径的分子和分母再求比率,不应简单平均各仓比率。[1] 分母为零、为负或不完整时,返回明确状态并交由业务规则处理;不要让模型把除零异常改写成“周转极高”。未完成期间也不能不作说明就与完整月份比较。
验收既测数字,也测拒绝给出完整结论的能力
用少量可人工复核的数据建立确定性用例。完整的四日日末 100、120、60、80,应得到期末 80、日均 90;删除第 2 日后,严格完整口径的四日日均应不可确定,覆盖率为 3÷4。若第 2 日是真实零库存且有有效记录,日均才是 60。缺行和零值必须走不同分支。
再加入两个仓库:A 在目标日有 80,B 只有前一日 20。精确期末总量应提示 B 缺失,不能把 80 当成完整合计;最近可用口径若被允许,可给出 100,但必须暴露 B 的日期和陈旧状态。添加一条重复快照或让维度关联重复,验收应发现主键或基数异常,而不是输出翻倍结果。
时间维度的测试还包括:按日结果不能直接相加成月末余额;在范围不重叠、同单位且同时点的前提下,分仓合计应等于整体余额;调换原始记录顺序不应改变结果。对时间加权构造例,应得到 85,而非 70。上述数值只是示例的确定答案,不是性能实测。
运行监控应分别记录有效快照覆盖率、超时实体数量和更正后的差异。覆盖率的分母来自应有实体日期集合,不能来自已经收到的行数;若高价值库存集中在少数实体,还可另列经批准权重的覆盖指标,避免数量覆盖掩盖业务暴露。先把这些规则固化,再让智能问数调用,才能稳定回答库存问题。
