{"record":{"id":"50152554a63c3ce6","repo":"cube-js/cube","slug":"can-t-build-join-tree-for-pre-aggregation-preagg","errorCode":null,"errorMessage":"Can't build join tree for pre-aggregation ${preAggObj.cube}.${preAggObj.preAggregationName}","messagePattern":"Can't build join tree for pre-aggregation (.+?)\\.(.+?)","errorType":"validation","errorClass":"UserError","httpStatus":null,"severity":"error","filePath":"packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts","lineNumber":1052,"sourceCode":"\n    for (const j of refs.joinTree.joins) {\n      hints.push([j.from, j.to]);\n    }\n\n    return hints;\n  }\n\n  // TODO check multiplication factor didn't change\n  private buildRollupJoin(preAggObj: PreAggregationForQuery, preAggObjsToJoin: PreAggregationForQuery[]): RollupJoin {\n    return this.query.cacheValue(\n      ['buildRollupJoin', JSON.stringify(preAggObj), JSON.stringify(preAggObjsToJoin)],\n      () => {\n        // It's not enough to call buildJoin() directly on cubesFromPreAggregation()\n        // because transitive joins won't be collected in that case.\n        const builtJoinTree = this.query.joinTreeForHints(this.cubesHintsFromPreAggregation(preAggObj), true);\n\n        if (!builtJoinTree) {\n          throw new UserError(`Can't build join tree for pre-aggregation ${preAggObj.cube}.${preAggObj.preAggregationName}`);\n        }\n\n        const targetJoins = this.resolveJoinMembers(builtJoinTree);\n\n        // TODO join hints?\n        const existingJoins = preAggObjsToJoin\n          .map(p => this.resolveJoinMembers(\n            this.query.joinTreeForHints(this.cubesHintsFromPreAggregation(p), true)\n          ))\n          .flat();\n\n        const nonExistingJoins = targetJoins.filter(target => !existingJoins.find(\n          existing => existing.originalFrom === target.originalFrom &&\n            existing.originalTo === target.originalTo &&\n            R.equals(existing.fromMembers, target.fromMembers) &&\n            R.equals(existing.toMembers, target.toMembers)\n        ));\n        if (!nonExistingJoins.length) {","sourceCodeStart":1034,"sourceCodeEnd":1070,"githubUrl":"https://github.com/cube-js/cube/blob/7d981676b36392fec34088b9afab6bdcad40207c/packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts#L1034-L1070","documentation":"When building a rollup-join, Cube tries to construct the join tree among the cubes referenced by a pre-aggregation. joinTreeForHints returned null (no joins could be derived from the pre-aggregation's cube hints), so the rollup join cannot be materialized and this UserError names the offending pre-aggregation.","triggerScenarios":"A pre-aggregation referenced by a rollupJoin spans cubes whose members do not require any join (single cube) or whose join hints cannot be resolved — joinTreeForHints(cubesHints, true) yields no tree during rollup-join construction.","commonSituations":"Rollup joins defined where the 'from'/'to' cubes are actually the same cube or unrelated cubes with no defined join path; missing or incorrect joins section in the cube definitions; referencing a pre-agg that only covers one cube in a rollupJoin.","solutions":["Verify each cube in the rollupJoin has correct joins defined to the other cubes (both directions implied).","Ensure the pre-aggregations listed in the rollupJoin actually span the joined cubes with full dimension paths (Cube.Dim.member).","Remove pre-aggregations that only cover a single cube from the rollupJoin definition.","Check that cubesHintsFromPreAggregation produces entries for all participating cubes — fix member references if some are missing."],"exampleFix":"// before\nrollupJoin: { dimensions: [Orders.status, Users.country], preAggregations: [ordersMain] } // ordersMain covers only Orders, no join path\n// after\npreAggregations: [Orders.ordersByStatusUser, Users.usersByCountry] // each pre-agg spans its cube's join side\nrollupJoin: { preAggregations: [Orders.ordersByStatusUser, Users.usersByCountry] }","handlingStrategy":"validation","validationCode":"// before defining a rollupJoin, verify each cube in the join has joins declared and each pre-agg spans >1 cube\nfor (const [cubeName, cube] of Object.entries(schema)) {\n  if (participatingCubes.includes(cubeName) && !cube.joins) {\n    throw new Error(`Cube ${cubeName} used in rollupJoin has no joins defined`);\n  }\n}","typeGuard":null,"tryCatchPattern":"try { await cubeApi.load(query); } catch (e) { if (/Can't build join tree for pre-aggregation/.test(e.message)) {\n  const preAgg = e.message.match(/pre-aggregation ([\\w.]+)/)?.[1];\n  throw new Error(`Fix cube joins / member paths for ${preAgg}`); } throw e; }","preventionTips":["Define joins on every cube participating in a rollupJoin","Only list multi-cube pre-aggregations in rollupJoin definitions","Use fully qualified member paths (Cube.dimension)","Write a schema test that builds all rollupJoins at compile time"],"tags":["rollup-join","join-tree","pre-aggregation"],"backgroundTag":"rollup-join-tree-unresolvable","analyzedSha":"7d981676b36392fec34088b9afab6bdcad40207c","analyzedAt":"2026-09-02T03:45:10.400Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}