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:
| # | 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.
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.executeSelectionapplies a measure's ownfilterby running a supplementary grouped sub-query per filtered measure (combineFilters(baseFilter, compiled.measureFilters[m])). ThecompareTopass does not:runCompareissues one shifted query over all base measures with onlybaseFilteras itswhere.compiled.measureFiltersis 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_countcarries a measure-scoped filter):Selection with
compareTo: { kind: 'previousPeriod', dimension: 'close_date' }. The three queries the executor emits, captured from the fake service:revenuewon_count{"stage":"closed_won"}revenue,won_countQuery 3 is the comparison pass.
won_count__compareis 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 asexecuteSelectionalready does for the current period. That also keeps the__comparecolumns consistent with the empty-group fill #4708 added over them.Cost: one extra query per filtered measure when
compareTois 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.