Chapter 17 · Physical Warehouse Design: Schemas, Tables, Constraints, Partitioning, Clustering, and Distribution

Schema/Database Organization, Naming, Ownership, Constraints, and Environment/Domain Boundaries

Organize analytical objects with explicit naming, ownership, constraints, environment boundaries, and domain contracts so physical deployment choices remain governable without changing grain or metric meaning.

Intermediate → Advanced125–150 minutesSchema/constraint governance labPython 3.13.5 · SQLite 3.46.1 · local/syntheticLast reviewed: September 2026

Learning outcomes

AtlasMart can have a perfectly designed star schema and still become unsafe if every team writes to one shared namespace, constraints are assumed but not enforced, and SQL contains production database names. Physical organization is therefore an operational contract: object location, owner, environment, and integrity behavior must be explicit before partitioning or clustering is useful.

01

Define database/schema/domain/environment boundaries separately from dimensional business grain.

02

Use names and ownership metadata to make data-product responsibility and promotion paths explicit.

03

Apply primary-key, foreign-key, NOT NULL, and CHECK constraints where the engine can enforce them, while knowing enforcement differs by platform.

04

Avoid hardcoding development, test, and production identifiers inside transformation SQL.

05

Test invalid rows deliberately so physical integrity assumptions are observable rather than decorative DDL.

Chapter 17 continuity contract

Physical design must not redefine the business model. The accepted Chapter 16 current controls remain 9 paid order-line facts, 7 paid orders, 11 units, 740 USD paid GMV, 450 USD cost-at-sale, and 290 USD gross profit. The benchmark harness expands those nine rows deterministically to 21,600 synthetic rows only to make physical-access differences observable. Benchmark-scale totals are test data, not new production controls. Grain, conformed dimension meaning, SCD history policy, metric formulas, source contracts, and Chapter 15–16 recovery semantics remain unchanged.

Execution and guarantee boundary

The mandatory lab is local, synthetic, and free. Generation-time validation ran with Python 3.13.5 and SQLite 3.46.1 in UTC. SQLite is used only as a row-store/access-path harness; it has no native cloud-warehouse clustering or distributed distribution keys. Date/month/hash file layouts are deterministic JSONL simulations used to calculate files and bytes that a dispatcher would scan. The lab never claims those numbers are Snowflake, BigQuery, Redshift, Databricks, ClickHouse, or DuckDB behavior. EXPLAIN QUERY PLAN evidence is SQLite-specific and its output format is documented as unstable across SQLite versions; the lessons interpret only the observed SCAN/SEARCH mechanism, not the literal formatting.

1. Database/schema boundaries organize deployment—not analytical grain

A database or catalog is an engine-defined top-level namespace/storage/security boundary. A schema is usually a namespace within a database/catalog. A domain boundary is an ownership/semantic boundary such as sales, inventory, or shared dimensions. An environment boundary separates dev/test/prod lifecycle and credentials. None of those is the fact grain.

“One schema per day” is not date partitioning. “One database per customer” is not a customer dimension. Physical namespaces should support ownership, access, deployment, retention, and discovery without encoding accidental business semantics.

2. A small naming contract beats ad-hoc object discovery

Object class Example Owner Mutation right Consumer expectation
Integration fact dw_core.fact_sales analytics engineering pipeline service Atomic paid order-line contract
Conformed dimension dw_core.dim_customer data stewardship + analytics eng governed dimension job Durable identity/history rules
Presentation model bi_sales.sales_daily sales analytics presentation job Certified consumer-facing semantics
Raw evidence raw_erp.sales_cdc platform/data engineering append-only ingest Replay evidence; not direct BI source

The exact namespace syntax differs by engine. The useful design information is owner, purpose, write path, and consumer contract—not whether the separator is a period or a catalog/schema pair.

3. Constraints are executable assumptions when the engine enforces them

The local fact_sales table declares a composite primary key, NOT NULL columns, positive quantity, non-negative revenue/cost, and foreign keys to customer/product/store dimensions. SQLite foreign-key enforcement is connection-scoped, so the fixture explicitly runs PRAGMA foreign_keys=ON. Other analytical engines may parse but not enforce some constraints, use them only for optimization, or enforce them differently.

