Data Modeling for OLTP and OLAP Systems

Two tables can contain the same customer identifier and still describe different things: the customer now, the customer at purchase time, or what the company believed about that customer last month. A model makes those distinctions explicit. SQL can join the tables either way; the model determines which join answers the intended question.

What the model commits to

A data model describes entities, relationships, constraints, and the meaning of stored values. Declaring what one row represents is an early decision: an order, a customerโ€™s current state, and a customer observed on a particular day have different grains. These tables may share attributes but retain different information. Before joining them, check the intended row identity, time basis, and matching relationship.

Operational and analytical workloads often need different models. A checkout database uses keys, constraints, and transaction logic to record purchases consistently; duplicate requests still require explicit handling. An analytical model makes recurring queries such as “revenue by country by month” easier to express; speed still depends on implementation and workload. The same order lives in both, in different shapes, and a large part of data engineering is carrying it from one shape to the other without losing meaning.

The central decisions are what a row represents, which attributes identify it, which facts depend on which keys, and which changes must remain observable. Operational and analytical models arrange these commitments around different workloads. A good transformation between them preserves the meanings required by consumers, even when it changes the physical shape of the data.

Separate conceptual, logical, and physical decisions

A conceptual model identifies business entities and relationships: customers place orders, payments settle obligations, and subscriptions change state. It establishes the language and scope. Even familiar words need agreement: a customer might be a person, a household, a billing account, or a legal organization. Those are different entities, not four interchangeable names for one key.

A logical model turns that meaning into attributes, relationships, keys, constraints, and history rules. It says whether one payment can cover several orders and whether a customer address means the current address or the address used for a particular shipment. A physical design chooses how the selected engine stores and accesses that model: data types, indexes, partitions, clustering, compression, and materialized results.

These levels inform each other, but an implementation choice should not quietly redefine a business concept. Partitioning an order table by arrival date does not make arrival date the order date. Replacing a join with a precomputed table does not authorize changing its grain. When a performance optimization changes meaning, it creates a different model and needs an explicit consumer agreement.

Entities, relationships, keys

An entity is a thing the business talks about: a customer, a product, an order, a subscription. A relationship is how entities connect, and its cardinality, how many of one go with how many of the other, decides the table structure.

CardinalityExampleShape
One-to-onea customer has one loyalty accountsame table, or a second table sharing the key
One-to-manya customer places many ordersthe “many” side carries the “one” side’s key (orders.customer_id)
Many-to-manyan order contains many products; a product is in many ordersa bridge table in the middle (order_items) with a row per order line; repeated products require a line identifier

A candidate key uniquely identifies a row with no unnecessary columns; the chosen one is the primary key (PK). A foreign key (FK) refers to an allowed key in another table. A natural key comes from the business domain; a surrogate key is generated for identification. Email is only a candidate key if the business guarantees uniqueness and defines how changes are handled. A surrogate key helps decouple identity from mutable attributes, but does not itself prevent duplicate business entities.

Keep identifiers stable within a documented namespace. If two systems both issue customer 123, preserve the source-system identifier as well. Text is appropriate for source codes with leading zeros or letters; integer warehouse surrogate keys and native UUID types are also valid. Choose the type for identity preservation and the database, not according to whether arithmetic will be performed.

Cardinality includes optionality. โ€œA customer has at most one loyalty accountโ€ permits zero; โ€œexactly oneโ€ does not. A nullable foreign key can represent an optional relationship, but a separate business rule may be needed to require participation. The maximum relationship count and the minimum required count should both be documented.

A constraint in a model and an enforced database constraint are different things. Some analytical systems treat declared keys as informational; a data-quality check can detect violations without preventing an intervening write. In PostgreSQL, a primary key combines uniqueness with non-null values, while unique and foreign-key constraints have their own null and matching rules. Check the engine behavior rather than interpreting every key annotation as equivalent protection. PostgreSQL documents these constraint semantics.

Grain defines what can be counted and joined

The grain of a table is the exact statement of what one row represents. Not roughly: exactly, including the time dimension.

TableGrainA row isโ€ฆ
ordersone row per orderorder A17, placed 2026-03-14, total 59.97
order_itemsone row per order per product lineorder A17, product P3, quantity 2
daily_revenueone row per day per currency2026-03-14, USD, 1,204,331.50
customer_daily_snapshotone row per customer per daycustomer C9 as they were on 2026-03-14
subscription_eventsone row per change to a subscriptionsubscription S4 was paused at 2026-03-14T09:12Z

