Skip to content

analytics: compareTo 在「日期维度本身就是网格维度」时从不对齐 —— 比较行按自己平移后的桶键落地,于是每行一半是真值一半是自信的 0,还多出窗口外的幻影行 #6007

Description

@hotlong

现象

compareTo 锚定的日期维度同时是网格维度(即写进了 selection.dimensions,趋势图的常见形状)时,比较趟的行按它自己平移后的桶键落地。mergeByDimensionsselection.dimensions 建键,而这里的键就是那个日期桶,所以 2025-01 永远匹配不上 2026-01 —— 比较行一条都合并不进去,全部作为新行追加

结果:每一行都只有一半是真数字,另一半被 fillEmptyGroups 填成 0;并且日期筛选窗口是 2026-01..2026-02,回来的网格里却坐着 2025-01 / 2025-02 两行。

复现

dataset:owner(lookup)、close_date(date, dateGranularity: month),measure opp_count(count)。

数据:2026-01 → 1 条;2026-02 → 2 条;2025-01 → 5 条;2025-02 → 7 条。

selection:

{ dimensions: ['close_date'],
  measures: ['opp_count'],
  timeDimensions: [{ dimension: 'close_date', dateRange: ['2026-01-01', '2026-02-28'] }],
  compareTo: { kind: 'previousYear', dimension: 'close_date' } }

实测(origin/main 628b028,与 #5688/PR #6003 前后逐字节相同 —— 该 PR 未触及此形状):

rows [{"close_date":"2025-01","opp_count__compare":5,"opp_count":0},
      {"close_date":"2025-02","opp_count__compare":7,"opp_count":0},
      {"close_date":"2026-01","opp_count":1,"opp_count__compare":0},
      {"close_date":"2026-02","opp_count":2,"opp_count__compare":0}]

一个「本期 vs 去年同期」的趋势图,期望是 2 行 × 2 列(2026-01: 1 vs 5;2026-02: 2 vs 7),拿到的是 4 行、每行一个 0,外加两行落在筛选窗口之外。

落点

  • packages/services/service-analytics/src/dataset-executor.ts runCompare:把比较行映射成 { ...dimensions, measure__compare } 时,dimensions 里的日期维度带的是平移后窗口的桶键;
  • 同文件 mergeByDimensions:按 dimensions 元组建键,2025-012026-01 自然不等,走 else 分支追加新行

fillEmptyGroups 的注释其实已经写下了这个行为的一半(「compareTo 趟会为每个上期存在、本期不存在的桶追加一行」),所以它是已知的;但那段话描述的是「上期有、本期没有」的边缘情形,而在日期维度即网格维度时,这是每一行的常态,而不是边缘。两种读法哪个是产品意图,需要裁定 —— 这正是本单不代裁的部分。

为什么 #4870 的 pin 没有发现它

packages/services/service-analytics/src/__tests__/dataset-selection-window.test.ts
「buckets the compareTo pass identically, so the grids actually merge」用例,其 fake 对两趟都返回同一个固定桶键:

executeAggregate: async (object, options) => {
  calls.push({ object, options });
  return [{ created_at: '2026-06', account_count: calls.length }];
}

两趟返回的 created_at 都是 '2026-06',合并因此必然成功。该用例真正钉住的是「两趟的 groupBy 粒度一致」(它确实做到了,且断言得很清楚),但它无法触及「真实数据下平移窗口产生的是平移后的桶键」这一层 —— 只要 fake 按 filter 真的分桶,合并就不成立了。

两种可能的裁法(不代裁)

  1. 按相对位置对齐:比较行的日期桶键在合并前被「平移回」当期(2025-01 → 2026-01),于是 2 行 2 列。语义清晰,但需要定义「平移回」在 previousPeriod(任意天数窗口)下的确切含义。
  2. 维持现状并明说:承认这是「两个窗口叠在同一条时间轴上」的呈现,那么 opp_count__compare 这一列对该形状就是无意义的,应当在该形状下不产出该列(而不是产出一列恒为 0),否则消费端无法区分「没有可比数据」与「对齐失败」。

可达性

需要 compareTo 的锚定维度同时出现在 selection.dimensions。这是趋势图 + 同比的标准形状,examples/app-crm / app-showcase 均有声明了 dateGranularity 的 date 维度。窗口-only 形状不受影响(那条已由 #5688 / PR #6003 修好并成对钉住)。

出处

#5688 实施期间实测发现,范围外未搭车(#5688 的裁决明确限定为「回填判据收窄」,且要求被选中的条目保持分桶不回归 —— 本单动的是分桶之后的合并,是另一层)。PR #6003 已把该行为作为「#5688 未改变的长期行为」原样断言在
dataset-window-timedimension-bucketing.test.ts 的「bucketed anchor」用例里(含上面那 4 行),所以裁定后改动这里会让那条用例有意转红,可直接作为靶子。


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions