Query Language¶
OrionBelt uses a structured query object to express analytical queries against a semantic model. The query language selects dimensions and measures, applies filters, sorts results, and limits output — all using business names rather than raw SQL.
Query Object Structure¶
select:
dimensions:
- Customer Country
- "Order Date:month" # with time grain
measures:
- Revenue
- Order Count
where:
- field: Customer Segment
op: in
value: [SMB, MidMarket]
having:
- field: Revenue
op: gt
value: 10000
order_by:
- field: Revenue
direction: desc
limit: 1000
In Python¶
from orionbelt.models.query import (
QueryObject,
QuerySelect,
QueryFilter,
QueryOrderBy,
FilterOperator,
SortDirection,
)
query = QueryObject(
select=QuerySelect(
dimensions=["Customer Country", "Order Date:month"],
measures=["Revenue", "Order Count"],
),
where=[
QueryFilter(field="Customer Segment", op=FilterOperator.IN, value=["SMB", "MidMarket"]),
],
having=[
QueryFilter(field="Revenue", op=FilterOperator.GT, value=10000),
],
order_by=[QueryOrderBy(field="Revenue", direction=SortDirection.DESC)],
limit=1000,
)
Select¶
The select section specifies which dimensions and measures to include.
Dimensions¶
Dimensions are referenced by name as defined in the semantic model. They become GROUP BY columns in the generated SQL.
Time Grain Override¶
Apply a time grain at query time using "dimension:grain" syntax:
select:
dimensions:
- "Order Date:month" # truncate to month
- "Order Date:quarter" # truncate to quarter
- "Order Date:year" # truncate to year
Supported grains: year, quarter, month, week, day, hour, minute, second.
This overrides any timeGrain set on the dimension definition.
Measures¶
Measures are referenced by name. They can be simple aggregations, expression-based measures, or metrics.
Secondary Join Paths¶
When a model defines secondary joins (e.g., Flights → Airports via departure and arrival), use usePathNames to select which join path to use:
select:
dimensions:
- Airport Name
measures:
- Total Ticket Price
usePathNames:
- source: Flights
target: Airports
pathName: arrival
Each entry specifies a (source, target, pathName) triple. The pathName must match a secondary join defined in the model. When active, the secondary join replaces the primary join for that pair.
In Python¶
from orionbelt.models.query import QueryObject, QuerySelect, UsePathName
query = QueryObject(
select=QuerySelect(
dimensions=["Airport Name"],
measures=["Total Ticket Price"],
),
use_path_names=[
UsePathName(source="Flights", target="Airports", path_name="arrival"),
],
)
In JSON (full mode)¶
{
"select": {
"dimensions": ["Airport Name"],
"measures": ["Total Ticket Price"]
},
"usePathNames": [
{"source": "Flights", "target": "Airports", "pathName": "arrival"}
]
}
If a usePathNames entry references a non-existent data object or pathName, the query will return a resolution error.
Filters¶
Filters restrict the result set. Dimension filters go in where (become SQL WHERE), and measure filters go in having (become SQL HAVING).
where:
- field: Customer Country
op: equals
value: Germany
having:
- field: Revenue
op: gte
value: 5000
Filter Structure¶
| Property | Type | Description |
|---|---|---|
field |
string | Dimension name (where) or measure name (having) |
op |
string | Filter operator (see table below) |
value |
any | Comparison value (string, number, list, etc.) |
Filter Reachability¶
A where filter field must reference a dimension whose data object is reachable from the query's join graph. The data object can be:
- Directly joined in the query (base object or any object in the join path)
- A descendant — reachable via directed joins from any already-joined object
If the data object is reachable but not yet in the join path, it is auto-joined automatically. If the data object is not reachable at all, the query returns an UNREACHABLE_FILTER_FIELD error.
A having filter field must reference a measure name.
Filter Operators¶
OrionBelt supports two operator naming conventions — OBML style and SQL style. Both are equivalent.
Comparison Operators¶
| OBML | SQL Style | SQL Output | Value Type |
|---|---|---|---|
equals |
=, eq |
= value |
scalar |
notequals |
!=, neq |
<> value |
scalar |
gt |
>, greater |
> value |
scalar |
gte |
>=, greater_eq |
>= value |
scalar |
lt |
<, less |
< value |
scalar |
lte |
<=, less_eq |
<= value |
scalar |
Set Operators¶
| OBML | SQL Style | SQL Output | Value Type |
|---|---|---|---|
inlist |
in |
IN (v1, v2, ...) |
list |
notinlist |
not_in |
NOT IN (v1, v2, ...) |
list |
Null Operators¶
| OBML | SQL Style | SQL Output | Value Type |
|---|---|---|---|
set |
is_not_null |
IS NOT NULL |
none |
notset |
is_null |
IS NULL |
none |
String Operators¶
| Operator | SQL Output | Value Type |
|---|---|---|
contains |
LIKE '%value%' (dialect-specific) |
string |
notcontains |
NOT LIKE '%value%' |
string |
starts_with |
LIKE 'value%' |
string |
ends_with |
LIKE '%value' |
string |
like |
LIKE 'pattern' |
string |
notlike |
NOT LIKE 'pattern' |
string |
Range Operators¶
| Operator | SQL Output | Value Type |
|---|---|---|
between |
BETWEEN low AND high |
list of 2 |
notbetween |
NOT BETWEEN low AND high |
list of 2 |
relative |
Relative time range | object |
Relative filter object
The relative operator expects an object with the following keys:
unit: one ofday,week,month,yearcount: positive integer number of unitsdirection(optional):past(default) orfutureinclude_current(optional): boolean, defaulttrue
Example (last 7 days, inclusive of today):
where:
- field: Order Date
op: relative
value:
unit: day
count: 7
direction: past
include_current: true
String contains is dialect-aware
The contains operator generates different SQL per dialect:
- Postgres/ClickHouse:
ILIKE '%' || value || '%' - Snowflake:
CONTAINS(column, value) - Dremio/Databricks:
LOWER(column) LIKE '%' || LOWER(value) || '%'
Ordering¶
Sort results by dimension or measure names from the query's SELECT, or by numeric position:
order_by:
- field: Revenue
direction: desc
- field: Customer Country
direction: asc # default
- field: "1" # numeric position (1-based)
direction: asc
| Property | Type | Default | Description |
|---|---|---|---|
field |
string | — | Dimension or measure name from the query's SELECT, or a numeric position (e.g. "1") |
direction |
enum | asc |
asc or desc |
The field must reference a dimension or measure that appears in the query's select. It cannot reference fields outside the SELECT. Alternatively, use a numeric string to reference the SELECT column by position (1-based).
Limit¶
Restrict the number of returned rows:
Validation¶
Invalid queries return error responses:
| Error Code | Status | Cause |
|---|---|---|
UNKNOWN_DIMENSION |
400 | Dimension name not in model |
UNKNOWN_MEASURE |
400 | Measure name not in model |
UNKNOWN_FILTER_FIELD |
400 | Filter field is not a dimension (WHERE) or measure (HAVING) |
UNREACHABLE_FILTER_FIELD |
400 | Filter dimension's data object is not reachable from join graph |
UNKNOWN_ORDER_BY_FIELD |
400 | ORDER BY field not in query's SELECT |
INVALID_ORDER_BY_POSITION |
400 | Numeric ORDER BY position out of range |
INVALID_FILTER_OPERATOR |
400 | Unrecognized filter operator |
INVALID_RELATIVE_FILTER |
400 | Malformed relative time filter |
AMBIGUOUS_JOIN |
422 | Multiple join paths possible |
Semantics Summary¶
| Query Element | SQL Equivalent |
|---|---|
select.dimensions |
SELECT + GROUP BY columns |
select.measures |
SELECT aggregate expressions |
where |
WHERE clause |
having |
HAVING clause |
order_by |
ORDER BY clause |
limit |
LIMIT clause |