Every column must have a defined meaning at the tableโ€™s grain. On an order, customer_country could mean country at purchase, current customer country, or shipping destination; one customer does not make those meanings interchangeable. Likewise, customer lifetime spend can be a documented as-of-order feature or a current value requiring refresh. Specify the time basis and whether a repeated customer-level measure may be aggregated across orders.

A join can repeat a measure at the wrong grain. An order worth 60 with three lines becomes three joined rows carrying 60 each; summing that order total gives 180. Sum line amounts, or aggregate each side to a shared grain before joining. The same failure hides in less obvious places: a daily table that also contains a “month to date” row, an events table where some rows are individual events and others are hourly rollups, a customer table where test accounts have one row per environment. The defense is procedural: state the grain in the table’s documentation, in one sentence, and check every join against it. Reviewers should ask for it; if the author cannot say it in one sentence, the table is not finished.

Keep the detail needed for supported questions and reprocessing, subject to retention, access, and cost requirements. A daily total alone cannot reconstruct its individual orders. Some measures can be aggregated from lines to orders, but shipping fees, taxes, and discounts need explicit allocation rules.

Modeling for operations: normalization

An operational database supports online transaction processing (OLTP): recording orders, updating stock, and handling concurrent changes. Normalization reduces redundant facts and the inconsistencies they can cause. Transactions and constraints are still needed; normalization alone does not prevent duplicate requests or guarantee correct writes.

The problem it solves

-- one flat table: one row per order line, with repeated customer and product attributes
order_id | customer_email    | customer_city | product_code | product_name | unit_price | qty
A17      | kim@example.com   | Seoul         | P3           | Coffee 1kg   | 19.99      | 2
A17      | kim@example.com   | Seoul         | P7           | Filter x100  | 4.50       | 1
A18      | kim@example.com   | Seoul         | P3           | Coffee 1kg   | 19.99      | 1

Assume customer_city means current city and product_name means current catalog name. An update anomaly occurs when a city change requires several rows to agree and only some are updated. An insertion anomaly occurs when this design has no place for a product before its first order. A deletion anomaly occurs when removing the last order line also removes the only recorded product description. These problems arise from combining facts with different dependencies, even when one controlled loader performs every write.

A functional dependency, written X โ†’ Y, means that equal X values require equal Y values in every permitted state of the relation. It is a business constraint, not merely a pattern in today’s sample. For current product descriptions, product_id may determine product_name; it does not determine the negotiated price of every historical sale. Time and meaning decide which dependencies actually hold.

The normal forms, in plain language

  • First normal form (1NF): every cell holds one value. No “P3, P7” in a products column, no repeating groups (product1, product2, product3). Lists become rows in another table.
  • Second normal form (2NF): after 1NF, each non-prime attribute must depend on each candidate key in full, rather than on a proper subset. A prime attribute belongs to at least one candidate key. If each product appears at most once per order and the key is (order_id, product_code), product_name depends only on product_code, so it moves to a products table.
  • Third normal form (3NF): remove transitive dependencies of non-key attributes on keys in this example: order_id โ†’ customer_id โ†’ current_city. Put current city in customers. If the column instead means shipping city for this order, it is an order attribute and belongs with the order.

For a simple design with one familiar business key, โ€œthe key, the whole key, and nothing but the keyโ€ is a useful mnemonic. It is not a full definition when candidate keys overlap. For a nontrivial dependency X โ†’ A, A is not already part of X. Third normal form requires X to be a superkey or A to be prime. Boyceโ€“Codd normal form requires X to be a superkey for every nontrivial dependency. Berkeley’s database-design notes explain functional dependencies, decomposition, and these distinctions. For the order example, the dependencies lead to the following tables:

customers (customer_id PK, email UNIQUE, city, ...)
products  (product_id  PK, product_code UNIQUE, product_name, current_price, ...)
orders    (order_id    PK, customer_id FK, ordered_at, status, ...)
order_items (order_id FK, line_no, product_id FK, quantity, unit_price_at_order, PK (order_id, line_no))

These four tables separate current customer and product attributes from order facts. The pair (order_id, line_no) identifies a line even when the same product appears twice. unit_price_at_order is a fact about that line, so it stays fixed when the current catalog price changes. This is a distinction in meaning, not an exception invented to bypass normalization. The example assumes email and product_code are unique in their stated scopes; a business that permits shared email addresses must remove that uniqueness assumption.

