Skip to content

The Ultimate Guide to Data Modeling

June 2026

A practical guide to data modeling: grain, fact and dimension design, history, marts, and how to turn raw operational data into durable analytics tables people can trust.

Start Here: What Data Modeling Actually Is

Data modeling is the work of deciding how data should be represented, named, related, and queried so people can answer real questions reliably. It is not just drawing entity diagrams or copying a source schema into a warehouse. It is the design step that turns raw operational records into analytic truth.

A source system usually stores data for application behavior: write speed, transactional integrity, and local business logic. An analytics model serves a different job. It needs stable grain, clear keys, readable measures, and semantics that survive dashboards, finance reviews, and executive questions. That difference is why the source schema and the warehouse model are often related but not identical.

Use a simple example. An application may store orders, order_items, customers, and refunds in separate normalized tables because the product needs safe writes. An analyst asking for daily revenue, refunded revenue, and repeat purchase rate does not want to rebuild all of that logic from scratch every time. The data model should carry that semantic burden once.

  • A model defines grain: what one row means and what it never means.
  • A model defines relationships: which keys join cleanly and which joins should not happen.
  • A model defines time: event time, snapshot time, validity windows, and the timezone contract all matter.
  • A model defines business language: revenue, churn, active customer, and trial conversion need stable meanings.

The First Three Decisions: Grain, Business Process, and History

Before naming tables or choosing dbt materializations, answer three design questions. If these are fuzzy, the model will drift no matter how clean the SQL looks.

  • What is the grain? One row per order, per invoice line, per customer, per subscription-day, or per event? The row meaning must be explicit.
  • Which business process is this model serving? Checkout, billing, subscription lifecycle, product usage, support activity, or something else? Good models map to one process cleanly.
  • Do consumers need current state or history? A current customer table, a changing customer profile history, and an event stream are three different analytical contracts.

These questions sound basic, but they prevent the most expensive modeling mistakes. Teams often think the problem is "how do we shape the warehouse?" when the real problem is "what exactly should one row represent for this question?"

A quick example: active_subscriptions could mean one row per current subscription, one row per subscription per day, or one row per subscription change event. Those are all valid models. They are not interchangeable.

Operational Schemas and Analytics Models Solve Different Jobs

Many modeling mistakes come from treating the source schema as if it were already the analytics answer. Operational schemas are often normalized, mutation-heavy, and optimized for application rules. Analytics models are optimized for explanation, consistency, and repeatable querying.

ConcernOperational schemaAnalytics modelPrimary goalRun the product correctlyAnswer business questions reliablyShapeOften normalized and mutation-friendlyOften denormalized or dimensionally organized for analysisTime behaviorCurrent state matters mostHistory and comparison often matter just as much as current stateNulls and edge casesHandled locally in product logicNeed stable downstream rules and documentationConsumerApplication code and servicesAnalysts, BI tools, ML systems, finance, and operations

This is why copying tables is not the same as modeling. A warehouse full of raw application tables can be useful, but it is not yet a data model people can trust under pressure.

Facts, Dimensions, Event Tables, and the Mixed-Grain Trap

Most analytics teams eventually use a small set of model types again and again. The important thing is not memorizing vocabulary. It is understanding what job each model shape actually performs.

  • Fact tables capture measurable business activity at a clear grain, such as one row per order, invoice line, shipment, or app session.
  • Dimension tables describe entities such as customers, products, plans, sales reps, or geography.
  • Event tables capture sequences of things that happened, often at high volume, such as clicks, page views, or feature usage.
  • Periodic snapshot facts capture the state of something at a regular interval, such as one row per subscription per day.
  • History dimensions preserve changing entity attributes when users need to know what was true at a point in time.

The common failure is not choosing the wrong buzzword. It is mixing multiple grains into one table and then pretending aggregation will still work. If one row sometimes means an order and sometimes means an order item, revenue numbers will eventually double-count. If a customer attribute is repeated across a fact with inconsistent history, segmentation drifts quietly.

