cube-js/cube · error · UserError

Data blending query granularities must match

Error message

Data blending query granularities must match

What it means

For a data blending query (an array of queries blended into one result set), all sub-queries must share exactly one common date granularity (e.g. all 'day' or all 'month'). The gateway extracts the granularities of the normalized queries via getQueryGranularity and throws if more than one distinct granularity is found, because blending rows on differing time buckets is undefined.

Source

Thrown at packages/cubejs-api-gateway/src/gateway.ts:1513

    this.log({
      type: 'Query Rewrite completed',
      queryRewriteId,
      normalizedQueries: normalizedQueries.map(q => this.sanitizeQueryForLogging(q)),
      duration: Date.now() - startTime,
      query
    }, context);

    normalizedQueries = normalizedQueries.map(q => remapToQueryAdapterFormat(q));

    if (normalizedQueries.some((currentQuery) => !currentQuery)) {
      throw new Error('queryTransformer returned null query. Please check your queryTransformer implementation');
    }

    if (queryType === QueryTypeEnum.BLENDING_QUERY) {
      const queryGranularity = getQueryGranularity(normalizedQueries);

      if (queryGranularity.length > 1) {
        throw new UserError('Data blending query granularities must match');
      }
      if (queryGranularity.length === 0) {
        throw new UserError('Data blending query without granularity is not supported');
      }
    }

    return [queryType, normalizedQueries, queryNormalizationResult.map((it) => remapToQueryAdapterFormat(it.normalizedQuery))];
  }

  protected async sql4sql({
    query,
    disablePostProcessing,
    context,
    res,
  }: {query: string, disablePostProcessing: boolean} & BaseRequest) {
    try {
      await this.assertApiScope('sql', context.securityContext);

View on GitHub (pinned to 7d981676b3)

Solutions

  1. Set the same `granularity` on the timeDimension of every sub-query in the array.
  2. Derive granularity once from a single UI control and apply it to all blended queries.
  3. If different granularities are truly needed, issue separate /load requests and blend on the client instead.

Example fix

// before
[{ measures:['a.c'], timeDimensions:[{dimension:'a.date', granularity:'day', dateRange:['2024-01-01','2024-03-01']}] },
 { measures:['b.c'], timeDimensions:[{dimension:'b.date', granularity:'month', dateRange:['2024-01-01','2024-03-01']}] }]
// after
[{ measures:['a.c'], timeDimensions:[{dimension:'a.date', granularity:'month', dateRange:['2024-01-01','2024-03-01']}] },
 { measures:['b.c'], timeDimensions:[{dimension:'b.date', granularity:'month', dateRange:['2024-01-01','2024-03-01']}] }]
Defensive patterns

Strategy: validation

Validate before calling

function blendGranularities(queries) {
  return [...new Set(queries.flatMap(q => (q.timeDimensions ?? []).map(td => td.granularity).filter(Boolean)))];
}
if (Array.isArray(query) && blendGranularities(query).length > 1) throw new Error('All blended queries must use the same granularity');

Type guard

function hasMatchingGranularity(qs: any[]): boolean {
  const gs = new Set(qs.flatMap(q => (q.timeDimensions ?? []).map(td => td.granularity).filter(Boolean)));
  return gs.size <= 1;
}

Try / catch

try {
  return await cubeApi.load(queries);
} catch (e) {
  if (String(e?.message).includes('granularities must match')) {
    const g = blendGranularities(queries)[0];
    return await cubeApi.load(queries.map(q => alignGranularity(q, g)));
  }
  throw e;
}

Prevention

When it happens

Trigger: POST /cubejs-api/v1/load with an array query (QueryTypeEnum.BLENDING_QUERY) where e.g. query[0].timeDimensions[0].granularity = 'day' and query[1].timeDimensions[0].granularity = 'month', or one uses no granularity while another uses 'week'.

Common situations: Blending two charts with different zoom levels (daily vs monthly); building the query array programmatically and applying granularity only to some sub-queries; UIs that let users pick granularity per metric instead of globally.

Related errors


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