Skip to content

Supertype and Subtype Schemas

Financial services and similar businesses offer many products across disparate lines of business. A retail bank may hold dozens of account types, from checking accounts to mortgages to credit cards.

All are accounts, but their measures barely overlap. A mortgage tracks interest rate, property value and principal remaining. A checking account tracks overdrafts and ATM withdrawals. A credit card tracks credit limit and minimum payment due.

The problem: the consolidated mega-fact table

The obvious response is one fact table holding the union of every product's measures.

account_typedate_keybalanceoverdraftproperty_valueinterest_ratecredit_limit
CheckingJan 1$5,000$0NULLNULLNULL
MortgageJan 1$300,000NULL$400,0007%NULL
Credit CardJan 1$2,000NULLNULL18%$20,000

Across dozens of products this produces hundreds of columns, almost all NULL for any given row. Users can't tell which columns apply to which products, and every query needs an account_type filter to avoid combining measures that have nothing to do with each other.

The solution: core and custom schemas

Split the model in two. A supertype (core) schema holds what every product shares. A subtype (custom) schema per product holds everything that product needs.

Core schema

The core fact table carries only the strict intersection - measures that exist for every account type, without exception.

Core Account Snapshot Fact:

date_keyaccount_keyaccount_type_keybalance
Jan 1101Checking$5,000
Jan 1102Mortgage$300,000
Jan 1103Credit Card$2,000

Core Account Dimension:

account_keycustomer_nameopen_datestatus
101John DoeJan 2025Active
102Mary SmithFeb 2025Active
103John DoeMar 2025Active

This answers the cross-product questions: total balances by line of business, customers holding more than one product type, portfolio growth over time.

NOTE

interest_rate applies to both mortgages and credit cards but not to checking accounts, so it stays out of the core. A measure shared by some subtypes belongs in those subtypes. Keeping the core narrow is what keeps it usable - a core that accumulates near-common measures drifts back toward the mega-fact table.

Custom schemas

Each product gets its own fact table containing all measures relevant to it, including the common ones.

Mortgage Fact:

date_keyaccount_keyloan_balanceinterest_rateproperty_valueprincipal_paid
Jan 1102$300,0007%$400,000$100,000

Checking Account Fact:

date_keyaccount_keybalanceoverdraft_countatm_withdrawals
Jan 1101$5,00003

Repeating balance in the custom tables is deliberate. It means a mortgage report never has to join back to the core fact table to get it - a fact-to-fact join, which is both slow and easy to get wrong. The core duplicates a small subset of measures so that cross-product questions don't require unioning every custom table.

Custom schemas can also have their own dimensions. A mortgage fact might reference a property dimension that no other product needs.

The same account_key appears in both schemas, so every custom fact table joins directly to the core account dimension. A mortgage-specific report can still group by customer name or filter on account status without duplicating those attributes.

account_type_key is the path in the other direction. A user looking at a core-level total can see which product types make it up, and that tells them which custom schema to open for detail.