Model: fct_orders Grain: one row per order_id Measures: order_total, discount_total, tax_total Foreign keys: customer_id, plan_id, order_date Not for: line-item analysis, subscription daily state, or product event counts

That little block is more useful than a lot of warehouse SQL. When a team can state the model contract that clearly, downstream confusion drops fast.

Star Schemas, Wide Marts, Event Models, and When Each Wins

There is no single best model shape for every question. Strong teams choose patterns deliberately based on workload, consumer skill, and semantic stability.

PatternBest forStrengthMain riskStar schemaReusable BI and dimensional analysisClear grain and consistent slicing through dimensionsCan feel heavyweight if the team never agrees on dimensionsWide martOne business domain or one heavily used dashboard familyEasy for consumers to query quicklyCan become a dumping ground for mixed semanticsEvent modelBehavioral analysis, funneling, sequence questionsPreserves time and sequence cleanlyNot a substitute for current-state dimensional reportingPeriodic snapshot factState-over-time metrics such as daily MRR or inventoryMakes semi-additive behavior explicit across timeExplodes in size if the snapshot rule is vague or too granularHistory or SCD dimensionAttribute history for entities such as customers or accountsPreserves what was true when an event happenedCan create duplicate joins or user confusion if validity windows are not modeled clearly

A practical default works well here: use star schemas when multiple business questions need the same conformed dimensions, use wide marts when one domain team needs a fast stable serving layer, use event models when sequence matters, use periodic snapshot facts when measures need to be read across time, and use history dimensions when entity attributes must be historically correct.

Current State, History, and Slowly Changing Dimensions

One of the most important modeling choices is whether the consumer needs today's truth, yesterday's truth, or both. This is where teams either build a durable model or quietly bake in confusion.

Take a customer record. If the sales owner changes from Alice to Ben, a current-state dimension should usually show Ben. But a historical pipeline-performance report for last quarter may still need Alice. Those are different questions and often require different model behavior.

  • Current-state dimensions are best when users mostly need the latest known attributes.
  • Type 1 style overwrite behavior is simple, but it erases history by design.
  • Type 2 style history preserves attribute changes with validity windows so analysts can ask "what was true at the time?"
  • Snapshot tables are useful when the real business question is the state of an entity across time, not just the latest row.
  • Event history is often better than dimension history when the source system already emits meaningful state transitions. NeedBest fitExampleLatest account ownerCurrent-state dimensiondim_customer with one row per customerWho owned the account when the deal closed?Historical dimension or valid-from / valid-to modeldim_customer_historyHow did subscription state change day by day?Snapshot or event-based state tablefct_subscription_daily or lifecycle event model

The useful discipline is to document which truth a model exposes. "One row per customer" is only half a contract. "One row per customer using the latest known attributes as of the most recent successful load" is a real contract.

Transaction Facts, Periodic Snapshots, and Measure Behavior

Advanced modeling gets much easier once you separate row grain from measure behavior. Two tables can both be correct and still answer different revenue questions because the measures roll up differently.

Model typeTypical grainMeasure behaviorExampleTransaction factOne row per business eventOften additive across time and dimensionsfct_orders, fct_invoice_linesPeriodic snapshot factOne row per entity per time bucketUsually semi-additive: sum across segments, but not blindly across timefct_subscription_dailyAccumulating snapshotOne row per lifecycle recordTracks milestone progression rather than clean additive measuresfct_deals_pipeline or order-fulfilment lifecycle

  • Additive measures can usually be summed across all relevant dimensions, such as invoice line amount or units sold.
  • Semi-additive measures can usually be summed across some dimensions but not safely across time, such as end-of-day MRR or account balance.
  • Non-additive measures such as ratios or percentages usually need to be recomputed from lower-level components rather than summed.

