cube-js/cube · error · UserError
Multiple rollups found that can be used for rollup join ${JS
Error message
Multiple rollups found that can be used for rollup join ${JSON.stringify(join)}: ${fromPreAggObj.map(p => this.preAggregationId(p)).join(', ')} What it means
When resolving a rollupJoin pre-aggregation, Cube finds candidate pre-aggregations matching the join's 'from' side. If more than one pre-aggregation qualifies, the join becomes ambiguous and the compiler throws this UserError instead of guessing. It is thrown in PreAggregations.ts during buildJoinPreAggregationForQuery resolution.
Source
Thrown at packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts:1102
}
private preAggObjForJoin(
preAggObjsToJoin: PreAggregationForQuery[],
joinMembers: string[],
join: JoinEdgeWithMembers,
rollupJoinPreAggName: string,
): PreAggregationForQuery {
const fromPreAggObj = preAggObjsToJoin
.filter(p => joinMembers.every(m => !!p.references.dimensions.find(d => m === d)));
if (!fromPreAggObj.length) {
const msg = `No rollups found that can be used for a rollup join from "${
join.from}" (fromMembers: ${JSON.stringify(join.fromMembers)}) to "${join.to}" (toMembers: ${
JSON.stringify(join.toMembers)}). Check the "${
rollupJoinPreAggName}" pre-aggregation definition — you may have forgotten to specify the full dimension paths`;
throw new UserError(msg);
}
if (fromPreAggObj.length > 1) {
throw new UserError(
`Multiple rollups found that can be used for rollup join ${JSON.stringify(join)}: ${fromPreAggObj.map(p => this.preAggregationId(p)).join(', ')}`,
);
}
return fromPreAggObj[0];
}
private resolveJoinMembers(join: FinishedJoinTree): JoinEdgeWithMembers[] {
const joinMap = new Set<string>();
return join.joins.map(j => {
joinMap.add(j.originalFrom);
const memberPaths = this.query.collectMemberNamesFor(() => this.query.evaluateSql(j.originalFrom, j.join.sql)).map(m => m.split('.'));
const invalidMembers = memberPaths.filter(m => !joinMap.has(m[0]) && m[0] !== j.originalTo);
if (invalidMembers.length) {
throw new UserError(`Members ${invalidMembers.join(', ')} in join from '${j.originalFrom}' to '${j.originalTo}' doesn't reference join cubes`);
}View on GitHub (pinned to 7d981676b3)
Solutions
- Make the rollupJoin's fromMembers specific enough (use full dimension paths) so only one pre-aggregation matches.
- Remove or rename one of the competing pre-aggregations listed in the error message.
- Add distinguishing dimensions/timeDimensions to one of the candidate pre-aggregations so only one is eligible.
- Restructure the rollupJoin to reference the exact pre-aggregation intended by name where possible.
Example fix
// before: two pre-aggs on Orders both match join.fromMembers
preAggregations: {
ordersByDay: { measures: [CUBE.count], dimensions: [Orders.status], timeDimension: Orders.createdAt, granularity: day },
ordersByDayWide: { measures: [CUBE.count], dimensions: [Orders.status], timeDimension: Orders.createdAt, granularity: day } // duplicate coverage
}
// after: remove/renamed the duplicate so exactly one rollup matches
preAggregations: {
ordersByDay: { measures: [CUBE.count], dimensions: [Orders.status], timeDimension: Orders.createdAt, granularity: day }
} Defensive patterns
Strategy: validation
Validate before calling
// Before defining a rollupJoin, list candidate pre-aggs per source cube and ensure exactly one covers each fromMembers set
function uniqueRollupForJoin(preAggs, fromMembers) {
const matches = preAggs.filter(p => fromMembers.every(m => p.references.dimensions.includes(m)));
if (matches.length > 1) throw new Error(`Ambiguous rollupJoin source: ${matches.map(p => p.name).join(', ')}`);
return matches[0];
} Type guard
function hasUniqueRollupMatch(preAggs, fromMembers): boolean { return preAggs.filter(p => fromMembers.every(m => p.references.dimensions.includes(m))).length === 1; } Try / catch
try { await cube.query(query); } catch (e) { if (/Multiple rollups found that can be used for rollup join/.test(e.message)) { console.error('Narrow fromMembers or remove a competing pre-aggregation:', e.message); } else throw e; } Prevention
- Keep one pre-aggregation per (measures, dimensions, granularity) combination per cube
- Use full dimension paths in rollupJoin fromMembers
- Review pre-aggregation additions on joined cubes for overlap with rollupJoin sources
- Add a unit test compiling your rollupJoins to catch ambiguity early
When it happens
Trigger: A pre-aggregation with type 'rollupJoin' whose fromMembers match two or more defined pre-aggregations on the source cube (e.g. two pre-aggregations covering the same dimension paths, or one plain and one multi-stage rollup both satisfying the join 'from' reference).
Common situations: Defining multiple overlapping pre-aggregations on a joined cube (e.g. one for daily and one covering all grain with the same dimensions); adding a new pre-aggregation that accidentally subsumes an existing rollupJoin source; forgetting to narrow fromMembers so several rollups match.
Related errors
- Only fixed rolling windows are supported by Cube Store but g
- Index SQL support is not implemented
- Unknown pre-aggregation type '${preAggregation.type}' in '${
- Output schema type for ${member} not found in pre-aggregatio
- Can't build join tree for pre-aggregation ${preAggObj.cube}.
AI-assisted analysis of cube-js/cube@7d981676b3 (2026-09-02).
Data as JSON: /api/errors/5ee66acbf8b76b46.
Report an issue: GitHub.