Data model mapping
In the SQL API, each cube or view from the data model is represented as a table. Measures, dimensions, and segments are represented as columns in these tables.Cubes and views
Given that you have a cube or a view calledorders, you can query it as if it’s
a table:
Dimensions
Given that your cube or view has a dimension calledstatus, you can reference it
as a column in the SELECT clause. Note that you’ll also have to add it to the
GROUP BY clause:
Measures
Given that your cube or view has a measure calledcount, you can reference it
by wrapping with the MEASURE aggregate function:
Aggregate functions
The specialMEASURE function works with measures of any type.
Measure columns can also be aggregated with the following aggregate functions that
correspond to measure types:
Segments
Segments are exposed as columns of theboolean type.
Given that your cube or view has a segment called is_completed, you can reference it
as a column in the WHERE clause:
Joins
Please refer to this page for details on joins.Post-processing and pushdown
Since 1.0 by default, the SQL API executes regular queries, queries with post-processing, and queries with pushdown.Query post-processing
The following query is performing aSELECT from the orders cube:
SELECT query fragments into a regular
query. It can be represented as follows in the REST (JSON) API query
format:
SELECT from cube tables. Please refer to the
SQL API reference to see whether a specific expression or function
is supported and whether it can be used in selection
(e.g., WHERE) or projection (e.g., SELECT) parts
of SQL queries.
For example, the following query won’t work because the SQL API can’t push down the
CASE expression to Cube for processing. It is not possible to translate CASE
expressions in measures.
SELECT
statement from a cube table (inner query) into another SELECT statement
(outer query) to perform calculations with expressions like CASE.
This outer SELECT is not part of the SQL query that being rewritten and
thus allows you to use more SQL functions, operators and expressions.
You can rewrite the above query as follows, making sure to wrap the original
SELECT statement:
CASE expression is supported in SELECT
queries not querying cube tables.
Query pushdown
Query pushdown provides a safe net for queries that can’t be rewritten into combination of a regular query and post-processing. Such queries’ SQL would be transpiled to target database query leveraging all target database capabilities for data processing. During the rewrite process, Cube validates that the target database would support transpired SQL queries. If direct conversion is not possible, different SQL transformation rewrite rules can be applied to achieve successful translation. Please refer to the SQL API reference for the list of supported SQL functions and clauses. Support varies based on the target database.Top-down and bottom-up evaluation
Fundamentally, every SQL operation results in a tabular data set. This is usually referred to as SQL operational closure or bottom-up SQL evaluation. However, for OLAP queries, most of the time, top-down evaluation is required. Top-down evaluation is whenever the outermost sub-query operation decides on how measures would be actually evaluated as opposed to innermost sub-query in case of standard SQL behavior. To balance between SQL guarantees and OLAP requirements, Cube- uses top-down evaluation from the innermost aggregation operation down to all ungrouped sub-queries,
- uses bottom-up evaluation from the innermost aggregation tabular result set up to the outermost sub-query.
On the other hand, a typical query that various BI tools generate:
For this particular query,
inner_query won’t be evaluated as a table.
Instead, Cube would postpone its execution until wrapping GROUP BY and would use only date_trunc('day', created_at) as a dimension to evaluate completed_percentage measure instead of full set of inner_query columns id,status and created_at.
To make it possible, Cube keeps track of ungrouped queries and evaluates them only on the first occurrence of a GROUP BY query in case there’s one.
Aggregated and non-aggregated queries
SQL API supports two types of queries against cube tables: aggregated (those withGROUP BY statement) and non-aggregated (those without).
Without query pushdown, queries that Cube runs against your database will always be aggregated,
regardless of whether you use aggregated (with
GROUP BY) or non-aggregated
queries with the SQL API.
Whenever you enable query pushdown, queries which do not contain GROUP BY clause will be executed as ungrouped queries.GROUP BY to execute such a query.
Whenever query pushdown is enabled, such query would run as ungrouped query.
As with REST (JSON) API such queries do not use GROUP BY and render measures as if those would be grouped by primary key of a cube.
Aggregated query must aggregate all measure columns and group by
all dimension columns. You can use the special MEASURE aggregate function
for measures of any type. This is quite convenient, especially
in case you’re manually writing ad-hoc queries:
Filtering
Without query pushdown, Cube supports most simple equality operators like=, <>, <, <=, >, >= as well as IN and LIKE operators.
Cube tries to push down all filters into a regular query.
In some cases, filtering can only be done during post-processing.
Time dimension filters will be converted to time dimension date ranges whenever
it’s possible.
Ordering
Without query pushdown, Cube tries to push down allORDER BY statements into
a regular query.
Row limit edge case
When part of a query can’t be pushed down, that part is performed during post-processing. The regular query it reads from is cut off at 50,000 rows unless a smaller limit of its own applies, and the post-processing then runs over only those rows, so the result can be incorrect without any error being raised. Aggregated queries usually return far fewer rows than the limit, so this rarely comes up in practice; please keep it in mind when designing your queries. Consider the following query. Because of theSUM(total_value) + 2 expression
in the projection of the outer query, the SQL API can’t push down ORDER BY:
EXPLAIN against the above query to look at the query plan.
As you can see, the sorting operation is done after the regular query and the projection:
ORDER BY is not the only operation this happens to. Anything left to post-processing
over a regular query that isn’t bounded by a small enough limit behaves the same way, so
EXPLAIN is the reliable way to tell. CubeScanExecutionPlan prints the query it will
run: CubeScanExecutionPlan, Request: followed by JSON, or CubeScanExecutionPlan, SQL:
followed by SQL where the query is pushed down. Pushdown can be partial, so operations
may still sit above a scan that prints SQL:. If the JSON has no limit key, or the
limit in the JSON or the printed SQL is larger than the number of rows you expect, the
row limit is applied when the query runs and every operation above the scan sees at most
that many rows.
If a query of yours has that shape, you can:
- Add an explicit
LIMITand confirm withEXPLAINthat it reaches the scan. It only does so when the limit ends up directly above the regular query — in the plan above aSortExecsits in between, so the limit bounds the final output rather than what the sorting reads. - Raise
CUBEJS_DB_QUERY_LIMITso the intermediate result is truncated later. Non-streaming SQL API queries are capped byCUBESQL_NON_STREAMING_QUERY_MAX_ROW_LIMIT, which defaults toCUBEJS_DB_QUERY_LIMITand can’t exceed it — if you’ve set it explicitly, raise it too. - Enable
CUBESQL_STREAM_MODE: streamed queries aren’t capped, so there is nothing to truncate. Whether a query streams is decided by the limit in the scan’s request — the oneEXPLAINprints — and not by theLIMITclause in your SQL: it streams when that request has no limit, or one above the cap. So this combines with the first workaround only up to a point: aLIMITthat reaches the scan and is below the cap turns streaming back off, while one that doesn’t reach it, as in the plan above, leaves it on. - Restructure the query so it is pushed down in full, e.g. by removing the expression that prevents it.