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

  1. Make the rollupJoin's fromMembers specific enough (use full dimension paths) so only one pre-aggregation matches.
  2. Remove or rename one of the competing pre-aggregations listed in the error message.
  3. Add distinguishing dimensions/timeDimensions to one of the candidate pre-aggregations so only one is eligible.
  4. 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

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


AI-assisted analysis of cube-js/cube@7d981676b3 (2026-09-02). Data as JSON: /api/errors/5ee66acbf8b76b46. Report an issue: GitHub.