query
Get data from a semantic model by describing the dimensions, measures, filters, and time buckets you need; the query compiles into SQL and returns the result rows.
Instructions
Query data from a semantic model. Call inspect(reference=".", entity_type="model") first to see available columns and measures, and search (with the entities you plan to use and/or a free-text question) to surface saved learnings and example queries before finalizing a query.
The query argument takes one of three forms:
Model name (string) — run a query-backed saved model by name, e.g.
"monthly_revenue"(honorsvariables; every other setting comes from the stored query).Query object (dict) — a single query; per-field documentation is on the SlayerQuery schema.
Multi-stage list (list of query objects) — a DAG of stages. Every entry except the last MUST carry a
name; the last entry is the root whose rows are returned. Stages reference one another by that name — as asource_modelor via a join in an inline ModelExtension — and the engine orders them topologically. An inner stage's result columns become plain columns of the outer stage (dotted paths flatten:stores.name->stores__name); a stage may reference only what its own source defines or what a prior stage projected — define before you reference. Use stages when a whole result set must be re-queried, joined, or reused; single-query nesting and computed dimensions already cover re-aggregation.
Expressions — one language, used in measures, computed dimensions, filters, and order. The same expression returns its value as a measure, groups by it as a computed dimension, masks as a filter (routed automatically to WHERE / HAVING / post-aggregation), and sorts in order.
Aggregations are function calls over a column or a same-model scalar expression:
count(*),sum(total),sum(amount - cost),percentile(price, p=0.95). Available: sum, avg (both take window='90d' for trailing time windows), min, max, count, count_distinct, count_distinct_approx, median, percentile(x, p=), weighted_avg(x, weight=col), stddev_samp, stddev_pop, var_samp, var_pop, corr(x, other=col), covar_samp(x, other=col), covar_pop(x, other=col), first(x[, time_col]) / last(x[, time_col]) (earliest/latest record's value per group), plus model-defined custom aggregations. Write count_distinct(x), never count(distinct x).All aggregations support
partition_by=(bare names:partition_by=region,partition_by=[region, city],partition_by=[]for the grand total), computing the aggregate at that coarser grain; the result is broadcast over the missing dimensions.Combine aggregations with arithmetic and transforms — missing dimensions broadcast on both sides. E.g. with dimensions ["city", "region"], the measure {"formula": "sum(total) / sum(total, partition_by=region)", "name": "share_of_region"} is each city's share of its region's total.
Aggregations nest: "avg(sum(total, partition_by=[region, city]), partition_by=[region])" averages the per-city totals within each region. The top-level partition_by must be a subset of the query's dimensions; inner aggregations' partition_by need not be. An outer aggregation's parameters must be determined by the operand's grain — a cell value at that grain, e.g. weight=count(id, partition_by=[region, city]), or a column that grain fixes; any other row column is a typed error.
Transforms wrap aggregated expressions: cumsum(x); change(x) / change_pct(x) (period-over-period delta / % change — calendar-aware and partition-safe, prefer these for growth); time_shift(x, -1[, 'year']) (the shifted value itself, for custom arithmetic); lag(x, n) / lead(x, n) (row-position shift, NULL at edges); first(x) / last(x) (broadcast the earliest/latest bucket's value); consecutive_periods(predicate) (trailing run length; the predicate may be row-level, e.g. status = 'paid'); rank(x), dense_rank(x), percent_rank(x), ntile(x, n=N) (rank family — optional partition_by=, no time dimension needed). All other transforms require a time_dimensions entry. Transforms nest in either order (change(cumsum(x))). Not supported: a row-level column mixed into a composite or nested input of time_shift / change / change_pct, or mixed with another aggregation's value inside one aggregation source.
Cross-model: reference any joined model's field as
model_name.field_name(or a longer dotted path) and the engine figures out the join paths, avoiding fan-outs and chasm traps — each aggregation computes over its own model's rows exactly once; ambiguous routes error naming the candidates, and result keys use the full routed path. An aggregation sliced by a dimension not attributable to it broadcasts its value with a warning — seeto_many_handlingto attribute or error instead.
Method — decompose the question into blocks first: every qualifier, projected column, filter, grouping, unit, rounding, and ordering hint is one block, and each must map to a named column/measure/filter/dimension. Never drop a qualifier because no entity matched — search for it, else encode it as an expression or an inline ModelExtension column; reference already-encoded quantities by name rather than re-deriving their logic. Pin explicitly rather than guessing: which aggregation ("typical" is not automatically avg vs median), the grouping column and raw-vs-standardized labels, each aggregate's scope (all rows vs a filtered subset), sort column + direction + tie-break, NULL handling, units and rounding, exact numeric constants. "How many / count of" -> a scalar count(*); "which / list / show" -> the rows. Project exactly the columns the question names — no extras, none missing.
Filter literals — build every ==/in/like predicate on a text column from that column's sampled values (inspect it), never a guessed spelling; samples are a top-N snapshot, so when a needed literal is absent verify it (e.g. a distinct-values query) rather than assume either way. Compare case/whitespace-insensitively in the FILTER position only, never on a projected, grouped, or join-key column; abbreviations that case-folding can't unify go in the IN-set. Apply only the transformations (TRIM/ROUND/CAST/dedup) the question or a governing definition requires.
Verify — run the exact final query and read the result (show_sql=true when unsure): row count plausible; no dimension-only GROUP BY when you wanted per-record rows (distinct_dimension_values: false); sort column + direction as asked; each aggregate's scope right; NULL behavior intended; string values carry the expected casing. On a wrong result, change ONE variable at a time — two changes per attempt make the outcome uninterpretable.
Query-object fields taking the functional time-granularity form
gran(col) — gran one of second, minute, hour, day, week,
week_sunday, month, quarter, year:
dimensions: group-by columns; a granularity call such as
month(created_at) buckets that timestamp, equivalent to a
time_dimensions entry (and orderable as month(created_at)).
time_dimensions: time-bucketed group-bys — {"dimension": ..., "granularity": ...} dicts, or the string form month(created_at).
main_time_dimension: which time dimension time-ordered transforms key off.
Top-level arguments (siblings of query, NOT fields inside it):
variables: Values for {placeholder} substitutions in filters / model SQL. Also
settable per query object; precedence: runtime (top-level) > named-stage >
outer-query > model.query_variables.
show_sql: When true, include the generated SQL in the response for debugging.
dry_run: When true, generate and return the SQL without executing it.
explain: When true, run EXPLAIN ANALYZE and return the query plan.
format: Output format — "markdown" (default, compact) | "json" | "csv". Case-insensitive.
Without an explicit limit the response is capped at 20 rows with a truncation notice.
Example: query(query={"source_model": "orders", "dimensions": ["status"], "measures": [{"formula": "count(*)"}], "filters": ["status == 'completed'"]})
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | ||
| format | No | markdown | |
| dry_run | No | ||
| explain | No | ||
| show_sql | No | ||
| variables | No |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| result | Yes |