Skip to content

Metric contracts in practice, from a pinned definition to a running query

A metric contract is what a semantic layer looks like once something is actually enforcing it. How a definition gets pinned, what happens to a question naming fields from two warehouses, and why the identifiers reaching the SQL come from the contract itself.

Updated · 6 min read

From a published definition to a pinned one

A definition that has been published and a definition that has been pinned look identical in a catalogue browser. Both carry a name, an expression and an owner. The difference shows up at the moment somebody asks a question.

Pinning is what happens when the layer reads the platform’s own contract object once and stores what it found: the members, their expressions, and the object they came from. Every question after that is resolved against the stored copy rather than against whatever the asker typed.

The stored copy holds names and expressions, never rows. It is re-learned on every sync: a measure added in the warehouse on Tuesday arrives on Tuesday’s sync, and nobody has to remember to retype it.

What a contract holds

A learned contract is a short, boring document, and the dullness is the feature.

  • The object it was learned from: a semantic model, a semantic view or a metric view, named in full.
  • Dimensions, each carrying the entity it belongs to and the field underneath it.
  • Measures, each carrying its aggregation and its field.
  • The synonyms the platform published, which is how a business word finds the member behind it.

A contract is a description and an allowlist at the same time. The document that tells a person what exists is the document that tells the query builder what may run. There is never a second copy to keep in step.

The names you can ask for

All four platforms let you define the same thing, and all four have their own idea of what a member is called. The vocabulary is worth settling before your first query, because the first few failures are almost always naming failures, not modelling ones.

Salesforce Data 360
Members are keyed by entity and name, and written that way. A short name still resolves as a courtesy when exactly one member carries it.
Snowflake
Members must be entity-qualified. A bare name that matches more than one comes back with the qualified candidates listed.
Databricks
Members are the metric view’s own dimension and measure names, taken from the definition the view was created with.
Palantir AIP
Members are keyed by object type and name. A measure is read only from a Function whose declared output is numeric, because an ontology publishes typed properties and declares no measures of its own.

The keying is by entity and name together rather than by name alone, and that came out of running against a live account rather than reading documentation. Five semantic views there each defined the same dimension name on two entities, one at document grain and one at section grain. A dictionary keyed on the name alone merged the twins into a single entry belonging to neither of them. Nothing errored, and every answer built on it would have been wrong in a way no one could see.

One question, one warehouse

A question naming fields from two different warehouses is a reasonable thing to want and an unreasonable thing to run. Two contracts belong to two engines, and there is no third engine underneath them both.

The layer does two jobs with the same objects and keeps them apart. For inventory, the same schema and table seen in several warehouses becomes one logical object remembering every source it came from, which is what makes a comparison report possible at all. For execution, a contract belongs to the warehouse it was learned from, and the query is routed there. A question mixing two contracts comes back naming what was mixed, and is never quietly resolved to whichever contract happened to be listed first.

Inside a single warehouse the same shape appears one level down. A model that declares no relationship between two entities cannot answer a question spanning them, and the warehouse says so in its own words. That is a modelling answer worth reading: it is telling you the join everybody assumed exists has never been declared.

Comparing across warehouses and computing inside one are different jobs. Keeping them separate is what lets the same table appear once in a governance report and still be computed in exactly one engine.

Why the identifiers come from the contract

When a question becomes a statement, the strings placed into that statement are the contract’s own copies. What the caller typed is used to look a member up. What reaches the SQL is what the sync stored.

It is a small distinction and it settles three things at once.

  • A name absent from the contract has no route to a warehouse at all. A governed measure has exactly one way of being computed through this product.
  • Identifiers are charset-bounded when they are learned and checked again when the statement is built, so a stored contract that has been tampered with fails while the statement is being assembled rather than while it runs.
  • Results are row-capped, which keeps a governed question a question rather than an export with extra steps.

The property is architectural, not instructional. A model can be told to use only approved names, and a sufficiently strange conversation will eventually talk it out of that. A code path that was never written has nothing to be talked out of.

Reading the answer when a name is off contract

A question the contract cannot answer comes back naming the problem, and that answer is the more useful half of the feature. Three shapes cover nearly everything you will meet.

Off contract
The name is absent from the stored contract. Either it was never in the model, or the model gained it after your last sync. Sync and ask again before concluding anything.
Ambiguous
The short name matches members on more than one entity. The qualified candidates arrive with the message.
Entities not related
The warehouse itself is reporting that the two entities carry no declared relationship. This one belongs with whoever owns the model rather than with whoever asked the question.

Those messages arrive from the warehouse verbatim rather than smoothed into a status code. A compiler describing its own answer is almost always more accurate than a paraphrase of it, and the paraphrase is what sends somebody off to debug a host that was fine the whole time.

Where to look in your own site

  • The semantic console shows what synced, per warehouse, with counts and parity chips, and renders the contract each backend learned.
  • A contract is re-learned on every sync. The fastest answer to a missing member is usually one more sync.
  • Every governed query is recorded, the answered ones and the turned-away ones alike, with the row count kept and no returned cell written down.

That last one repays a second look. Keeping the shape of a result and discarding its contents is what makes an audit trail safe to keep for years, and years is the only useful length for an audit trail.