This matters because a daily MRR snapshot and an order fact are both about revenue, but they are not the same revenue story. Orders can answer booked demand. Invoice lines can answer billed and refunded amounts. Subscription daily snapshots can answer active recurring value across time. An ultimate guide should make that separation explicit because it is one of the fastest ways to stop measure drift.

A Worked Example: Modeling a SaaS Billing and Product Stack

Use one realistic example to make the choices concrete. Imagine a SaaS company with source tables for customers, orders, subscriptions, invoice_lines, and product_events. The business wants answers to five questions:

  • How much booked revenue did we generate each day?
  • What is monthly recurring revenue and how did it change over time?
  • Which customers refunded after purchase?
  • Which plans convert from trial to paid most effectively?
  • How does product usage differ between free and paid customers?

That question set already tells you a single table is the wrong answer. The grains are different.

ModelGrainWhy it existsdim_customerOne row per customerStable entity attributes for segmentation and joinsdim_planOne row per billing planConformed plan metadata for revenue and usage analysisfct_ordersOne row per orderBooked order revenue and order-level conversion analysisfct_invoice_linesOne row per invoice lineDetailed billing and refund attributionfct_subscription_dailyOne row per subscription per dayMRR, expansion, contraction, and churn analysis across timefct_product_eventsOne row per product eventBehavioral usage analysis and funneling

The important move is not just creating many models. It is resisting the temptation to force order facts, invoice details, daily subscription state, and product events into one giant wide table. That giant table feels convenient for a week and then becomes impossible to reason about.

Once the grains are separated, gold marts become easier to state cleanly: mart_daily_bookings can build from fct_orders, mart_billing_and_refunds can build from fct_invoice_lines, mart_subscription_mrr can build from fct_subscription_daily, and mart_plan_retention can combine fct_subscription_daily with dim_plan. That is a much stronger semantic contract than one catch-all "revenue" mart pretending all revenue facts are interchangeable.

Keys, Time, Late Data, and Conformed Definitions Matter More Than Fancy SQL

Strong data models often look ordinary in SQL. The hard part is not writing complex syntax. It is choosing keys, time rules, and shared definitions that hold under stress.

  • Natural versus surrogate keys: some models should preserve the business key directly, while others benefit from warehouse surrogate keys for history handling and joins.
  • Event time, effective time, and loaded time: these are different timestamps and strong models usually preserve all three when they answer different questions.
  • Timezone normalization: if one model uses local timestamps and another uses UTC, daily metrics become arguments instead of answers.
  • Date and calendar logic: fiscal periods, week starts, and reporting cutoffs need one stable definition.
  • Conformed dimensions: if customer, plan, or geography is defined differently across marts, the business does not actually have one analytics model.
  • Late-arriving facts and backfills: the model needs a rule for how old corrections land and which timestamp drives downstream reporting.
  • Identity stitching and bridge logic: when one business concept spans accounts, workspaces, users, or households, the bridge itself becomes part of the model contract.
  • Null semantics: there is a real difference between unknown, missing, not applicable, and not yet happened.

For example, if revenue models join on customer_id but product-usage marts use a different identity derived from workspace membership, "revenue by customer health" becomes unreliable before anyone notices. The fix is usually conceptual, not computational: make the identity contract explicit and reuse it across models.

The same goes for time. A late refund might arrive today for an order from last week. Finance may want that refund attributed to last week's effective period, while operations may want to know it was processed today. Strong models preserve enough timestamp context that both questions can be answered honestly instead of forcing one hidden reporting rule on everyone.

How Bronze, Silver, Gold, and dbt Support Modeling Discipline

Data modeling gets easier when the warehouse has clear layer boundaries. In Skippr's documented approach, bronze holds raw landed data, silver holds cleaned and typed staging models, and gold holds business-ready models. That structure matters because modeling discipline usually breaks when raw recovery, cleanup logic, and business semantics all live in the same place.

  • Bronze keeps the source contract recoverable and inspectable.
  • Silver is where naming, typing, conformed keys, and reusable business building blocks should get clarified.
  • Gold is where business-facing marts should expose stable measures and dimensions to consumers.
  • dbt gives those layers a visible DAG, tests, and documentation rather than hiding them in ad hoc warehouse SQL.