Normalized models can support analysis, but repeatedly assembling the same business view creates work and opportunities for join mistakes. Performance depends on data volume, indexes, storage, and the query plan, not simply the number of joins. An analytical model makes recurring questions easier to express.

Decomposition should preserve information: joining the decomposed relations should reconstruct the intended relation without inventing combinations. This is the lossless-join requirement. Dependency preservation asks whether the original constraints can be enforced through the decomposed relations without reconstructing the whole join. Neither property follows simply from increasing the number of tables.

The operational reason to care is visible in a checkout transaction. The model identifies orders and lines, while transaction logic coordinates updates that must succeed together. Uniqueness does not establish that stock was sufficient, and normalization does not prevent a duplicated business request with a newly generated ID. Schema constraints, transaction isolation, and application invariants contribute different parts of correctness.

Modeling for analysis: facts and dimensions

An online analytical processing (OLAP) model supports queries that filter, compare, and aggregate many records. Dimensional modeling organizes those queries around measurements and their descriptive context. Denormalizing selected attributes can simplify queries, with trade-offs in storage, refresh cost, and consistency.

The dominant pattern is the star schema: a central fact table of measurements at a declared grain, surrounded by dimension tables that describe the who, what, where, when of each measurement.

                 dim_customer                dim_product
                 (customer_key, name,        (product_key, name,
                  country, segment, โ€ฆ)        category, brand, โ€ฆ)
                          \                      /
                           \                    /
                         fact_order_items  (grain: one row per order line)
                         order_line_key, order_id, date_key, customer_key, product_key,
                         quantity, unit_price, line_amount, discount_amount, currency
                           /                    \
                          /                      \
                 dim_date                         dim_store / dim_channel
                 (date_key, day, week, month,     (channel_key, channel_name, โ€ฆ)
                  quarter, is_holiday, โ€ฆ)
  • Measures need aggregation rules. Sales amounts can be additive across disjoint transactions in a common currency and accounting basis; account balances are not additive across dates; percentages and unit prices generally cannot be summed. Compute an overall ratio from its numerator and denominator. Distinct-customer counts from overlapping groups cannot be summed, and summing a balance over dates answers a different question from the balance at a date. Fact tables may also record events without a numeric measure.
  • Dimensions are descriptive context: names, categories, countries, dates. Their size depends on the entities and retained history; a date dimension and a versioned customer dimension can have very different row counts. They are where the filters and group-bys come from.
  • “Revenue by country by month” becomes: fact joined to dim_customer for country and dim_date for month, sum(line_amount), group by. Group by currency too unless the amounts share one currency. An appropriate date filter can enable partition pruning when the storage and engine support it.

Kimballโ€™s dimensional modeling techniques distinguish additive, semi-additive, and non-additive measures.

A transaction fact records one occurrence, such as an order line. A periodic snapshot fact records a regular observation, such as each accountโ€™s closing daily balance. An accumulating snapshot fact follows one process instance, updating milestones such as accepted, shipped, and delivered dates. Its latest row cannot by itself reproduce what was known yesterday; that requires retained versions or events.

Conformed dimensions give different fact tables compatible attribute definitions and values. Reports using customer segment must agree on its meaning and historical interpretation; compatible copies can serve separate systems. Before comparing orders and support tickets, aggregate each fact separately to the chosen shared reporting grain. Joining their detail rows by customer can multiply both measures.

A many-to-many relationship may require a bridge and an allocation rule. Suppose an order worth 100 is credited to two campaigns. Counting 100 for each answers โ€œvalue associated with each campaign,โ€ whose totals overlap. Allocating 60 and 40 answers an attributed-value question. Both are possible models, but the weights, time basis, and reporting interpretation must be stated; a bridge table alone does not make the amounts additive.

Denormalization shifts consistency work to the writer or refresh process. It can make recurring questions easier to express, but an interrupted refresh can still leave copied descriptions inconsistent. A single loader reduces coordination problems; it does not abolish update anomalies. A star, snowflaked dimensions, normalized core, or purpose-built wide table may each be suitable depending on ownership, history, reuse, and query patterns.

Wide tables and when to use them

