Offerings
Features & Capabilities
Customer Stories
Learn
Engage
Books
Every BI vendor demos the same AI chat. The real differentiator is the semantic layer underneath, and most are more primitive than they look.
A look-up question on a single fact table will impress any modern LLM, on any vendor. It says almost nothing about whether the underlying system can answer the questions your business actually asks day to day.
Simple metric + dimension combinations. Any LLM with a schema gets this.
"Of customers who signed up in January, what was 30-day conversion vs. April's cohort, and how does ARPU compare for the two groups?"
Four operators stacked into one breath.
If the AI gets user intent right, it hands that intent to the semantic layer. The layer deterministically generates accurate SQL, without guessing or drift.
LLM owns intent and execution.
LLM owns intent. Semantic layer owns execution.
Conventional SQL-based semantic layers handle the simple half. The questions stakeholders actually ask, day to day, sit above the ceiling.
↑ Above the ceiling · pulls an analyst back in
Nested aggregation
"Average revenue per active user."
Period over period
"MTD vs. previous MTD" / "YoY %."
Cohort analysis
"Of Jan signups, how many bought within 7 days?"
Cross-grain ratios
"Conversion rate," numerator and denominator at different grains.
Cross-model metrics
"Revenue spanning orders + products" via relationships.
Parameterized metrics
"N-day conversion" for N = 7, 14, 30, 90.
Where conventional SQL semantic layers stop.
Every question above this line is a ticket back to the data team — and the same ceiling your AI agent inherits the moment you ship it.
Look-up
"Revenue last month."
Slice & dice
"Revenue by region by month."
Filter / group
"Active users by tier in APAC."
Top-N
"Top 10 products by sales."
↓ Below the ceiling · handled by any semantic layer
The share of real user questions that fall outside what a semantic layer can answer natively, forcing the answer to be generated from scratch elsewhere. When that happens, the system ends up in the exact failure mode the semantic layer was meant to prevent.
YES →
NO → leaks back to raw SQL
Semantic leakage doesn't show up in the PoC — the vendor's job is to make sure it doesn't. The data team's job, for the next five years, is to keep it invisible to the rest of the org.
What the vendor sells you
Excel-like formulas the analyst writes inside the workbook, sold as "promotable to the semantic model in one click."
Downstream cost
What the vendor doesn't say
The silent default of SQL-based semantic layers. The team writes one denormalized model per question shape because the semantic layer can't.
What the vendor calls "power-user freedom"
Analyst writes raw SQL in a query runner, bypassing the semantic model for anything the layer can't express.
AI answers questions faster than your team can ask "is this right?" Every verification gate that used to catch errors — peer review, metric ownership, lineage — collapses under the volume.
When semantic leakage meets AI
Errors ship and stay shipped
Stakeholders ask
? ? ? ? ? ? ?
machine speed · 24/7 · AI lowers the cost of asking
AI writes SQL
per query, from scratch
∅ Where peer review used to be
No reviewer in the loop
volume too high · no anchor · no trail
Ships to dashboard
The question every executive eventually asks
"Who confirms any of this is correct?"
Holistics's semantic layer is built on two purpose-built languages — a composable query language and a native development language — both compiled down to standard SQL.
Composable query language for metrics
Metrics as first-class objects
Named, stored, modified, combined like variables. Single source of truth.
metric arpu = revenue / users
High-level analytics functions
Time intelligence, cohort, nested aggregation, and level-of-detail are first-class primitives.
revenue | relative_period(year, -1)
Fully composable operations
A pipe operator chains operations like lego blocks. Unlimited complexity from simple primitives.
revenue | where(...) | by(country) | of_all()
Native development language for analytics
Programmable constructs, like a real language
The same building blocks programmers use to keep code DRY — without YAML config or Jinja-templated SQL.
Type-safe IDE with a live feedback loop
Strongly typed (TypeScript for analytics). Errors caught as you type, before production. The same type system guardrails AI from misreading the semantic layer.
One semantic layer. One compiled SQL output. Every warehouse.
Standard SQL, every warehouse
Snowflake · BigQuery · Postgres · Redshift · Databricks. No proprietary execution layer, no lock-in.
Debuggable, auditable, AI-readable
Analysts read, debug, and explain the generated SQL. Existing query tooling just works.
One AML snippet expresses cross-model metrics, multi-step composition, and a nested top-N — avoiding CTEs, derived tables, and string-interpolated SQL altogether.
metric revenue = sum(order_items, products.price * order_items.quantity); metric user_count = users | count(users.id); metric top_countries = top(3, countries.name, by: user_count); metric top_country_names = top_countries | select(countries.name); metric total_revenue = top_countries | sum(revenue); metric avg_revenue = top_countries | avg(revenue); explore { dimensions { continent: countries.continent_name } measures { top_country_names, avg_revenue, total_revenue } }
revenue reaches across order_items and products via the relationship. Defined once, reused across models.
revenue
order_items
products
total_revenue and avg_revenue compose revenue and top_countries. No copy-paste, no duplication drift.
total_revenue
avg_revenue
top_countries
top_countries is itself a metric. "Top countries by user_count, then revenue within those" composes as one named pipeline.
Every reference is a named metric. No SQL strings mashed together by Jinja or macros. The semantics stay whole — for humans and for AI.
Each primitive below is native, reusable, and AI-readable. Each card is one less place where semantic leakage can happen.
group() | aggregate() | aggregate()
"Average monthly signups across years." Aggregate of aggregate, one composable metric.
previous() · relative_period() period_to_date()
Same period last year, YoY %, MTD vs. previous MTD. Reusable across dashboards.
with_relationships() filter(cohort_dim)
Segment a population by signup window, then track behavior at the cohort grain across time.
relationship() with_relationships()
Numerator and denominator at different grains. One metric, auto-composed across models.
metric args · dataset fields
N-day conversion for N = 7, 14, 30, 90. One metric, parameterized at query time.
pipe | · metric-of-metric
Compose group → filter → aggregate as named, reusable steps. No monolithic CTE.
A PoC that only proves look-up doesn't prove much. The prompts below surface the ceiling early. Ask them of any vendor — including us.
The fastest way to understand the difference is to put a hard question to it and read the SQL it compiles.