Repository navigation
Description
Found while implementing #4708 (packages/services/service-analytics); filed rather than fixed there, because it is a wrong number, not a blank one, and deserves its own decision.
Mechanism
DatasetExecutor.executeSelection applies a measure's own filter by running a supplementary grouped sub-query per filtered measure (combineFilters(baseFilter, compiled.measureFilters[m])). The compareTo pass does not: runCompare issues one shifted query over all base measures with only baseFilter as its where.
const compareRows = await this.runCompare(
compiled, selection, [...baseMeasures], dimensions, baseFilter, context,
);compiled.measureFilters is never consulted on that path. So for a measure declared with a filter, the current-period column is scoped and the comparison column is not — two different measures rendered side by side under one label.
Reproduction
Dataset (won_count carries a measure-scoped filter):
measures: [
{ name: 'revenue', aggregate: 'sum', field: 'amount' },
{ name: 'won_count', aggregate: 'count', filter: { stage: 'closed_won' } },
]Selection with compareTo: { kind: 'previousPeriod', dimension: 'close_date' }. The three queries the executor emits, captured from the fake service:
| # | measures | where |
|---|---|---|
| 1 | revenue |
— |
| 2 | won_count |
{"stage":"closed_won"} |
| 3 (shifted) | revenue, won_count |
absent |
Query 3 is the comparison pass. won_count__compare is therefore a count of every opportunity in the previous window, not of the won ones — inflated by exactly the rows the measure exists to exclude. A "won deals vs. last month" tile reads as a collapse in performance when nothing changed.
revenue (unfiltered) compares correctly, which is what makes this hard to spot: only the measures that carry a filter are wrong, and they are wrong in the direction of "the comparison period looks better".
Why not fixed in #4708
#4708's fix is about a measure that comes back absent being rendered as no-data. Here the number is present and wrong, which is a different failure mode with a different remedy (issue one shifted sub-query per filtered measure, mirroring the primary path's split), and a different blast radius — it changes numbers that dashboards are showing today.
Suggested direction
Mirror the primary path: split the comparison pass into unfiltered-measures-in-one-query plus one shifted sub-query per filter-scoped measure, merged by dimension key with mergeByDimensions, exactly as executeSelection already does for the current period. That also keeps the __compare columns consistent with the empty-group fill #4708 added over them.
Cost: one extra query per filtered measure when compareTo is set. The alternative — declaring the discrepancy in the response — is not a real option, since the two columns are meant to be directly comparable.
Related: #4708.
Activity
claude commentedon Aug 3, 2026
认领:PM 循环第 7 轮
会话:
session_015Br2xsJsczFsTR9bvbh2Ny分支:
claude/issue-4820-compare-measure-filtersWorktree:
objectstack-issue-4820域:
services文件面:
packages/services/service-analytics/src/dataset-executor.ts并行批隔离:本轮同批的另三条落在互不相交的包 —— #4825⚠️ #4821(
packages/metadata、#4813packages/core、#4808packages/plugins/plugin-auth、#4820packages/services/service-analytics。mergeByDimensions分隔符)与 #4820 同文件,未同批派发,串到下一轮以避开撞车。Generated by Claude Code
fix(service-analytics): compareTo 带上 measure 自己的 filter,__compare 列不再…