A one big table (OBT) prejoins dimension attributes onto facts. It can simplify a stable consumer workload, and columnar compression can reduce repeated-value storage. When attributes mean current values, updates may touch many rows; when they mean values at event time, preserving the old value may be intentional. Choose stars or wide tables according to workload, history semantics, and maintenance cost rather than a universal core-versus-mart rule.

Wide and long describe a reshape, not a star schema

Wide and long describe where repeated measurements appear. A population table can have one row per country with columns population_2023 and population_2024. Its long representation has one row per country-year and one population column. The reshape changes the row grain. It is a separate decision from whether order facts carry copied customer attributes in a wide analytical table.

If country B has an unknown population for 2024, the model must decide whether to retain an explicit unknown value or omit the observation. Neither means zero. Returning to the wide representation requires at most one value per country-year, or an aggregation policy that intentionally changes information. Long format does not automatically mean less storage or faster queries; those depend on the representation and workload.

Events and state

Two useful representations are current state and recorded events. Specify which one a table contains before counting changes.

State tableEvent table
A row isan entity as it is nowsomething that happened, once, at a time
Rows areupdated in placenormally appended; corrections need an explicit policy
Examplesubscriptions: S4 is pausedsubscription_events: S4 started 01-10, paused 03-14
Answershow many are paused right now?how many paused in March? how long do pauses last? what was S4’s status on 02-01?
Cannot answerearlier values that were overwritten and not retainedcurrent state without interpretation or a maintained projection

Operational models often maintain current state for actions such as checkout, while also retaining transaction or event records. A current-state table alone cannot answer which customers became inactive last month after those values have been overwritten. Choose retained events, repeated observations, or version history to support the particular change question; not every analytical question requires history.

Reconstructing state from events requires an initial state, complete relevant events, ordering, and event semantics. Keeping only the latest row works when each event contains the full resulting state; a balance built from deposits and withdrawals requires accumulation. Comparing snapshots yields observed differences, not every event between observations. A change that is reversed before the next snapshot disappears from that comparison. Log-based CDC and periodic snapshot comparison therefore provide different evidence. Keep either or both according to the questions and available history.

Keeping history

When a source overwrites an attribute without retaining history, the platform needs a deliberate capture policy to support historical questions. Slowly changing dimension (SCD) techniques describe how dimension attributes change. Choose the required interpretation before deciding how to store versions.

Type 1: overwrite

Replace the old dimension value. Historical orders joined to that current value are grouped by the customerโ€™s current city. That is appropriate for a report explicitly asking about current customer location or for an agreed correction, but not for location at order time.

Type 2: a new row per version

dim_customer (type 2)
customer_key | customer_id | city  | segment | valid_from  | valid_to    | is_current
1001         | C9          | Seoul | basic   | 2024-01-10  | 2025-07-01  | false
2417         | C9          | Seoul | premium | 2025-07-01  | 2026-03-14  | false
3902         | C9          | Busan | premium | 2026-03-14  | 9999-12-31  | true

The three rows describe customer C9 during three recorded validity intervals. An order on March 14, 2026 uses version key 3902; an order on June 1, 2025 uses 1001. A fact can store the resolved version key so later joins do not search by natural key alone. If lookup begins with customer_id, it must also use the relevant business time to select a version. Counting transitions in this history describes recorded changes at its capture resolution, not necessarily every real-world change. Returning from state A to B and then to A creates three intervals, even though only two distinct attribute combinations appear. Deduplicating by attributes would destroy that sequence.

The table uses half-open date intervals: valid_from โ‰ค event_date < valid_to. Adjacent ranges can share an endpoint without both matching it. The far-future end is a storage convention for the currently open version, not a claim that its value will remain true forever. Timestamp histories also require a timezone and sufficient precision. Half-open bounds do not prevent overlaps, gaps, reversed ranges, or an entirely missing entity history; those are separate integrity rules.

Late facts use the version effective when the fact happened, according to the model’s time interpretation, rather than the version current when it arrived. A late correction to a dimension can split an interval and require affected facts or derived reports to be reassigned. If the requirement is to preserve what an earlier report knew, a revised business-validity interval alone is insufficient; system-version history is a separate concern.

A join check must reason about each fact key. One unmatched fact and one fact with two matching versions can leave the total row count unchanged. Require the intended match cardinality for each original fact, then inspect unknown matches separately. Uniqueness of the version key protects that join path; uniqueness of customer_id is not expected across a Type 2 history.

Type 3: keep the previous value in a column

