Skip to content

compareTo drops every measure-scoped filter, so <measure>__compare silently reports a different measure than the column beside it #4820

Description

@os-zhuang

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.

constcompareRows=awaitthis.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:

#measureswhere
1revenue
2won_count{"stage":"closed_won"}
3 (shifted)revenue, won_countabsent

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.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions