Skip to content

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.

select:
  dimensions:
    - Customer Country
    - Product Category

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.

select:
  measures:
    - Revenue
    - Order Count
    - Revenue per Order    # metric

Secondary Join Paths

When a model defines secondary joins (e.g., FlightsAirports 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 of day, week, month, year
  • count: positive integer number of units
  • direction (optional): past (default) or future
  • include_current (optional): boolean, default true

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:

limit: 1000

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