# Semantic layer: what it is, how it works and how to choose in 2026

> A semantic layer defines each business metric once and compiles it to SQL for every tool. See a tested MetricFlow example, the 2026 options and AI limits.

- URL: https://computese.com/semantic-layer/
- Author: Duong Quan Nguyen, CEO, Computese
- Published: 2026-09-29
- Updated: 2026-10-09
- Topics: Data, AI & automation

## In short
- A semantic layer defines entities, dimensions and metrics once, in version-controlled code, and compiles each request into SQL, so dashboards, notebooks, apps and AI assistants return the same number.
- In the lab project in this post, one net_revenue definition returned 860 overall, 180 for July 2026 and 370 for Canada, and validation failed the build when an upstream column was renamed.
- As of October 2026 the main options are dbt MetricFlow, Cube, AtScale, Looker, Power BI semantic models, Databricks metric views and Snowflake semantic views. They differ in where definitions live and who can query them.
- In dbt Labs' April 2026 benchmark, a vendor study, two models scored 98.2% and 100.0% through its Semantic Layer against 90.0% and 84.1% writing SQL directly. Questions nobody modelled get an error, not an answer.
- Start with 10 to 20 metrics that each have an owner, test their values in CI, and close the routes that bypass the layer, including assistants that query raw tables.

A semantic layer sits between your data platform and every tool that reads it. It defines business entities, dimensions and metrics once, in code, and compiles each request into SQL with the same joins, filters and access rules. Dashboards, notebooks, apps and AI assistants then return one revenue number instead of one per team.

