90+ prebuilt functions
The CTE-and-window gymnastics SQL forces on you are single functions here.
AQL · Analytics Query Language
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.
What’s our refund rate?
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.”
IanBI Engineer, DNSFilter · 9–10 · AQL, scored by a BI engineerMost 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.
In each region, what’s the average revenue of the top 5 countries by users?
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" …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
) subdimension 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, …… 20+ more lines of CTEs and date math
metric moving_avg_3m =
trailing_period(
revenue, orders.created_at,
interval(3 months)
) / 3SELECT 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 spinedimension 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“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.”
Alex HancockDirector, Data / Digital / Loyalty, Pinter · On cohort analysisA 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 revenue =
sum(order_items.quantity
* products.price)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.
metric refunded_revenue =
revenue | where(orders.status == 'refunded')
metric refund_rate =
safe_divide(refunded_revenue, revenue)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.
Lineage
Catalog status
weekly_revenueEndorsedregional_revenueEndorsedai_calc_revenue_v2BlockedAQL 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, but it has no native way to define a metric once and compose it. AQL covers exactly that gap. When you need raw SQL, passthrough functions (sql_number, agg_text, …) are the escape hatch.
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.”
Guido StarkHead of Data, World VisionGo deeper: the functions cheatsheet, learn AQL in 30 minutes, and the AML modeling language