为什么 BI 系统会说谎:三种致命的数据聚合错误
BI 工具中的指标常常显示出虚假的精确性。在实际应用中,一旦数据从原始交易转向预聚合的数据集市,系统性扭曲就会出现——有时误差高达数十个百分点。这不是 bug,而是速度与准确性之间的架构权衡。让我们拆解三种关键的数学陷阱,这些陷阱让分析对决策变得危险。
错误一:平均平均值——一场数学灾难
平均收据金额、平均请求处理时间、平均订单成本——这些指标是非加性的。你不能简单地对已经平均过的值再取平均。经典例子:BI 系统取一月、二月和三月的各门店平均收据金额,相加后除以三得到季度数据。公式如下:
SELECT
region_name,
EXTRACT(QUARTER FROM year_month) AS quarter,
AVG(avg_receipt_amount) AS total_average_check
FROM dwh.store_monthly_aggregated
WHERE EXTRACT(QUARTER FROM year_month) = 1
GROUP BY region_name, EXTRACT(QUARTER FROM year_month);
问题是 A/B + C/D ≠ (A+B)/(C+D)。一家每天 10,000 笔收据的大卖场在计算中与每天 50 笔的小亭子权重相同。一个销售高峰月与一个“死月”被等同对待。在真实数据中,这会导致 -6% 到 +7% 的偏差。在零售业,每一个平均收据金额的百分点提升都意味着数百万卢布,这样的扭曲是不可接受的。最糟糕的是,当分母的巧合偶然给出正确结果时——错误就会在 DWH 中被固化成“正确”。
错误二:过滤上下文变化时的 Count Distinct
计算唯一对象——客户、收据、产品——在改变明细级别时会崩溃。想象三笔交易:
- 收据 #101 | 杂货
- 收据 #101 | 饮料
- 收据 #102 | 饮料
真实唯一收据数——2。但如果数据按类别预聚合:
- 杂货:1 笔收据 (#101)
- 饮料:2 笔收据 (#101, #102)
然后在 Power BI 或 SQL 引擎中折叠层次结构时,DAX 执行简单求和:1 + 2 = 3。唯一性丢失了。系统双重计算了收据 #101。这个错误会立即扭曲平均收据金额、转化率、LTV——所有依赖唯一实体计数的指标。用户甚至没意识到自己改变了视图——就得到了假数字。
错误三:半加性度量和僵化日历
库存余额、账户余额、活跃用户数——这些是快照指标。它们可以在空间(门店、区域)上求和,但不能在时间上求和。为了规避此问题,开发者使用 DAX 中的 LASTNONBLANK 或 MDX 中的 NonEmpty 等函数:
Balance on konets perioda =
CALCULATE (SUM('Inventory'[Balance]),
LASTNONBLANK ('Calendar'[Date],
CALCULATE(SUM('Inventory'[Balance]))
))
这会产生三个问题:
- 性能:引擎在每次过滤变化时迭代层次结构中的所有日期。大数据量时——冻结。
- 现实故障:如果 30 号收银机未能传输数据,系统会在 31 号显示零余额。价值数十万卢布的商品“消失”。
- 代码复杂度:用户必须是开发者。多个业务日历时,公式变得难以阅读且脆弱。
关键要点
- 聚合杀死准确性。为速度预压缩数据是一种隐含接受的权衡,却未理解其后果。
- 非加性指标需要原始数据。任何平均值、中位数、唯一计数——必须从完整数据集实时计算。
- 快照指标不容忍僵化时间边界。系统必须灵活找到当前值,而不是盲目跟随日历。
- BI 用户不应编写 DAX/MDX。架构应隐藏复杂度,而不是甩给分析师。
- 新平台摒弃预聚合。动态链接原始源是通往数学精确分析的唯一路径。
范式转变:无妥协分析
经典 BI 系统建立在一种幻觉上:我们可以预计算业务所需的一切。现实中,业务问题不断变化。与其编写 DAX 变通方案,现代平台直接处理原始数据。例如,计算“库存覆盖率”——库存与销售的比率——可以结合两个不同源:数十亿库存行和数十亿收据行。与其在 DWH 中构建僵化数据集市,系统在每次用户点击时动态链接源,以数学精确度重新计算指标。核心本身确定时间窗口、找到当前库存水平,并仅加载必要数据到内存。这不是“更快”;这是“正确”。当银行高管首次看到真实的请求处理时间——没有平均平均值的扭曲——他们开始问正确的问题。零售连锁店不再愿意为 500 万行限制牺牲意义。分析应是 RAW 文件,而不是 JPEG。只有这样才能保留细节并提炼洞见。技术已存在——现在是时候重塑我们的思维架构了。
— Editorial Team
暂无评论。