This does not mean medallion is the whole story. The model quality still comes from grain decisions, history choices, and semantic clarity. The layer model simply gives those decisions a more legible place to live. A concrete example is a late refund: bronze keeps the raw source row, silver preserves event time and business-effective time, and gold decides whether the mart is answering operational processing questions or finance-period questions.

Common Failure Patterns in Real Data Models

Most bad models are bad in predictable ways. Naming those patterns early helps teams avoid months of downstream cleanup.

  • Mixed grain: one table quietly combines order, order-item, and customer-level semantics.
  • Source-schema worship: the warehouse mirrors the application structure even when that structure is painful for analytics.
  • History by accident: some models overwrite attributes while others preserve them, with no documented rule.
  • Wide tables with unclear ownership: every new request adds five more columns until nobody can explain what belongs there.
  • Metric logic in dashboards instead of models: the warehouse looks clean, but the real business definitions live in BI-layer SQL.
  • Inconsistent keys and time rules: models join and aggregate, but nobody can explain why counts differ by report.
  • Gold models without reusable silver building blocks: every mart re-implements the same cleanup and semantics differently.

The pattern underneath these failures is simple: the team built tables before it built contracts. Good data modeling reverses that order.

How Skippr Fits a Practical Data Modeling Workflow

Skippr fits best when the team wants to move from raw source data to a reviewable analytics modeling surface quickly, while keeping the output standard and the data boundary explicit.

  • Clear starting point: the documented pipeline follows discover, sync raw data, model, and validate, so the team starts from a visible warehouse path rather than a blank modeling repo.
  • Standard artifacts: the generated project includes normal dbt files such as dbt_project.yml, profiles.yml, source definitions, staging models, and package config.
  • Documented division of responsibility: schema discovery and destination mapping, type reconciliation and evolution handling, incremental checkpoints and replay behavior, and CDC reconciliation stay deterministic, while dbt model and test scaffolding, naming and staging-model structure, and descriptive metadata and model documentation are the AI-assisted side.
  • Explicit data boundary: row-level data only ever exists in two places, the machine running skippr and your destination, while metadata is the default model input and the cloud path handles authentication and control-plane services rather than row-level source or warehouse data.

That is a strong fit for teams that do not want to spend the first phase of modeling work wiring bronze landing and scaffolding by hand. What Skippr does not remove is the need to choose grain, define business semantics, or decide when a gold model is truly ready for consumers. Those are the core human decisions in data modeling, and this guide treats them that way on purpose.

For related reading, pair this guide with The Ultimate Guide to Data Migration, The Ultimate Guide to Change Data Capture, The Medallion Architecture Guide, What Is dbt?, and the Core Concepts docs.

Your Practical Data Modeling Checklist

If you want one section to keep open while designing models, use this one.

  • State the business question before naming the table.
  • Write down the grain in one sentence before adding a column.
  • Separate current state from history intentionally.
  • Choose whether the model is a fact, dimension, event table, history table, or a deliberately scoped mart.
  • Define keys, time rules, and null semantics before consumers build reports on top.
  • Keep reusable cleanup and conformance logic in silver instead of duplicating it in every mart.
  • Use gold tables to present stable business contracts, not to hide unresolved ambiguity.
  • Treat tests, docs, and model descriptions as part of the model itself.
  • Review whether a new question needs a new grain instead of one more column on an old table.
  • Keep generated scaffolding, but make the semantics explicitly yours before the model becomes authoritative.

That sequence reflects how strong modeling actually happens. First define meaning. Then choose structure. Then build the SQL. The tables get better when the decisions do.