Skip to main content

Use case

Some datasets are spread across more than one database. Product events, for example, are often split by age: the last few months stay in a fast analytical database that serves live dashboards, while everything older is moved to cheaper storage. Both tables describe the same events and carry the same columns. The goal is to report on them together — one result set with the rows of both databases appended, plus a dimension that says which database each row came from. This is a union: it adds rows. It is a different problem from a rollup_join, which adds columns by relating entities that live in different databases. It is also different from data blending, which unions cubes inside a single database. The SQL API can do this at query time, against live data and without pre-aggregations. Each cube is queried on its own data source, and Cube appends the results.

Configuration

Define the two connections as multiple data sources. The default source needs no name; every other source gets one, and the full list goes in CUBEJS_DATASOURCES:

Data modeling

Model each table as its own cube, and point one of them at the named data source with data_source. The cube without a data_source uses the default one. Give both cubes a matching set of members, and add a constant dimension that identifies the origin of each row. That dimension is what makes the two halves of the union distinguishable once they sit in the same result set:

Querying

Connect to the SQL API and append the two cubes with UNION ALL:
Each half of the union is evaluated against its own database, in that database’s own dialect, and Cube appends the two results. Filters reach the databases rather than being applied afterwards, so a WHERE clause on either side limits what that database scans:
Add a time dimension to both halves to line the databases up on a common grain:
A cube whose name starts with pg_ cannot be referenced by that name alone. The SQL API routes such a name to pg_catalog, where no cube is ever found, and the query fails with Table or CTE with name 'pg_...' not found. Qualify it with the schema that holds cubes, as in FROM public.pg_costs, or avoid the prefix.

Aggregating across the union

Wrap the union in a CTE to aggregate over both databases at once. Here the per-tier totals combine events from both databases:
UNION also works where duplicate rows should collapse, as do an outer ORDER BY and LIMIT over the union.
Aggregating over the union like this is only correct for additive measures such as count and sum. Non-additive measures — count_distinct, avg, percentiles — cannot be combined from per-source results: summing distinct counts double-counts anything present in both databases, and averaging averages ignores how many rows each database contributed. No error is raised, so report these measures per data source instead.

Inspecting how a query is split

Run EXPLAIN on any of these queries to see the plan. It puts a Union over one CubeScan per cube, and each scan carries its own filter — the WHERE clause is part of the request sent for that cube, not a step applied after the results are appended:
The plan identifies each scan by cube, not by data source, so read it together with the data_source of each cube to see which database serves which half.

Limitations

Unions combine rows across data sources; joins do not. A query that joins two cubes on different data sources is rejected, and relating entities across databases needs a rollup_join instead. Each half of the union is subject to the maximum row limit on its own, and the cap applies before the results are appended. Aggregate inside each half of the union, as in the examples above, rather than unioning raw rows and aggregating afterwards.