Skip to content

Concepts / The semantic model

The semantic model ​

A semantic model is the governed translation between physical tables (ord_hdr, cust_mst) and business meaning ("Net Revenue", "Customer", "Region"). In Keel it is one document — entities, joins and metrics — stored as immutable, content-addressed revisions (ADR-0008). The same document drives the workspace canvas, the YAML code view, the query compiler, lineage, exports and the MCP catalog.

Building blocks ​

ObjectWhat it isExample
EntityA business thing backed by one table (or a SQL query) with a primary key and a kind: Fact, Dimension, Bridge, Snapshot, Slowly changing dimension, Aggregate, Event, Other.customer on dw.dim_customer
DimensionAn attribute to group or filter by: Categorical, Time (with grains Day…Year), Boolean, Geo or Numeric; may carry a sensitivity.customer.email (Restricted)
MeasureAn aggregation over a column or expression (Sum, Avg, Count, CountDistinct, Min, Max) with its additivity (Additive, SemiAdditive, NonAdditive).credit_limit — semi-additive
HierarchyOrdered dimension levels for drill-down.country → state → city
SegmentA named filter on an entity.high_income
JoinA link between two entities: key columns, cardinality (ManyToOne, OneToOne, ManyToMany), join type, and a role for role-playing joins.internet_sales → date as order_date, due_date, ship_date
MetricWhat consumers ask for: Simple (one measure), Ratio, Derived (an expression over metrics), Cumulative or Conversion; with a certification level.gross_margin = gross_profit / revenue

Each object has a snake_case name, an optional business name and description, tags, an owner, and provenance: origin (Discovered, Inferred, LlmSuggested, UserDefined, Imported), confidence and rationale.

In YAML ​

The workspace's code view and the open-YAML export show the same document:

yaml
entities:
  - name: customer
    businessName: Customer
    kind: Dimension
    source: { dataSourceId: 0b5e…0001, schema: dw, table: dim_customer }
    primaryKey: [customer_key]
    dimensions:
      - { name: email, expression: email_address, type: Categorical, sensitivity: Restricted }
      - { name: full_name, expression: "CONCAT(first_name, ' ', last_name)", type: Categorical }
    measures:
      - { name: credit_limit, expression: credit_limit, aggregation: Sum, additivity: SemiAdditive }
    segments:
      - { name: high_income, filter: "yearly_income >= 100000" }
joins:
  - name: internet_sales_customer
    left: internet_sales
    right: customer
    keys: [{ leftColumn: customer_key, rightColumn: customer_key }]
    cardinality: ManyToOne
    joinType: Left
metrics:
  - { name: revenue, type: Simple, measure: internet_sales.sales_amount }
  - { name: gross_margin, type: Ratio, numerator: gross_profit, denominator: revenue, businessName: Gross Margin % }

The canonical form sorts every named collection, omits empty values and uses string enums; a revision's content address is the SHA-256 of that canonical JSON, so identical documents always hash the same.

Validation ​

ModelValidator runs on every commit, autosave and change request. Errors (block a merge): invalid or duplicate names, unknown entities or columns (checked against the latest scan), missing join keys, ambiguous join paths, summing a non-additive measure, unresolved or cyclic metric references, time metrics without a time dimension. Warnings: fan traps and chasm traps — the compiler handles both safely, the warning just makes them visible.

How queries use it ​

The query compiler binds a request's metric and dimension names against a release of the model, plans the join paths (the unique shortest path over many-to-one and one-to-one joins, honouring roles), aggregates each fact at its own grain to avoid fan-out, applies access policies, and generates parameterized SQL for the source's dialect.

Anchor Keel is a working product name: one platform, a shared core and licensed modules (Keel Semantic, Keel Migrate). Documentation built from the repository — facts are derived from code, ADRs and the decisions log.