by Holistics
Try with anfra

AMQL · Analytics Modeling & Query Language

The open, programmable semantic layer.

Define reusable metrics and business logic as code. Compose complex analytical queries with functions that operate on metrics, and compile them into native warehouse SQL.

AMLecommerce.aml
Model orders {
  table_name: 'ecommerce.orders'

  dimension status     { type: 'text' }
  dimension created_at { type: 'datetime' }
}

// assemble models into a governed dataset
Dataset ecommerce {
  models: [orders, order_items, products, users]
  relationships: [
    relationship(order_items.order_id > orders.id, true),
    relationship(order_items.product_id > products.id, true)
  ]

  metric revenue {
    type: 'number'
    definition: @aql
      sum(order_items,
          order_items.quantity * products.price) ;;
  }
}
AQLqueryrevenue, vs. last year
revenue
  | where(orders.status != 'refunded')
  | relative_period(orders.created_at,
                    interval(-1 year))

Powerful analytics

Go beyond basic metrics with composable calculations and reusable analytical functions.

Open by design

Own your definitions in Git. Keep core semantic models portable through open standards.

Built for trust

Keep metrics consistent across reports and AI. Catch errors early and inspect how results are calculated.

The semantic layer behind analytics at

  • DNSFilter
  • Strongmind
  • Mainspring Energy
  • ZOE
  • Aurora
  • Retail Insight
  • Pinter
  • World Vision

Model it once. Ask it anything.

AMQL is two languages over one model. AML defines what your data means. You ask your question in AQL, and the compiler turns that AQL into SQL on your warehouse.

Read the docs
AMLAnalytics Modeling Language

Programmable modeling, as code

  • Models, relationships and metrics as code, with types and a compiler.
  • Reuse with extend, Func and Partial instead of copying blocks.
  • Lives in Git. Reviewed, tested, versioned like the rest of your code.
Explore AML
AQLAnalytics Query Language

The metric query language

  • Ask by metric name. The model supplies the joins and the grain.
  • Time comparisons, running totals, nested aggregation in one expression.
  • Write your own functions, and pass them whole metrics.
Explore AQL

Define once, reuse everywhere

A metric is written one time and every query, dashboard and agent reads that one definition.

Numbers you can trust at any grain

The model knows the joins and the grain, so a total stays right when you slice, filter or join.

Think in metrics, not tables

Queries ask for the metrics and dimensions your team already defined. Closer to how the business thinks, and expressive enough for an AI agent to reason from.

Every proven modeling pattern, built in

AML models every proven warehouse pattern, from a single star to shared dimensions, deep hierarchies, role-playing dates, composite keys and validity windows. Declare facts, dimensions and relationships once, and the model stays accurate and easy to maintain as tables and questions grow.

Explore AML

One business process, many ways to slice it.

Star schema in the docs
AMLecommerce.dataset.aml13 lines
Dataset ecommerce {
  models: [
    fct_orders,     // fact
    dim_users,      // dimension
    dim_products,   // dimension
    dim_dates       // dimension
  ]
  relationships: [
    relationship(fct_orders.user_id    > dim_users.id, true),
    relationship(fct_orders.product_id > dim_products.id, true),
    relationship(fct_orders.created_at > dim_dates.date, true)
  ]
}
fct_ordersdim_usersdim_productsdim_dates
FactDimensionRelationship

Express complex analytics as simply as you think them

Six real analytical questions, from time comparisons to cohort retention. AQL expresses reusable analytical logic, while SQL spells out the query mechanics. See how the same calculations can be expressed with composable metrics instead of repeatedly writing joins, aggregations and window functions.

Explore AQL

“How does revenue compare with the same month last year?”

AQLReusable AQL metric4 lines
// Report: by order month, showing
// sum_revenue beside this metric
metric revenue_previous_year =
  sum_revenue
  | relative_period(orders.created_at,
                    interval(-1 year));
SQLEquivalent SQL query15 lines
WITH monthly AS (
  SELECT date_trunc('month', o.created_at) AS month,
         SUM(oi.quantity * p.price)       AS revenue
  FROM orders o
  JOIN order_items oi ON oi.order_id = o.id
  JOIN products p     ON p.id = oi.product_id
  GROUP BY 1
)
SELECT curr.month,
       curr.revenue AS sum_revenue,
       prev.revenue AS revenue_previous_year
FROM monthly curr
LEFT JOIN monthly prev
  ON prev.month = curr.month - INTERVAL '1 year'
ORDER BY curr.month

Go beyond metrics. Program your analytics.

Turn business rules and analytical patterns into reusable functions. Pass metrics as inputs, transform how they’re calculated, and reuse the same logic across your analytics.

Try in playground

Define a business rule as a function that accepts a metric. Apply the same eligibility logic consistently to revenue, order count and customer count.

Read about AQL functions
AQLacme.aml13 lines
AqlFunctionModule Acme {
  // what counts as a sale, company-wide
  function Billable {
    param pMetric {
      type: 'metric'
    }
    return_type: 'number'
    definition: @aql
      pMetric
      | where(users.is_internal == false,
              orders.status != 'refunded') ;;
  }
}
AQLecommerce.aml17 lines
Dataset ecommerce {
  function_module Acme: Acme

  metric revenue {
    definition: @aql
      Acme.Billable(sum(orders.amount)) ;;
  }
  metric order_count {
    definition: @aql
      Acme.Billable(count(orders.id)) ;;
  }
  metric customer_count {
    definition: @aql
      Acme.Billable(
        count_distinct(orders.user_id)) ;;
  }
}

Sits on your stack. Owned by you.

The fear with any semantic layer is that your business logic ends up inside a vendor. Here it is plain text in your Git, compiles to SQL you can read, and moves to an open interchange format on one command.

See the SQL in the playground

Your logic is code in your Git

AML and AQL are plain-text files in your own repo, not rows in a vendor database. Clone, grep, diff, move.

Every query compiles to SQL you can read

No proprietary runtime. The output runs on your warehouse with nothing of AMQL in the path.

Moves with you through Apache Ossie

Export any dataset to Ossie, the open semantic interchange format, and import an Ossie model back. Holistics maintains the converter inside the Apache Ossie repo.

Ossie ecosystem

Reusable logic, in their words

“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
“I have KPI widgets that appear in 50 rows of my dashboard. I took one function, parameterized it, and just pass in the variables. The ability to parameterize a widget and reuse it was a game changer.”
Mike K.Director of Enterprise Analytics, Strongmind
“The semantic layer is Holistics’ biggest draw, because I was able to build reusable models, dimensions, and metrics, and then describe them as code.”
Sterling ParamoreSr Staff Data Engineer, Mainspring Energy

One semantic model. Every surface.

Define it once in AMQL. Run it in anfra or Holistics, on your warehouse, for every consumer.

“Isn’t this just another proprietary language?”

Isn’t this just YAML in Git?

No. YAML in Git is still YAML: no functions, no types, no real composition. Reuse means copy-paste or Jinja string-templating. AML is a typed language where reuse (extend, Func, Partial) is a first-class feature the compiler checks for you, and AQL lets you write your own functions that take whole metrics.

Am I locked in?

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.

Why not just SQL?

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.

What about the learning curve?

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. When you do want to write it yourself, anyone who reads SQL picks it up from the 30-minute guided tour.