| name | data-modeler |
| description | Expert data modeling covering star schema, snowflake schema, Data Vault 2.0, dimensional modeling, slowly changing dimensions, bridge tables, fact table types, conformed dimensions, ERD creation, and modeling tool usage for building enterprise-grade analytical and operational data models.
Use when the user asks about data modeler, data modeler best practices, or needs guidance on data modeler implementation.
Do NOT use when the user needs a different specialized skill or is asking about an unrelated technology domain.
|
| license | Apache-2.0 |
| metadata | {"author":"foundry-skills","version":"1.0.0","tags":"data-science sql architecture","category":"data-engineering","subcategory":"data-modeling","depends":"","disclaimer":"none","difficulty":"intermediate"} |
Data Modeler
Overview
Data modeling is the discipline of designing the structure, relationships, and constraints of data to support both operational and analytical workloads. This skill covers the full spectrum from third normal form (3NF) operational models through dimensional models for analytics and Data Vault for enterprise data integration.
Dimensional Modeling (Kimball Methodology)
Star Schema
The star schema is the foundation of dimensional modeling. A central fact table connects to denormalized dimension tables via foreign keys.
CREATE TABLE fact_sales (
sale_key BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
date_key INT NOT NULL REFERENCES dim_date(date_key),
product_key INT NOT NULL REFERENCES dim_product(product_key),
customer_key INT NOT NULL REFERENCES dim_customer(customer_key),
store_key INT NOT NULL REFERENCES dim_store(store_key),
promotion_key INT NOT NULL REFERENCES dim_promotion(promotion_key),
transaction_id VARCHAR(20) NOT NULL,
quantity_sold INT NOT NULL,
unit_price NUMERIC(10,2) NOT NULL,
discount_amount NUMERIC(10,2) NOT NULL DEFAULT 0,
net_amount NUMERIC(12,2) NOT NULL,
cost_amount NUMERIC(12,2) NOT NULL,
profit_amount NUMERIC(12,2) GENERATED ALWAYS AS (net_amount - cost_amount) STORED,
inventory_on_hand INT,
margin_pct NUMERIC(5,2) GENERATED ALWAYS AS (
CASE WHEN net_amount > 0
THEN ((net_amount - cost_amount) / net_amount * 100)
ELSE 0 END
) STORED
);
CREATE TABLE dim_product (
product_key INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
product_id VARCHAR(20) NOT NULL,
product_name VARCHAR(200) NOT NULL,
brand VARCHAR(100),
category VARCHAR(100),
subcategory VARCHAR(100),
department VARCHAR(100),
unit_of_measure VARCHAR(20),
is_active BOOLEAN DEFAULT TRUE,
effective_date DATE NOT NULL,
expiration_date DATE NOT NULL DEFAULT '9999-12-31',
is_current BOOLEAN NOT NULL DEFAULT TRUE,
version INT NOT NULL DEFAULT 1,
row_hash CHAR(32) NOT NULL
);
Date Dimension (Critical Reference)
CREATE TABLE dim_date (
date_key INT PRIMARY KEY,
full_date DATE NOT NULL UNIQUE,
day_of_week SMALLINT NOT NULL,
day_name VARCHAR(10) NOT NULL,
day_of_month SMALLINT NOT NULL,
day_of_year SMALLINT NOT NULL,
week_of_year SMALLINT NOT NULL,
iso_week SMALLINT NOT NULL,
month_number SMALLINT NOT NULL,
month_name VARCHAR(10) NOT NULL,
month_name_short CHAR(3) NOT NULL,
quarter_number SMALLINT NOT NULL,
quarter_name CHAR(2) NOT NULL,
year_number INT NOT NULL,
fiscal_year INT NOT NULL,
fiscal_quarter SMALLINT NOT NULL,
fiscal_month SMALLINT NOT NULL,
is_weekend BOOLEAN NOT NULL,
is_holiday BOOLEAN NOT NULL DEFAULT ,
holiday_name (),
is_business_day ,
year_month () ,
year_quarter () ,
is_current_day ,
is_current_month ,
is_current_quarter ,
is_current_year ,
days_ago ,
months_ago
);
dim_date
TO_CHAR(d, ):: date_key,
d full_date,
(ISODOW d):: day_of_week,
TO_CHAR(d, ) day_name,
( d):: day_of_month,
(DOY d):: day_of_year,
(WEEK d):: week_of_year,
(ISOYEAR d):: iso_week,
( d):: month_number,
TO_CHAR(d, ) month_name,
TO_CHAR(d, ) month_name_short,
(QUARTER d):: quarter_number,
(QUARTER d) quarter_name,
( d):: year_number,
( d)
( d)::
( d):: fiscal_year,
( d)
((( d) ) )::
((( d) ) ):: fiscal_quarter,
( d)
(( d) )::
(( d) ):: fiscal_month,
(ISODOW d) (, ) is_weekend,
is_holiday,
holiday_name,
(ISODOW d) (, ) is_business_day,
TO_CHAR(d, ) year_month,
( d) (QUARTER d) year_quarter,
, , , , ,
generate_series(::, ::, ) d;
Snowflake Schema
Normalizes dimension tables into sub-dimensions. Reduces storage but increases query complexity with additional joins.
fact_sales
-> dim_product
-> dim_brand
-> dim_category
-> dim_department
-> dim_store
-> dim_city
-> dim_state
-> dim_country
-> dim_date
When to use snowflake over star:
- Dimension tables are very large (>10M rows) and share sub-dimensions
- Storage cost is a primary concern
- Query patterns always filter on sub-dimension attributes
- ETL team prefers normalized source-of-truth dimensions
When to prefer star:
- Query performance is the priority (fewer joins)
- BI tools work better with flat dimensions
- Dimension tables are small-to-medium (<1M rows)
- Team prefers simplicity
Data Vault 2.0
Data Vault is an enterprise modeling methodology designed for agility, auditability, and parallel loading.
Core Components
CREATE TABLE hub_customer (
hub_customer_hk CHAR(32) PRIMARY KEY,
customer_bk VARCHAR(50) NOT NULL,
load_date TIMESTAMP NOT NULL,
record_source VARCHAR(100) NOT NULL,
UNIQUE (customer_bk)
);
CREATE TABLE link_order (
link_order_hk CHAR(32) PRIMARY KEY,
hub_customer_hk CHAR(32) NOT NULL REFERENCES hub_customer,
hub_product_hk CHAR(32) NOT NULL REFERENCES hub_product,
hub_store_hk CHAR(32) NOT NULL REFERENCES hub_store,
order_bk VARCHAR(50) NOT NULL,
load_date TIMESTAMP NOT NULL,
record_source VARCHAR(100) NOT NULL,
UNIQUE (hub_customer_hk, hub_product_hk, hub_store_hk, order_bk)
);
CREATE TABLE sat_customer_details (
hub_customer_hk () hub_customer,
load_date ,
load_end_date ,
record_source () ,
hash_diff () ,
first_name (),
last_name (),
email (),
phone (),
tier (),
(hub_customer_hk, load_date)
);
eff_sat_order (
link_order_hk () link_order,
load_date ,
load_end_date ,
record_source () ,
is_active ,
(link_order_hk, load_date)
);
pit_customer (
hub_customer_hk () ,
snapshot_date ,
sat_customer_details_ldts ,
sat_customer_contact_ldts ,
sat_customer_finance_ldts ,
(hub_customer_hk, snapshot_date)
);
Data Vault Loading Pattern
import hashlib
def hash_key(*business_keys):
"""Generate hash key from business key components."""
concatenated = '||'.join(str(k).strip().upper() for k in business_keys)
return hashlib.md5(concatenated.encode('utf-8')).hexdigest()
def hash_diff(**attributes):
"""Generate hash diff from satellite attributes for change detection."""
concatenated = '||'.join(
str(attributes.get(k, '')).strip()
for k in sorted(attributes.keys())
)
return hashlib.md5(concatenated.encode('utf-8')).hexdigest()
def load_hub(source_df, hub_table, business_key_col, record_source, engine):
"""Load hub table - insert only new business keys."""
source_df['hub_hk'] = source_df[business_key_col].apply(hash_key)
source_df['load_date'] = pd.Timestamp.utcnow()
source_df['record_source'] = record_source
sql = f"""
INSERT INTO {hub_table} (hub_hk, business_key, load_date, record_source)
SELECT hub_hk, business_key, load_date, record_source
FROM staging
WHERE hub_hk NOT IN (SELECT hub_hk FROM {hub_table})
"""
Fact Table Types
Transaction Fact Table
- One row per event/transaction
- Most common type
- Additive measures
- Example:
fact_sales, fact_clicks
Periodic Snapshot Fact Table
- One row per entity per time period
- Semi-additive measures (cannot sum across time)
- Example:
fact_account_daily_balance, fact_inventory_weekly
CREATE TABLE fact_account_daily_snapshot (
date_key INT NOT NULL REFERENCES dim_date,
account_key INT NOT NULL REFERENCES dim_account,
balance NUMERIC(15,2) NOT NULL,
credit_limit NUMERIC(15,2),
transactions_count INT NOT NULL DEFAULT 0,
deposits_amount NUMERIC(15,2) DEFAULT 0,
withdrawals_amount NUMERIC(15,2) DEFAULT 0,
PRIMARY KEY (date_key, account_key)
);
Accumulating Snapshot Fact Table
- One row per entity lifecycle (updated as milestones are reached)
- Multiple date keys tracking progress through a process
- Example:
fact_order_fulfillment, fact_claim_processing
CREATE TABLE fact_order_fulfillment (
order_key BIGINT PRIMARY KEY,
order_date_key INT REFERENCES dim_date,
payment_date_key INT REFERENCES dim_date,
ship_date_key INT REFERENCES dim_date,
delivery_date_key INT REFERENCES dim_date,
return_date_key INT REFERENCES dim_date,
customer_key INT REFERENCES dim_customer,
product_key INT REFERENCES dim_product,
days_to_payment INT,
days_to_ship INT,
days_to_deliver INT,
days_to_return INT,
order_amount NUMERIC(12,2),
current_status VARCHAR(20)
);
Factless Fact Table
- Records events or conditions with no measures
- Example: student enrollment, event attendance, coverage eligibility
CREATE TABLE fact_student_enrollment (
date_key INT NOT NULL REFERENCES dim_date,
student_key INT NOT NULL REFERENCES dim_student,
course_key INT NOT NULL REFERENCES dim_course,
instructor_key INT NOT NULL REFERENCES dim_instructor,
PRIMARY KEY (date_key, student_key, course_key)
);
Bridge Tables
Bridge tables resolve many-to-many relationships between facts and dimensions.
CREATE TABLE bridge_diagnosis (
diagnosis_group_key INT NOT NULL,
diagnosis_key INT NOT NULL REFERENCES dim_diagnosis,
diagnosis_rank SMALLINT NOT NULL,
weighting_factor NUMERIC(5,4) NOT NULL,
PRIMARY KEY (diagnosis_group_key, diagnosis_key)
);
CREATE TABLE fact_medical_visit (
visit_key BIGINT PRIMARY KEY,
patient_key INT REFERENCES dim_patient,
provider_key INT REFERENCES dim_provider,
date_key INT REFERENCES dim_date,
diagnosis_group_key INT NOT NULL,
total_charge NUMERIC(12,2)
);
SELECT
d.diagnosis_name,
SUM(f.total_charge * b.weighting_factor) AS weighted_charges
FROM fact_medical_visit f
JOIN bridge_diagnosis b ON f.diagnosis_group_key = b.diagnosis_group_key
JOIN dim_diagnosis d ON b.diagnosis_key = d.diagnosis_key
d.diagnosis_name;
Conformed Dimensions
Conformed dimensions are shared across multiple fact tables and business processes, ensuring consistent reporting.
Design Principles
- Single source of truth: One
dim_customer used by sales, support, marketing
- Consistent grain: Same business key definition across all consumers
- Shared attributes: Common descriptive columns with identical meanings
- Bus matrix: Documents which dimensions are used by which business processes
Business Process Matrix (Bus Matrix):
| dim_date | dim_customer | dim_product | dim_store | dim_employee |
---------------------+----------+--------------+-------------+-----------+--------------+
fact_sales | X | X | X | X | X |
fact_inventory | X | | X | X | |
fact_customer_svc | X | X | X | | X |
fact_web_clickstream | X | X | X | | |
fact_hr_attendance | X | | | X | X |
Slowly Changing Dimensions Decision Guide
| SCD Type | Behavior | History | Storage | Complexity | Use When |
|---|
| Type 0 | Never changes | N/A | Minimal | Lowest | Fixed attributes (birthdate, SSN) |
| Type 1 | Overwrite | Lost | Minimal | Low | Corrections, non-historical attributes |
| Type 2 | New row | Full | High | Medium | Track all changes (address, status) |
| Type 3 | New column | Previous only | Moderate | Low | Only care about previous value |
| Type 4 | Mini-dimension | In separate table | Moderate | Medium | Rapidly changing attributes |
| Type 6 | Hybrid 1+2+3 | Full + current | Highest | Highest | Need both current and historical views |
ERD Creation Best Practices
Naming Conventions
Tables:
- Fact tables: fact_{business_process} (fact_sales)
- Dimension tables: dim_{entity} (dim_customer)
- Bridge tables: bridge_{relationship} (bridge_diagnosis)
- Staging tables: stg_{source}_{entity} (stg_crm_contact)
- Hub tables: hub_{entity} (hub_customer)
- Link tables: link_{relationship} (link_order)
- Satellite tables: sat_{hub/link}_{descriptor} (sat_customer_details)
Columns:
- Surrogate keys: {table_name}_key or {table_name}_sk
- Business keys: {entity}_bk or {entity}_id
- Foreign keys: {referenced_dimension}_key
- Hash keys: {entity}_hk (Data Vault)
- Measures: descriptive name ({quantity_sold}, {net_amount})
- Dates: {event}_date or {event}_at
- Flags: is_{condition} (is_active, is_current)
Model Documentation
Every model should include:
- Grain statement: "One row per [X] per [Y] per [Z]"
- Source mapping: Which source systems feed each column
- Business rules: How derived columns are calculated
- SCD strategy: Which type applies to each dimension attribute
- Refresh frequency: How often the table is updated
- Row count estimates: Expected volume and growth rate
- Partitioning strategy: If applicable for the target platform
Modeling Anti-Patterns
- Centipede fact table: Too many dimensions joined directly; use bridge tables or junk dimensions
- Enterprise bus without conformed dimensions: Siloed facts that cannot be compared
- Overloaded dimensions: Mixing unrelated entities in one dimension (customer + vendor)
- Missing surrogate keys: Using natural keys as PKs prevents SCD Type 2
- Null foreign keys: Use "Unknown" or "Not Applicable" dimension rows instead
- Inappropriate grain: Mixing daily and monthly granularity in the same fact table
- Header/line fact split: Breaking an order into separate header and line fact tables when a single line-level fact suffices
When to Use
Use this skill when:
- Designing or implementing data modeler solutions
- Reviewing or improving existing data modeler approaches
- Making architectural or implementation decisions about data modeler
- Learning data modeler patterns and best practices
- Troubleshooting data modeler-related issues
Do NOT use this skill when:
- The question is about a fundamentally different technology domain
- A more specific sibling skill covers the exact topic needed
- The user needs a complete hands-on tutorial rather than expert guidance
Output Format
# Data Modeler Analysis
## Context Assessment
[Situation summary and constraints]
## Recommended Approach
[Primary recommendation with rationale]
## Implementation Steps
1. [Step with specific details]
2. [Step with specific details]
3. [Step with specific details]
## Trade-offs and Considerations
- [Key trade-off 1]
- [Key trade-off 2]
## Next Steps
- [Immediate action item]
- [Follow-up action item]
Example
Input: "Help me implement data modeler for a medium-scale production application"
Output: A structured analysis covering current state assessment, recommended data modeler approach with specific patterns, implementation roadmap with milestones, and risk mitigation strategies tailored to the application scale and constraints.
Edge Cases
- Legacy system integration: When data modeler must coexist with legacy approaches, provide a gradual migration path rather than a complete rewrite
- Scale mismatch: When the solution complexity exceeds the project scale, recommend a simpler approach and note when to revisit
- Team skill gaps: When the team lacks experience with the recommended approach, include learning resources and simpler alternatives
- Conflicting requirements: When constraints conflict (e.g., performance vs. maintainability), explicitly state the trade-off and recommend based on stated priorities