fact_sales.sql
PRAGMA foreign_keys=ON;CREATE TABLE fact_sales(  order_date TEXT NOT NULL,  order_id TEXT NOT NULL,  line_no INTEGER NOT NULL,  customer_id TEXT NOT NULL REFERENCES dim_customer(customer_id),  product_id TEXT NOT NULL REFERENCES dim_product(product_id),  store_id TEXT NOT NULL REFERENCES dim_store(store_id),  quantity INTEGER NOT NULL CHECK(quantity > 0),  revenue_usd INTEGER NOT NULL CHECK(revenue_usd >= 0),  cost_usd INTEGER NOT NULL CHECK(cost_usd >= 0),  gross_profit_usd INTEGER NOT NULL,  PRIMARY KEY(order_id,line_no));

The lab injects two invalid rows. Quantity 0 is rejected by the CHECK constraint and customer C999 is rejected by the foreign key. That is observable evidence that the local assumptions are enforced.

Negative case Observed SQLite 3.46.1 result Production interpretation
quantity = 0 REJECTED: CHECK constraint failed Do not rely on documentation-only constraints; test actual enforcement.
customer_id = C999 REJECTED: FOREIGN KEY constraint failed Orphan policy is explicit; unknown-member routing must happen before this fact insert if that is the governed design.

4. Environment promotion should change configuration, not business SQL

A brittle model contains prod_dw.dw_core.fact_sales in every query and requires string replacement for test. A safer build resolves environment-specific catalogs, credentials, object roots, retention settings, and compute classes from configuration while keeping the transformation graph identical. Promotion means moving a tested artifact/configuration through environments—not copying a mutable notebook until it “works in prod.”

environment_config.json
{  "dev":  {"catalog": "atlasmart_dev",  "raw_root": "./raw/dev",  "role": "dw_dev_writer"},  "test": {"catalog": "atlasmart_test", "raw_root": "./raw/test", "role": "dw_test_writer"},  "prod": {"catalog": "atlasmart_prod", "raw_root": "s3://example-placeholder/not-used-in-lab", "role": "dw_prod_pipeline"}}// The lab remains local; the prod URI is illustrative configuration, not executed cloud storage.

Do not put secrets in that file. Identity/credential injection belongs to the deployment system. The lesson separates configuration shape from secret material.

5. Controlled failure: constraints and ownership exist only in a slide deck

If an analytical engine does not enforce foreign keys, a declared constraint may be useful documentation but cannot replace reconciliation. If every developer can write directly to certified schemas, ownership labels do not prevent semantic drift. The repair is defense in depth: enforced constraints where supported, data tests where not, least-privilege write identities, code review, lineage, and consumer certification.

6. Hands-on: inspect the negative constraint evidence

inspect_constraints.py
import jsonfrom pathlib import Pathr=json.loads(Path("atlasmart_ch17_lab/reports/benchmark_report.json").read_text())print(r["constraint_tests"])# Expected keys: negative_quantity and orphan_customer# Both values begin with "REJECTED:" in the validated fixture.

This proves enforcement only for the local SQLite connection that enabled foreign keys. If you port the schema, rerun negative tests on the target engine instead of assuming DDL words have identical force.

7. Production judgment and bridge

Organization and constraints reduce accidental ambiguity, but they do not choose a partition key. Lesson 3 uses the fixed workload to decide whether date, coarser range, or hash partitioning can actually prune work—and shows why tiny/high-cardinality partition schemes create their own operational cost.

Knowledge check

Check your understanding

  1. Is a schema boundary the same thing as fact grain?
  2. Why does the fixture set PRAGMA foreign_keys=ON?
  3. What should promotion change?
  4. What if an analytical engine does not enforce declared foreign keys?
  5. Why test negative rows?
Review the answers

1. No. Namespace/ownership boundaries organize deployment; grain defines what one fact row represents.

2. SQLite foreign-key enforcement is connection-scoped and should not be assumed implicitly.

3. Environment-specific configuration/identity and physical endpoints, not the semantic transformation logic.

4. Use explicit data/reconciliation tests and governance; do not treat decorative DDL as integrity evidence.

5. To observe whether the physical engine actually enforces the assumptions the model depends on.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.