This guide builds a small MetricFlow project and shows the SQL it generates, compares seven options as of October 2026, explains the Apache Ossie standard, reviews what a semantic layer does for AI answers and lays out a rollout. For where the layer sits in a whole product, see [building data analytics software](https://computese.com/building-data-analytics-software/).

## Why do three dashboards show three different revenue numbers?

Ask three teams for last quarter's revenue and you can get three correct answers. The lab project in this post has nine orders. Finance counts money kept after refunds and skips cancelled orders. Sales counts order value before refunds and also skips cancelled orders. A dashboard built straight on the orders table counts every row.

| Who computes it            | Cancelled orders | Refunds  | Revenue |
| -------------------------- | ---------------- | -------- | ------- |
| Dashboard on the raw table | Counted          | Ignored  | 1,150   |
| Sales (gross revenue)      | Skipped          | Ignored  | 950     |
| Finance (net revenue)      | Skipped          | Deducted | 860     |

The figures are invented: one 200-unit order is cancelled, and three orders carry refunds totalling 90. Currency, time zone and calendar widen the gap. An order placed at 21:00 in Toronto on September 30, 2026 is 01:00 UTC on October 1, so a UTC dashboard books it in October and a Toronto finance report books it in September. A fiscal year that starts on July 1 puts July to September in its first quarter.

Each team can defend its number, so the argument is about definitions, not arithmetic. A semantic layer turns each definition into a named object with one owner and one place to change it. Every dashboard, notebook and assistant then asks for `net_revenue` by name instead of recomputing it.

![Three dashboards read one database and show bars of different heights. Below, the same three dashboards, fed through one orange block, show bars of equal height.](https://computese.com/images/blog/semantic-layer/three-answers.fe41ba9094-1536.webp)

*Three dashboards that compute revenue themselves disagree; fed from one governed definition, they show the same bars.*

## How does a semantic layer work?

A semantic layer does five jobs between the warehouse and whoever asks a question.

1. **Model.** It records which tables hold which business entities, how they join, which columns are dimensions and how each metric aggregates. In [dbt's spec](https://docs.getdbt.com/docs/build/latest-metrics-spec), entities are what you group or join by, and dimensions are what you filter or slice by.
2. **Compile.** A request names metrics and dimensions, never tables. The layer finds where the data lives, performs the joins and writes the SQL, as [dbt's architecture page](https://docs.getdbt.com/docs/use-dbt-semantic-layer/sl-architecture) describes it.
3. **Cache.** [Cube](https://docs.cube.dev/docs/introduction) builds rollup tables called pre-aggregations in its own store and routes a matching query to one. dbt can pre-build [cache tables](https://docs.getdbt.com/docs/use-dbt-semantic-layer/sl-cache) from saved queries, but only on Enterprise-tier plans.
4. **Control access.** Cube applies access policies, written as code and ranging from row-level rules up, before a query reaches the warehouse.
5. **Serve.** One interface for BI tools, notebooks, apps and AI assistants: SQL, REST, GraphQL, JDBC or MCP, depending on the product.

> [!WARNING]
> Cached results can bypass permissions. dbt's documentation says cached data "is stored separately from the underlying models" and that when metrics come from the cache, "we don't have the security context applied to those tables at query time." The page says dbt plans to apply those permissions to cached tables in the future. Cache only metrics that every user of the Semantic Layer may see.

The layer does not clean data or repair a weak model. It assumes the tables underneath have a clear grain, which is the job of a [star schema](https://computese.com/star-schema/) on the gold layer of a [medallion architecture](https://computese.com/medallion-architecture/).

## How is a semantic layer different from a warehouse, a catalog or a mart?

The four overlap in conversation but differ in function.

| Component              | Holds                                    | Its job                         | What it cannot do                             |
| ---------------------- | ---------------------------------------- | ------------------------------- | --------------------------------------------- |
| Warehouse or lakehouse | Tables of data                           | Store and compute               | Stop two queries defining revenue differently |
| Data mart              | A subset of tables for one team          | Give a team focused tables      | Share definitions across marts                |
| Data catalog           | Descriptions, owners and lineage         | Help people find and trust data | Compute a metric                              |
| Semantic layer         | Entities, dimensions and metrics as code | Turn a metric request into SQL  | Hold data or fix a weak model                 |

The boundaries are blurring. [Databricks](https://docs.databricks.com/aws/en/uc-semantics/metric-views/query) defines metric views inside Unity Catalog, and [Snowflake](https://docs.snowflake.com/en/user-guide/views-semantic/overview) stores semantic views as schema-level objects, so the layer can now sit beside the data it describes. The warehouse and its tools are covered in [big data analytics explained](https://computese.com/the-power-of-big-data-analytics/).

## How do you define a metric in MetricFlow?

The lab ran dbt-core 1.12.5, dbt-metricflow 0.15.0 (MetricFlow 0.213.0) and dbt-duckdb 1.11.0 on DuckDB 1.5.6 on October 8, 2026. The [project README](https://github.com/dbt-labs/metricflow) lists MetricFlow as Apache 2.0 licensed from version 0.209.0, after the AGPL and then the BSL. DuckDB is not among the platforms dbt [lists for MetricFlow](https://docs.getdbt.com/docs/build/about-metricflow) (Snowflake, BigQuery, Databricks, Postgres on dbt v1 only, and Redshift), but it lets you reproduce everything on a laptop.

The data is nine orders in `fct_orders` and five customers in `dim_customers`. The YAML follows the latest spec: the semantic model sits under the model, entities and dimensions sit under `columns`, and simple metrics replace the older measures.

```yaml
# models/orders.yml
models:
  - name: fct_orders
    description: One row per order.
    semantic_model:
      enabled: true
    agg_time_dimension: ordered_at

    columns:
      - name: order_id
        entity:
          type: primary
          name: order
      - name: customer_id
        entity:
          type: foreign
          name: customer
      - name: ordered_at
        granularity: day
        dimension:
          type: time
      - name: status
        dimension:
          type: categorical

    metrics:
      - name: gross_revenue
        description: Order totals before refunds. Cancelled orders are excluded.
        type: simple
        agg: sum
        expr: order_total
        filter: "{{ Dimension('order__status') }} != 'cancelled'"
        config:
          meta:
            owner: sales

      - name: refunds
        description: Refunds issued against completed orders.
        type: simple
        agg: sum
        expr: refunded_total
        filter: "{{ Dimension('order__status') }} != 'cancelled'"
        config:
          meta:
            owner: finance
```

Each simple metric carries its own filter, so no dashboard has to remember to drop cancelled orders. The owner sits in `config.meta`, which lands in `target/semantic_manifest.json`, so a script can read it. Metrics that build on other metrics can sit in a top-level `metrics` block, which the spec requires when they depend on a different semantic model:

```yaml
# models/customers.yml
models:
  - name: dim_customers
    semantic_model:
      enabled: true
    columns:
      - name: customer_id
        entity:
          type: primary
          name: customer
      - name: region
        dimension:
          type: categorical

metrics:
  - name: net_revenue
    description: Gross revenue minus refunds. The number finance reports.
    type: derived
    expr: gross_revenue - refunds
    input_metrics:
      - name: gross_revenue
      - name: refunds
    config:
      meta:
        owner: finance

  - name: refund_rate
    description: Refunds as a share of gross revenue.
    type: ratio
    numerator: refunds
    denominator: gross_revenue
    config:
      meta:
        owner: finance
```

A query names metrics and dimensions, never tables. The lab returned:

| Query                                     | Result                            |
| ----------------------------------------- | --------------------------------- |
| `gross_revenue`, `refunds`, `net_revenue` | 950, 90, 860                      |
| `net_revenue` by month                    | 180, 240, 440 (July to September) |
| `net_revenue` for Canada only             | 370                               |
| `refund_rate`                             | 0.0947368                         |
| `net_revenue` by fiscal quarter           | `FY2027-Q1`: 860                  |

`mf query --explain` prints the SQL instead of running it. This is `net_revenue` by month and customer region, with table aliases shortened:

```sql
SELECT
  metric_time__month
  , customer__region
  , gross_revenue - refunds AS net_revenue
FROM (
  SELECT
    metric_time__month
    , customer__region
    , SUM(gross_revenue) AS gross_revenue
    , SUM(refunds) AS refunds
  FROM (
    SELECT
      DATE_TRUNC('month', fct_orders.ordered_at) AS metric_time__month
      , fct_orders.status AS order__status
      , dim_customers.region AS customer__region
      , fct_orders.order_total AS gross_revenue
      , fct_orders.refunded_total AS refunds
    FROM "shop"."main"."fct_orders" fct_orders
    LEFT OUTER JOIN
      "shop"."main"."dim_customers" dim_customers
    ON
      fct_orders.customer_id = dim_customers.customer_id
  ) subq_5
  WHERE order__status != 'cancelled'
  GROUP BY
    metric_time__month
    , customer__region
) subq_9
```

Nobody wrote that filter or join: the filter comes from the metric definitions, the join from the shared `customer` entity, and the outer query applies the derived expression.

![A request card with three chips goes into a gear box, which produces a document of grey bars with one orange bar, then a database cylinder holding two joined tables.](https://computese.com/images/blog/semantic-layer/generated-sql.7bf4fcd447-1536.webp)

*The filter and the join come from the definitions, so every consumer's query carries them.*

> [!TIP]
> Run `mf query --explain` on every new metric and read the SQL once before you publish it. A wrong join or filter is easier to spot in a short query than in a dashboard total.

The fiscal calendar is defined once too. A daily date model carries a `fiscal_quarter` label (the fiscal year starts on July 1), and the time spine config registers it as a custom granularity, so any metric can be grouped by `metric_time__fiscal_quarter`:

```yaml
# models/time_spine.yml
models:
  - name: time_spine_daily
    time_spine:
      standard_granularity_column: date_day
      custom_granularities:
        - name: fiscal_quarter
          column_name: fiscal_quarter
    columns:
      - name: date_day
        granularity: day
      - name: fiscal_quarter
```

## Which metric types can you define, and where do they break?

MetricFlow has [five metric types](https://docs.getdbt.com/docs/build/about-metricflow). The lab covered four.

| Type       | What it computes                                                                  | In the lab               |
| ---------- | --------------------------------------------------------------------------------- | ------------------------ |
| Simple     | One aggregation of one column, with an optional filter                            | `gross_revenue`: 950     |
| Ratio      | One metric divided by another                                                     | `refund_rate`: 0.0947368 |
| Derived    | An expression over other metrics                                                  | `net_revenue`: 860       |
| Cumulative | An aggregation of a simple metric over a window                                   | 120, 200, 500 by month   |
| Conversion | A base event followed by a conversion event for the same entity within a set time | Not tested               |

Cumulative metrics held the surprises:

- **Month start or month end.** Grouped by month with no `period_agg`, the running total of `gross_revenue` read 120, 200 and 500 for July to September: its value when each month opens, with July at 120 because the first order arrived on July 3. Setting `period_agg: last` gave month-end totals of 200, 500 and 950.
- **No time range.** Without a start and end time the same query returned 18 monthly rows, to December 2027 at 950, because the lab's time spine runs that far. Bound every cumulative query.
- **Derived inputs.** A cumulative metric over the derived metric `net_revenue` failed `mf validate-configs` and then failed at query time with the one-word error `'net_revenue'`. The same definition over the simple metric `gross_revenue` worked.

Behaviour like this is why metric values need tests in CI.

## Which semantic layer should you choose in 2026?

Seven options cover most choices as of October 2026. Cells come from each vendor's own documentation; the AtScale cells come from its marketing page, so treat them as claims.

| Option                                                                                      | Definitions live in                                                      | Tools reach it through                                                                                   | Open or sold                                                         |
| ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| [dbt Semantic Layer](https://docs.getdbt.com/docs/use-dbt-semantic-layer/sl-architecture)   | YAML in a dbt project                                                    | CLI; JDBC and GraphQL APIs and integrations such as Tableau, Hex and Google Sheets from Starter plans up | MetricFlow is Apache 2.0; the service layer and APIs are proprietary |
| [Cube](https://docs.cube.dev/docs/introduction)                                             | Cubes and views in YAML or JavaScript                                    | Postgres-compatible SQL with `MEASURE`, REST, GraphQL, MCP                                               | Cube Core is open source; the Cube platform is built on it           |
| [AtScale](https://www.atscale.com/use-cases/universal-semantic-layer/)                      | SML models, open-sourced                                                 | Excel, Tableau, Power BI, Python and MCP, resolved against optimized aggregates                          | Commercial, priced on consumption                                    |
| [Looker](https://docs.cloud.google.com/looker/docs/sql-interface)                           | LookML project files                                                     | Looker itself; Open SQL Interface over JDBC, with no joins, window functions or subqueries               | Part of Looker                                                       |
| [Power BI](https://learn.microsoft.com/en-us/power-bi/connect-data/service-datasets-rename) | A semantic model, an Analysis Services data model (renamed from dataset) | Power BI reports                                                                                         | Part of Power BI, one experience in Microsoft Fabric                 |
| Databricks                                                                                  | Metric views in YAML                                                     | SQL with `MEASURE()`; AI/BI dashboards, Genie Agents, alerts                                             | Part of Databricks                                                   |
| Snowflake                                                                                   | Semantic views, schema-level objects                                     | `SELECT` statements; Cortex Agents                                                                       | Part of Snowflake                                                    |

Two details change the choice: Looker's SQL interface needs a LookML project on a BigQuery connection and wraps every measure in `AGGREGATE()`, and Power BI's rename was a rename only, with REST API operations that still say dataset. How to choose:

- **dbt already runs your transformations.** Start with MetricFlow. The open-source engine covers definitions, validation and a CI check; the APIs and BI integrations need a dbt platform plan.
- **Applications and embedded analytics come first.** Cube pairs a data model with REST, GraphQL and SQL interfaces for them.
- **Several BI tools must share one model.** AtScale sells exactly that, and its own page names Excel, Tableau and Power BI. Prove its claims in a pilot.
- **One BI tool dominates.** Its native model is the cheapest start, but other tools get less: Looker's SQL interface has no joins.
- **Your platform is Databricks or Snowflake.** Definitions sit beside the data, and each vendor's own AI agents read them.

Whatever you pick, ask three questions: can you review definitions in Git, who can query them and through which interface, and what do you lose if you leave?

## What are Open Semantic Interchange and Apache Ossie?

Every product above stores definitions in its own format, so moving between them means rewriting. Open Semantic Interchange (OSI) set out to fix that. Snowflake was one of its founding organizations, and the repository opened in November 2025. On July 10, 2026 the project announced a new name, [Apache Ossie (Incubating)](https://ossie.apache.org/updates/ossie-enters-apache-incubator/), after joining the Apache Incubator, to avoid confusion with other projects that share the OSI acronym. The announcement says the specification did not change and that the coalition grew from 17 launch partners to more than 50 organizations. Incubation is an early stage: the project notice says it "has yet to be fully endorsed" by the Apache Software Foundation.

dbt v1.12 reads [Ossie documents](https://docs.getdbt.com/docs/build/ossie-semantic-models) in `.json`, versioned 0.1.0 or 0.1.1, from an `osi/` folder. Support for dbt v2 is listed as coming soon, and constructs it cannot convert are dropped with warning `I078`. In the lab, dbt 1.12.5 also wrote `target/osi_document.json` for the native project above, with each metric flattened to an ANSI SQL expression (excerpt):

```json
{
  "name": "net_revenue",
  "expression": {
    "dialects": [
      {
        "dialect": "ANSI_SQL",
        "expression": "SUM(CASE WHEN order__status != 'cancelled' THEN fct_orders.order_total END) - SUM(CASE WHEN order__status != 'cancelled' THEN fct_orders.refunded_total END)"
      }
    ]
  }
}
```

Two limits showed up. The filter uses MetricFlow's name `order__status`, while the dataset field in the same file is called `status`. And when the project held cumulative metrics, dbt warned that their "cumulative window and grain semantics cannot be represented" and exported them as ordinary sums. The Ossie announcement lists "advanced metric logic, windowing functions" among additions the community may propose, and adds that none of it is predetermined. Use the format to move simple, ratio and derived metrics, and diff exported expressions against your definitions before you trust a round trip.

## How does a semantic layer help AI assistants, and where does it stop?

An assistant writing SQL from a question works from table and column names and writes a new query every time. dbt Labs says the model "has to infer the semantics of your data from structural clues". With a semantic layer the task shrinks: the assistant picks metrics and dimensions by name, and the layer writes the SQL. The [dbt MCP server](https://docs.getdbt.com/docs/dbt-ai/mcp-available-tools) offers `list_metrics`, `get_dimensions` and `query_metrics` for that, and `get_metrics_compiled_sql` returns the SQL without running it. Cube describes the same pattern: every query is "validated against the data model and has access policies applied deterministically before reaching the warehouse".

![A laptop chat window sends a question into an orange block that queries a database cylinder, while a dashed line straight from the chat to the cylinder is broken in the middle.](https://computese.com/images/blog/semantic-layer/assistant-path.ffa9be5e08-1536.webp)

*An assistant that asks for metrics by name gets governed numbers; one with direct table access writes its own SQL.*

Not every product compiles the query. Snowflake's documentation says Cortex Agents currently "reads the information captured in the semantic view definition and generates the SQL against the physical tables directly": the definitions guide the model, but the model still writes the SQL.

In April 2026 dbt Labs [reran its text-to-SQL comparison](https://docs.getdbt.com/blog/semantic-layer-vs-text-to-sql-2026), vendor research built on the ACME Insurance benchmark created by Juan Sequeda and colleagues at data.world: 11 questions, each run 20 times, across 15 tables. On the modelled project:

| Model             | Text-to-SQL | Semantic Layer |
| ----------------- | ----------- | -------------- |
| claude-sonnet-4-6 | 90.0%       | 98.2%          |
| gpt-5.3-codex     | 84.1%       | 100.0%         |

Three details matter more than the headline. The text-to-SQL runs loaded "the entire schema as context, which isn't practical for larger datasets". Before extra modelling, some questions could not be answered through the layer at all, because the normalized schema needed too many entity hops. And three new dbt models, drafted by an LLM, let the layer answer every question. The authors sum up the failure modes: "With text-to-SQL, failure looks like a plausible but incorrect answer. With the Semantic Layer, failure looks like an error message."

The limits are plain. A layer cannot answer a question nobody modelled, cannot correct a wrong definition, and does nothing for an assistant that holds warehouse credentials and writes its own SQL. Treat the assistant like any other consumer: read-only tools with the least access they need, answers grounded in approved sources, and an evaluation set built from real questions that runs before every change, which is how Computese runs [AI and automation](https://computese.com/services/ai-automation/) work. Text-to-SQL and its risks are covered in [generative AI for databases](https://computese.com/generative-ai-for-databases/).

## How do you introduce a semantic layer without a big-bang project?

Prove one number end to end before modelling anything else:

1. **Pick 10 to 20 metrics** that appear in board and finance reports. Write each as a sentence first: what it counts, what it excludes, its time grain and who signs it off.
2. **Name one owner per metric.** Computese's data process uses the same gate: every key metric has one definition and one owner.
3. **Model the tables underneath.** One fact table per business event, conformed dimensions, a stated grain.
4. **Define the metrics in code** and review them in pull requests like any other change.
5. **Reconcile before you switch.** Run old and new side by side, compare totals within a tolerance the business agrees, and have the owner sign off before the old report is retired.
6. **Migrate the top dashboards first,** then delete the duplicate calculations from the BI tools.
7. **Close the side doors.** Give BI and AI service accounts access to the layer, not to the raw tables.

Tests make the plan stick. The lab's `check_metrics.sh` builds the project, validates every definition, fails on any metric without an owner, and compares one value that finance has signed off:

```bash
#!/usr/bin/env bash
# Fails the build when a definition breaks, a metric has no owner or a value drifts.
set -euo pipefail

dbt build --quiet   # also writes target/semantic_manifest.json, which mf reads
mf validate-configs

python3 - <<'PY'
import json, sys
metrics = json.load(open("target/semantic_manifest.json"))["metrics"]
missing = [m["name"] for m in metrics if not m["config"]["meta"].get("owner")]
if missing:
    sys.exit("metrics without an owner: " + ", ".join(missing))
PY

mf query --metrics net_revenue --group-by metric_time__month \
  --start-time 2026-07-01 --end-time 2026-07-31 --csv net_revenue_2026_07.csv

got="$(tail -n 1 net_revenue_2026_07.csv | tr -d '\r' | cut -d, -f2)"
want="180"
if [ "$got" != "$want" ]; then
  echo "net_revenue for 2026-07: got $got, expected $want" >&2
  exit 1
fi
```

On the project as it stands the script exits 0. With `want="181"` it prints `net_revenue for 2026-07: got 180, expected 181` and exits 1. With the owner removed from `refunds` it stops with `metrics without an owner: refunds`.

Validation earns its place on upstream changes. When the lab renamed `refunded_total` to `refunded_amount` in the `fct_orders` model, the view still built, but `mf validate-configs` exited 1 with five errors: the `fct_orders` semantic model, the `refunds` measure and the metrics `refunds`, `net_revenue` and `refund_rate`, each reporting that column `refunded_total` was not found. Restoring the column made the script pass again.

| Symptom                                         | Likely cause                                             | Fix                                                       |
| ----------------------------------------------- | -------------------------------------------------------- | --------------------------------------------------------- |
| Two dashboards still disagree                   | A BI tool computes the metric itself or reads raw tables | Delete the local calculation and remove the direct grants |
| Build fails after an upstream change            | A column was renamed or dropped                          | Fix the model and the definition in the same pull request |
| A running total returns months with no activity | A cumulative metric queried with no time range           | Pass a start and end time                                 |
| Users see rows they should not                  | Cached tables sit outside the source permissions         | Cache only metrics every user may see                     |
| Slow dashboards after go-live                   | Every query goes to the warehouse                        | Add pre-aggregation or caching for the busiest queries    |

Computese builds this as part of its [data platform service](https://computese.com/services/data-platform/): models pass their tests in CI before production, and new numbers are reconciled with the old ones and signed off by the business.

## Key terms
- **Semantic layer**: A layer between the data platform and every tool that reads it. It defines entities, dimensions and metrics once, in code, and compiles requests into SQL.
- **Metric**: A named business calculation, such as net revenue, defined once with its aggregation, its filters and the dimensions it can be sliced by.
- **Semantic model**: In MetricFlow, a dbt model annotated with entities, dimensions and metrics so the engine knows how to join and group it. Power BI uses the same words for what it used to call a dataset.
- **Entity**: A key that identifies a real-world object such as an order or a customer. MetricFlow uses entities to work out how semantic models join.
- **Dimension**: An attribute used to filter or group a metric, such as order status, region or order date.
- **MetricFlow**: The engine behind the dbt Semantic Layer. It reads YAML definitions and generates SQL. It is licensed under Apache 2.0 from version 0.209.0.
- **Time spine**: A date table with one row per day that MetricFlow reads as its calendar for time-based metrics. It can also carry custom calendars such as a fiscal quarter.
- **Apache Ossie**: The name, since July 2026, of Open Semantic Interchange (OSI): an open specification for exchanging metrics, dimensions and relationships between tools. It is in the Apache Incubator.
- **Text-to-SQL**: Asking a language model to write SQL from a plain-language question, using the schema as context. The model writes a new query on every request.

## Common questions

### What is a semantic layer in simple terms?

It is a translation layer between your data and the people or programs that ask questions about it. Someone defines what revenue or active customer means once, in code, and every dashboard, notebook and AI assistant gets that definition. The layer writes the SQL, so users ask for a metric instead of joining tables.

### Is a semantic layer the same as a data warehouse or data mart?

No. A warehouse or mart stores the data. A semantic layer stores no data: it holds definitions and generates the SQL that reads the warehouse. Some products now keep those definitions inside the warehouse's own catalog, which blurs where one ends and the other starts.

### Is the dbt Semantic Layer free?

The MetricFlow engine is Apache 2.0 licensed from version 0.209.0 and covers definitions, validation and querying from the command line. The semantic APIs and BI integrations sit in the dbt platform (Starter, Enterprise and Enterprise+ plans), and caching needs an Enterprise-tier plan. The README still opens its licence history with the words source-available, so read the version list rather than the label.

### Does a semantic layer make AI answers accurate?

It makes answers repeatable for the questions you have modelled. In dbt Labs' April 2026 benchmark, a vendor study, two models scored 98.2% and 100.0% through the layer against 90.0% and 84.1% writing SQL directly. A question outside the model gets an error rather than a guess, so you still need an evaluation set and a plan to widen coverage.

### What is the difference between a semantic layer, a metrics layer and headless BI?

Treat them as overlapping names, not separate categories. Compare products on what they define (metrics only, or entities, dimensions and joins as well), how tools query them and who can bypass them.

### What is Apache Ossie?

Apache Ossie (incubating) is the new name for Open Semantic Interchange, an open specification for exchanging business metrics, dimensions and relationships between tools. The rename was announced on July 10, 2026, and the project is still in the Apache Incubator.

### How many metrics should a first semantic layer cover?

Start with 10 to 20 core metrics from board and finance reports, each with one owner, and prove them end to end before adding more. Modelling everything at once delays the first number people trust.

## Sources
1. [About MetricFlow](https://docs.getdbt.com/docs/build/about-metricflow), dbt Labs
2. [Migrate to the latest YAML spec](https://docs.getdbt.com/docs/build/latest-metrics-spec), dbt Labs
3. [dbt Semantic Layer architecture](https://docs.getdbt.com/docs/use-dbt-semantic-layer/sl-architecture), dbt Labs
4. [Cache common queries](https://docs.getdbt.com/docs/use-dbt-semantic-layer/sl-cache), dbt Labs
5. [Welcome to MetricFlow (README and licence history)](https://github.com/dbt-labs/metricflow), dbt Labs on GitHub
6. [Apache Ossie semantic layer documents](https://docs.getdbt.com/docs/build/ossie-semantic-models), dbt Labs
7. [Available tools (dbt MCP server)](https://docs.getdbt.com/docs/dbt-ai/mcp-available-tools), dbt Labs
8. [Semantic Layer vs. Text-to-SQL: 2026 Benchmark Update](https://docs.getdbt.com/blog/semantic-layer-vs-text-to-sql-2026), dbt Labs
9. [Apache Ossie (Incubating): The New Name for Open Semantic Interchange](https://ossie.apache.org/updates/ossie-enters-apache-incubator/), Apache Ossie
10. [Introduction](https://docs.cube.dev/docs/introduction), Cube
11. [The Universal Semantic Layer for Enterprise AI and Analytics](https://www.atscale.com/use-cases/universal-semantic-layer/), AtScale
12. [Open SQL Interface](https://docs.cloud.google.com/looker/docs/sql-interface), Google Cloud (Looker)
13. [New name for Power BI datasets](https://learn.microsoft.com/en-us/power-bi/connect-data/service-datasets-rename), Microsoft
14. [Query metric views](https://docs.databricks.com/aws/en/uc-semantics/metric-views/query), Databricks
15. [Overview of semantic views](https://docs.snowflake.com/en/user-guide/views-semantic/overview), Snowflake
