立单于 #5537 的实施过程(PR 见该单)。与 #5537 的 fields 装配无关:下面的复现跑在 #5537 从未影响的那条「无 measure filter 单查询」路径上,且在 origin/main(c36abfe98,含 #5587/#5634/#5667)与 #5537 的分支上逐字节同样复现。范围外,单独立单。
现象
一个 dataset selection 只把日期维度用作窗口(timeDimensions: [{ dimension, dateRange }],不带 granularity,也不把它列进 selection.dimensions)——这正是 dashboard 日期区间筛选器的产物,executor 自己的注释就把它称作 "a dashboard date-range filter is the usual source"——结果网格却额外按该日期维度分桶:多出一列没人选过的时间列,行数按月裂开。
复现
dataset(关键条件:日期维度声明了显式 dateGranularity):
dimensions:
- { name: owner, field: owner_id, type: lookup, label: Owner }
- { name: close_date, field: close_date, type: date, label: Close Date, dateGranularity: month }
measures:
- { name: opp_count, aggregate: count, label: Opps }
三条数据:u1 @2026-01、u1 @2026-02、u2 @2026-01。
selection —— 只按 owner 分组,日期只当窗口:
{ dimensions: ['owner'],
measures: ['opp_count'],
timeDimensions: [{ dimension: 'close_date', dateRange: ['2026-01-01', '2026-02-28'] }] }
实测响应:
fields [{"name":"owner","type":"string","label":"Owner"},
{"name":"close_date","type":"time"}, <-- 没人选过这一列
{"name":"opp_count","type":"number","label":"Opps"}]
rows [{"owner":"u1","close_date":"2026-01","opp_count":1},
{"owner":"u1","close_date":"2026-02","opp_count":1}, <-- u1 被拆成两行
{"owner":"u2","close_date":"2026-01","opp_count":1}]
期望:2 行(u1: 2、u2: 1),fields 为 owner + opp_count。
落点
packages/services/service-analytics/src/dataset-executor.ts buildQuery(origin/main c36abfe L888-L909):
const resolvedTimeDims = selTimeDims.map((t) => {
if (t.granularity) return t;
const granularity = granularityFor(t.dimension); // L899
return granularity ? { ...t, granularity } : t;
});
granularityFor 走 resolveDimensionGranularity(selection, name, datasetDefault),datasetDefault 即「dataset 显式声明过 dateGranularity」时编译出的单元素 cube.granularities。于是一个只有 dateRange 的条目被补上 granularity: 'month';而一旦条目带上 granularity,它就是 GROUP BY 项、并且按 #4033 被 projectedDimensions(objectql-strategy.ts L1130,native 侧同义)投影成结果列。
这条补默认值本身是刻意的,buildQuery 上方的长注释写明了理由:compareTo 需要的正是「窗口条目不得抑制分桶」,否则主网格按月、比较网格按原始时间戳,两边维度键对不上,每个 compare 列都空。所以两种需求在同一处打架:
判据看起来应该是「这个 dimension 是否出现在 selection.dimensions(或调用方自己写了 granularity)」,但这属于会改变响应形状的契约取舍,不该由我在 #5537 里顺手猜,故立单。
触发条件与影响面
必要条件(三者同时):dataset 的该日期维度声明了显式 dateGranularity;selection 的 timeDimensions 条目不带 granularity;selection.dateGranularity 未设。HotCRM 一类 dataset 普遍声明 dateGranularity,dashboard 的日期区间筛选器又普遍只发 dateRange,因此可达性不低。
命中时是行数与列集都变(不是纯元数据问题):一个「Won by Owner」表加上日期筛选后每人一行变成每人每月一行,KPI 单值卡会拿到多行里的第一行。
附带的次要症状(同一根因,同一条路径):这样投影出来的时间列在 fields 里只有 type 没有 label —— analytics-service.ts 的维度 label 富化(L915 起)只遍历 selection.dimensions,看不到只在 timeDimensions 里的列。#5537 的 PR 把这一点当作两条路径一致的既有行为钉住了(dataset-dimension-field-descriptors.test.ts 里成对的控制用例),并明确指向本单。
不重复
已搜:timeDimensions dateGranularity(命中 #3650/#3777,均已关闭且是另一件事:前者是 dateRange 被忽略,后者是上界打在 datetime 列上)、dataset-executor buildQuery grouping、dateRange granularity bucket dashboard widget rows、date range filter extra column group by month widget —— 无 open 重复。
立单于 #5537 的实施过程(PR 见该单)。与 #5537 的 fields 装配无关:下面的复现跑在 #5537 从未影响的那条「无 measure filter 单查询」路径上,且在
origin/main(c36abfe98,含 #5587/#5634/#5667)与 #5537 的分支上逐字节同样复现。范围外,单独立单。现象
一个 dataset selection 只把日期维度用作窗口(
timeDimensions: [{ dimension, dateRange }],不带granularity,也不把它列进selection.dimensions)——这正是 dashboard 日期区间筛选器的产物,executor 自己的注释就把它称作 "a dashboard date-range filter is the usual source"——结果网格却额外按该日期维度分桶:多出一列没人选过的时间列,行数按月裂开。复现
dataset(关键条件:日期维度声明了显式
dateGranularity):三条数据:
u1 @2026-01、u1 @2026-02、u2 @2026-01。selection —— 只按 owner 分组,日期只当窗口:
实测响应:
期望:2 行(
u1: 2、u2: 1),fields为owner+opp_count。落点
packages/services/service-analytics/src/dataset-executor.tsbuildQuery(origin/main c36abfe L888-L909):granularityFor走resolveDimensionGranularity(selection, name, datasetDefault),datasetDefault即「dataset 显式声明过dateGranularity」时编译出的单元素cube.granularities。于是一个只有dateRange的条目被补上granularity: 'month';而一旦条目带上 granularity,它就是 GROUP BY 项、并且按 #4033 被projectedDimensions(objectql-strategy.tsL1130,native 侧同义)投影成结果列。这条补默认值本身是刻意的,
buildQuery上方的长注释写明了理由:compareTo需要的正是「窗口条目不得抑制分桶」,否则主网格按月、比较网格按原始时间戳,两边维度键对不上,每个 compare 列都空。所以两种需求在同一处打架:判据看起来应该是「这个 dimension 是否出现在
selection.dimensions(或调用方自己写了 granularity)」,但这属于会改变响应形状的契约取舍,不该由我在 #5537 里顺手猜,故立单。触发条件与影响面
必要条件(三者同时):dataset 的该日期维度声明了显式
dateGranularity;selection 的timeDimensions条目不带granularity;selection.dateGranularity未设。HotCRM 一类 dataset 普遍声明dateGranularity,dashboard 的日期区间筛选器又普遍只发dateRange,因此可达性不低。命中时是行数与列集都变(不是纯元数据问题):一个「Won by Owner」表加上日期筛选后每人一行变成每人每月一行,KPI 单值卡会拿到多行里的第一行。
附带的次要症状(同一根因,同一条路径):这样投影出来的时间列在
fields里只有type没有label——analytics-service.ts的维度 label 富化(L915 起)只遍历selection.dimensions,看不到只在timeDimensions里的列。#5537 的 PR 把这一点当作两条路径一致的既有行为钉住了(dataset-dimension-field-descriptors.test.ts里成对的控制用例),并明确指向本单。不重复
已搜:
timeDimensions dateGranularity(命中 #3650/#3777,均已关闭且是另一件事:前者是dateRange被忽略,后者是上界打在 datetime 列上)、dataset-executor buildQuery grouping、dateRange granularity bucket dashboard widget rows、date range filter extra column group by month widget—— 无 open 重复。