Store selected previous or alternate values in additional columns, such as segment and previous_segment. This retains limited history without another row. A change timestamp is also needed to answer when the change happened; the two value columns alone cannot identify who changed recently.

Snapshots: a copy per period

A periodic snapshot records selected attributes at a defined observation time. Daily snapshots cannot reveal every change within the day, and an evening snapshot is not automatically valid for a prediction made that morning. Training features must have been available by prediction time. Type 2 or snapshot joins select historical values, but do not by themselves prevent leakage from late corrections, aggregates computed from future events, or label construction.

Choosing

You needUse
Only the current value; history irrelevant or a correctionType 1
Facts attributed to the value current at the timeType 2
“What was everything like on day X”; ML features as-ofSnapshot or versioned attributes at the required resolution, with availability history for ML
What happened, when, and in what orderEvent table, and derive the rest

Decide per attribute and document the policy. Overwritten history can only be recovered if another retained source, log, snapshot, or backup contains it. Preserve the evidence needed for supported questions within the agreed retention policy; daily snapshots cannot reconstruct every intermediate event.

Time in the model

Historical questions can involve two time axes. Some models retain only one; name which axis the stored timestamps represent. A valueโ€™s business validity and its availability to a particular system can differ.

  • Valid time (business time): when the fact was true in the world. The customer moved on March 14.
  • Transaction time (system time): when the modeled system recorded a version. The move was recorded on March 20, because the customer updated their profile late.

A March 16 report may use the city then known to the platform, while a later report restates that date using a correction received on March 20. A bitemporal model tracks both business validity and system-version intervals so those interpretations can be distinguished. A single loaded_at column does not preserve overwritten system versions. Reproducing an old report also requires its query logic and other inputs. For ML, check availability in the serving or feature system, not just the source databaseโ€™s transaction time. A March 14-effective value received March 20 must not become a feature for a March 16 prediction simply because its valid-time interval covers that date.

Smaller decisions that cause large problems

  • Money. Record the currency alongside each amount, or in an explicit single-currency dataset contract; amounts are DECIMAL or integer minor units; a converted amount carries the rate and the rate’s date (amount_usd, fx_rate, fx_rate_date) so it can be reproduced.
  • Units. duration_ms, weight_g, distance_km. A column called duration will be summed in seconds by one person and milliseconds by another.
  • Unknown versus not applicable. A dimension usually has a special row (key -1, name UNKNOWN) so that facts with a missing dimension value still join; a fact-preserving join must account for every intended fact. Distinguish “we do not know the country” from “country does not apply to this kind of order” if the business does.
  • Booleans and status codes. Case comparison depends on the engine and collation. Map source spellings to agreed statuses, and decide explicitly whether paid and captured have the same business meaning. Preserve the source value.
  • Derived values. If line_amount = quantity ร— unit_price โˆ’ discount, store the inputs and compute the result in the model; storing only the result loses the ability to check it.
  • Identity across systems. A web user, app user, email subscriber, and support contact may represent the same person under different IDs; establish that link with evidence rather than assuming it. Modeling that link, explicitly, as a mapping table with history, with correction and merge/split rules rather than silently replacing historical identities, is an identity-resolution task.

The modeling process

Modeling turns business questions into definitions, structures, and checks that can be reviewed and implemented.

  1. Collect the questions. From the consumer requirements: what do people actually ask? “Revenue by country by month”, “time to second order”, “churn by cohort”. Each question names its measures, its dimensions, and its grain.
  2. List the business processes. Ordering, paying, shipping, subscribing, canceling, contacting support. Choose transaction, periodic snapshot, or other fact grains that serve the questions; one process can need several tables.
  3. List the entities that describe them. Customer, product, date, channel, location. Use a dimension where the questions require that context, and conform definitions across facts that need comparison.
  4. Declare grain and keys. One sentence per table. Choose entity keys and version keys separately; retain source identity and namespace when needed for reconciliation.
  5. Decide history per attribute. Type 1, 2, snapshot, or event.
  6. Walk the questions through the model. Can each one be answered with the tables as drawn, with the joins you expect? If a join fans out, decide which measures would repeat and whether pre-aggregation, allocation, or an explicit many-to-many model is needed.
  7. Write the data dictionary. For every table: grain, keys, history policy, owner. For every column: meaning, type, unit, nullability, example. This is the document analysts read; it is also the input to the catalog and the contract.

