Customer Stories
Learn
Engage
Books
AQL is the high-level query language for analytics. Expressive enough for an AI agent to answer any question, concise enough for a human to trust the answer.
metric revenue = sum(order_items.quantity * products.price) metric refunded_revenue = revenue | where(orders.status == 'refunded') metric refund_rate = safe_divide(refunded_revenue, revenue)
AQL works at the level of metrics, not tables. You define a metric once on your semantic model, then ask for it by name, and AQL compiles that to native SQL.
metric revenue = sum(order_items.quantity * products.price)
explore { dimensions { customers.country } measures { revenue } }
SELECT c.country, SUM(oi.quantity * p.price) AS revenue FROM order_items oi JOIN products p ON oi.product_id = p.id JOIN orders o ON oi.order_id = o.id JOIN customers c ON o.customer_id = c.id GROUP BY c.country ORDER BY revenue DESC
"I'd score AQL a 9 or 10. It's up there with the best tools I've used. We can now define metrics based on other metrics, stacking them on top of each other."
Most semantic layers store metrics as SQL strings, so composition breaks the moment one metric builds on another. AQL makes each metric a real object you compose like functions, then compiles to native SQL.
The CTE-and-window gymnastics SQL forces on you are single functions here.
Define a metric once, and others build on it by reference. Metrics compose like functions, instead of repeating each other's SQL.
AQL runs on your semantic model, so queries reference your defined metrics, dimensions, and relationships by name — not raw schema.
AQL is a compile step, not a runtime. It outputs standard SQL that runs natively in your warehouse, with no proprietary engine in between.
Write once. The compiler speaks the dialect.
A strong type system validates queries as you write — errors surface in the IDE, not in production dashboards.
Most natural-language tools translate your question straight into SQL. Holistics adds an intermediary step. The AI first writes concise, high-level AQL, which a compiler then turns into your warehouse's SQL.
metric count_users = count(users.id)dimensions { region: country.region }measures { avg_revenue: top(5, countries.id, by: count_users) | avg(revenue)}
WITH "aql__t3" AS ( SELECT "ecommerce_countries"."continent_name" AS "ecommerce_countries→continent_name", "ecommerce_countries"."name" AS "name", COUNT("ecommerce_users"."id") AS "count_ecommerce_users→id" FROM "ecommerce"."users" "ecommerce_users" JOIN "ecommerce"."countries" …
Routing the AI through AQL instead of straight to SQL pays off on every axis that matters for agentic analytics.
The answer comes back as a few lines of compact, high-level logic instead of pages of SQL. It's small enough to read, comprehend, and verify yourself.
Working in a high-level language, the AI never sweats database-specific syntax or SQL gymnastics for common analytics, so it's far more accurate than a model writing raw SQL directly.
AQL works directly with your semantic layer, so generated logic always uses the correct metric and dimension definitions, and clears access-control checks before it serves an end user.
Composability and 90+ pre-built analytical functions let it handle far more complex use cases, the long-tail questions that direct SQL generation stumbles on.
Period comparisons, cohorts, percent-of-total, nested aggregation — the analytics that mean a new dataset or a custom SQL block almost everywhere else. With AQL, each one is a single composable expression.
metric revenue_ly = revenue | relative_period(-1 year) metric yoy_change = safe_divide(revenue - revenue_ly, revenue_ly)
WITH monthly AS ( SELECT DATE_TRUNC('month', created_at) AS month, SUM(amount) AS revenue FROM orders GROUP BY 1 ) SELECT curr.month, curr.revenue, (curr.revenue - prev.revenue) * 100.0 / prev.revenue AS yoy_change FROM monthly curr LEFT JOIN monthly prev ON curr.month = prev.month + INTERVAL '1 year'
metric category_share = (revenue * 1.0) / (revenue | of_all(products.category))
SELECT p.category, SUM(oi.quantity * p.price) * 100.0 / SUM(SUM(oi.quantity * p.price)) OVER () AS pct_of_total FROM order_items oi JOIN products p ON p.id = oi.product_id GROUP BY 1
The percentage stuff is really powerful — percentage of the whole, of this dimension, of that one. It's powerful because percentage is always "but of what?"
users | group(users.created_at | month()) | select(monthly_count: count(users.id)) | avg(monthly_count)
SELECT AVG(monthly_count) FROM ( SELECT DATE_TRUNC('month', created_at) AS month, COUNT(*) AS monthly_count FROM users GROUP BY 1 ) sub
dimension acquisition_cohort = min(orders.created_at | month()) | dimensionalize(users.id) metric retention = (total_users * 1.0) / (total_users | of_all(orders.month_no))
WITH first_orders AS ( SELECT user_id, DATE_TRUNC('month', MIN(created_at)) AS cohort_month FROM orders GROUP BY 1 ), activity AS ( SELECT o.user_id, f.cohort_month, …
metric moving_avg_3m = trailing_period(revenue, orders.created_at, interval(3 months)) / 3
SELECT month, AVG(revenue) OVER ( ORDER BY month ROWS BETWEEN 2 PRECEDING AND CURRENT ROW ) AS moving_avg_3m FROM monthly_revenue -- counts rows, not months: a gap month -- silently skews the average; fixing it -- needs a generated calendar spine
dimension performance_tier = case( when: ntile(4, order: revenue | desc()) = 1, then: 'Top 25%', when: ntile(4, order: revenue | desc()) = 2, then: '25–50%', else: 'Bottom 50%' ) | dimensionalize(products.id)
SELECT product_id, CASE WHEN NTILE(4) OVER (ORDER BY revenue DESC) = 1 THEN 'Top 25%' WHEN NTILE(4) OVER (ORDER BY revenue DESC) = 2 THEN '25–50%' ELSE 'Bottom 50%' END AS tier FROM ( SELECT product_id, SUM(amount) AS revenue FROM order_items GROUP BY 1 ) t
The harder the question, the more the language matters.
"AQL lets me create a cohort view I couldn't do before. Every time I wanted a different type of cohort, I had to go back to the model and add to it — now I can just do it. A dynamic, flexible cohort viz, for a retention person, is really powerful for understanding the drivers behind the successful cohorts."
A metric in AQL is a real semantic object, not a SQL string — so the AI builds on it like code, instead of pasting SQL together.
Semantic-aware — it knows your models and relationships, so the join resolves itself. There's no SQL text to copy.
metric refunded_revenue = revenue | where(orders.status == 'refunded') metric refund_rate = safe_divide(refunded_revenue, revenue)
Each metric builds on the one before by reference, like functions. A string-based tool would re-embed all of revenue's SQL here — AQL never does.
revenue
Define it once and it stays one definition — change revenue and everything built on it follows, and the same metric works in any report, sliced any way.
The AI reuses your team's endorsed metrics and composes new ones from existing definitions — it doesn't reinvent "revenue" from raw tables, and access controls in the semantic layer apply automatically.
The AI composes from endorsed definitions — it never invents an unverified metric to answer a question.
AQL compiles to native SQL you can inspect on every query — nothing is hidden. Your definitions are plain-text code in Git, not rows in a vendor database. If you leave, you leave with readable logic.
AQL is a complement, not a replacement. SQL is great at pipelines and ad-hoc exploration; it has no native way to define a metric once and compose it. AQL covers exactly that gap — and when you need raw SQL, passthrough functions (sql_number, agg_text, …) are the escape hatch.
sql_number
agg_text
AQL is designed for agents to write and humans to read. The AI writes most of it; your job is to read a few lines and confirm it understood the question — and because the logic is high-level, that read is fast. When you do want to write it yourself, anyone who reads SQL picks it up from the 30-minute guided tour.
"The AI does far more than just fetch answers — it understands the context and generates working solutions, even for edge cases."
In production across hundreds of enterprises.
Go deeper: the functions cheatsheet · learn AQL in 30 minutes · see the full Holistics AI product →