Our engine never sees your warehouse password
datahashi

BlogModeling & metrics

Dimensions that do not explode joins

How to model dimensions so metric grain stays correct and joins do not multiply rows.

The datahashi team3 min read

A metric looks correct until someone adds one more dimension and the total doubles. That is rarely a warehouse bug. It is usually a grain problem: a join that was many-to-one became many-to-many, and every aggregate after that inherited the fan-out.

datahashi treats dimensions as part of the metric contract, not as free-form columns you attach late. You declare what can be sliced, publish an immutable YAML version, and serve the same definition to humans and agents through REST, the console, or MCP. The point is not clever SQL. The point is keeping grain honest.

Start from the fact grain

Before you add a dimension, write down the grain of the fact table in plain language: one row per order line, one row per daily account balance, one row per session. Every dimension you attach must map cleanly to that grain without multiplying rows.

If the dimension table has multiple rows per fact key (two addresses for one customer, three product tags for one SKU), joining it directly into the metric path will explode counts and sums. The fix is not “join carefully in the dashboard.” The fix is to decide how the dimension should resolve at the fact grain: pick a primary record, aggregate attributes first, or expose a separate metric at the attribute grain.

Prefer keys that preserve one-to-many direction

Safe dimension modeling keeps the relationship direction obvious. Facts many, dimension one. When you need attributes that live at a finer grain than the fact, resolve them upstream in the warehouse into a table that is unique on the join key the metric will use.

Do not ask the semantic layer to invent that uniqueness at query time. datahashi is a thin layer: YAML definitions, immutable publish, and the same request shape across REST, console, and MCP tools (list_catalog, describe_metric, run_semantic_query). It does not turn raw tables into a free join playground for agents, and it does not accept SQL in or out through MCP.

Name dimensions by meaning, not by table alias

Call the dimension what analysts mean: customer_segment, billing_region, product_family. Avoid exposing every column that happens to exist on a joined table. Extra columns invite accidental filters that change grain or leak attributes that were never reviewed.

When a dimension is optional for a metric, say so in the definition. When a dimension is incompatible with a metric’s grain, keep it out of the allowed set. Catalog clarity beats “join everything and hope.”

Test the total before you trust the slice

A practical check: compute the metric with no dimensions, then with each dimension alone, then with the combinations you plan to ship. If the undimensioned total and the dimensioned totals disagree in ways that are not explained by nulls or incomplete coverage, the join is wrong.

Metering in datahashi helps you see which metrics and dimensions are actually used. Rollups have shipped. A p99 latency target has not. Correct grain comes first.

Agents need the same discipline

Agents that can invent joins will invent explosions. Governed metrics remove that temptation. An agent lists the catalog, describes a metric, and runs a semantic query against published dimensions. Humans get the same path. No hallucinated SQL, no silent row multiplication.

Model dimensions so the fact grain stays true. If a join would multiply rows, fix the model before you publish the metric. That is how slices stay believable.

Product notes

An occasional email when we ship something or write something worth reading.