Worked example: the subscription commerce company

A fictional subscription commerce company. The operational schema is what the application team built; the analytical model is what the platform derives from it.

OPERATIONAL (Postgres, 3NF, current state)          ANALYTICAL (core layer, star, history kept)
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€          โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
customers (id, email, name, city, country,          dim_customer  (type 2 on country, segment, plan;
           plan, created_at, updated_at)                            type 1 on name, email)
addresses (id, customer_id, line1, city, โ€ฆ,         dim_product   (type 2 on category, price band)
           is_default)                              dim_date      (generated; 1 row per day, 20 years)
products  (id, sku, name, category, price)          dim_channel   (web / ios / android / support)
subscriptions (id, customer_id, plan, status,
           started_at, next_billing_at)             fact_order_items   grain: one row per order line
orders    (id, customer_id, status, placed_at)                          keys: date, customer(v), product(v), channel
order_items (order_id, line_no, product_id, qty,                                 measures: qty, unit_price, line_amount, discount
           unit_price)                              fact_payments      grain: one row per payment attempt (Stripe)
payments  (id, order_id, provider_ref, amount,                          measures: amount, fee; status as a dimension
           status, captured_at)                     fact_subscription_events  grain: one row per status change
                                                                        (from CDC on subscriptions)
                                                    snap_subscription_daily   grain: one row per subscription per day
                                                                        (for "active subscribers on date X", and ML)

The subscription source exposes current state. A change feed can support an observed transition history, and repeated observations can support snapshots at their stated resolution; neither recovers changes that were never captured. A business event such as โ€œsubscription canceledโ€ may need interpretation beyond a row update. Customer attributes use the history policy appropriate to each question. Payments and order lines remain separate facts because their grains differ; reconciling them requires a shared grain or an explicit allocation relationship, not an unrestricted detail join. Country is sufficient for this example’s questions, so the analytical model does not need a full address dimension. A payment-attempt fact must distinguish failed, authorized, captured, and other supported states before an amount is interpreted. Counting attempts or summing their requested amounts is not the same as counting settled payments or recognized revenue.

Anti-patterns

  • Undeclared grain. The table “has orders in it”. Nobody can join to it safely. Write the sentence.
  • Entity-attribute-value. A table of (entity_id, attribute_name, value) to avoid deciding on columns. Useful for some sparse, evolving attributes, but typing and queries need extra rules. Prefer explicit typed columns for stable, frequently queried fields.
  • Everything in a JSON column. JSON can preserve nested or evolving data. For frequently queried fields, define types and validation rules, and choose native JSON queries or extracted columns according to the engine and workload.
  • Natural keys as identity. Email as the customer key. Changing a mutable key can require coordinated key updates and complicate history; define identity independently when that is the intended business meaning.
  • Overwriting history by default. Type 1 everywhere because it was easiest. Discovered two years later when someone asks a question about the past.
  • Modeling the source instead of the business. Copying the application’s 40 tables into the warehouse as-is and calling it a model. The application’s structure exists for the application’s reasons; the analytical model exists to answer questions.
  • Premature wide tables in core. A 300-column customer table that must be rebuilt whenever anything changes. Choose the shape from query needs and refresh semantics.

Logical meaning survives a change of engine

A model can be implemented in a row-oriented database, a columnar warehouse, or tables on object storage. Engine choices change which constraints are enforced, which joins are affordable, and how a replacement result becomes visible. They do not change whether an order amount repeats on every line or whether a current-country join answers a historical-country question.

Operational models often emphasize maintaining valid state under concurrent commands. Analytical models often emphasize combining processes and comparing measurements across history. Neither workload mandates one universal schema shape: operational systems may keep denormalized read models, and analytical platforms may retain normalized integration models. The design must say which representation is authoritative, which is derived, and how changes propagate between them.

Before selecting a physical design, a reader should be able to trace a business question through row grain, entity identity, join cardinality, aggregation rules, and time interpretation. If those choices are explicit, performance and product decisions have a stable target. If they are ambiguous, a faster query can only return the ambiguity sooner.

References and scope

The commerce schemas, values, and dates are explanatory examples, not a benchmark or a reported deployment.


Discover more from Insightful Data Lab

Subscribe to get the latest posts sent to your email.

Similar Posts

Questions, corrections, or additional insights?

This site uses Akismet to reduce spam. Learn how your comment data is processed.