Where Everyone Was
An easy Data Modeling interview practice problem on DataDriven. Design the schema on the canvas: tables, keys and relationships, then get feedback on it.
- Domain
- Data Modeling
- Difficulty
- easy
- Seniority
- L4
Problem
Our customer support team needs to see a customer's current address and their full address history for regional sales analysis. How would you design a schema that handles address changes?
People move. Sometimes twice in a month. How do you remember where everyone was, and when?
Worked solution and explanation
Why this problem exists in real interviews
This is a tight probe of SCD Type 2 modeling. The moment a prompt asks for historical values and current values, the candidate must reach for effective-dated rows rather than mutating the dimension in place.
Break down the requirements
Step 1: Separate customers from addresses
Customer identity is stable; addresses change. Keep customers as the identity table and model addresses as their own dimension so several customers can share an address or reuse one after a move.
Step 2: Introduce a bridge with effective dates
customer_addresses carries (customer_id, address_id, effective_from, effective_to). This is a textbook Type 2 bridge: one row per period a customer lived at an address.
Step 3: Use NULL as sentinel for current
NULL on effective_to marks the current row. Query for current address with WHERE effective_to IS NULL; query for as-of with WHERE ts BETWEEN effective_from AND COALESCE(effective_to, 'infinity').
Step 4: Enforce one current row per customer
A partial unique index on (customer_id) WHERE effective_to IS NULL prevents two simultaneously active addresses per customer, which is the correctness guarantee the support team actually relies on.
The solution
Below is one conceptually sound approach: a Type 2 bridge between customers and addresses. The composite key (customer_id, address_id, effective_from) uniquely identifies each time-slice, and the NULL sentinel on effective_to anchors the current-row queries.
- customers: customer_id BIGINT PK, full_name TEXT, email TEXT, created_at TIMESTAMP
- addresses: address_id BIGINT PK, street TEXT, city TEXT, state TEXT, postal_code TEXT, country TEXT
- customer_addresses: customer_id BIGINT PK, address_id BIGINT PK, effective_from TIMESTAMP PK, effective_to TIMESTAMP, address_type TEXT
- customers references customer_addresses
- addresses references customer_addresses
The analysis pattern
SELECT
c.full_name,
a.street,
a.city,
a.state
FROM customers c
JOIN customer_addresses ca ON ca.customer_id = c.customer_id
JOIN addresses a ON a.address_id = ca.address_id
WHERE ca.effective_from <= '2024-01-15'
AND (ca.effective_to IS NULL OR ca.effective_to > '2024-01-15')
AND c.customer_id = 42Trade-offs and alternatives
- Type 2 SCD bridge
- One join path, perfect as-of queries, clean constraints. Cost: slightly more complex writes and a close-then-insert transaction.
- Type 1 plus separate audit log
- Current address on
customers, moves appended to a separate audit table. Cost: as-of queries require a union across two tables and drift between the two is silent.
Common follow-up questions
- How would you model a customer who has both a billing and a shipping address at the same time? (Tests whether `address_type` on the bridge is enough to allow multiple concurrent addresses of different types.)
- What if GDPR requires a customer's history to be erased? (Tests whether the candidate introduces a tombstone versus hard delete on the bridge.)
- How would you detect data entry errors where the `effective_from` is accidentally backdated? (Tests range constraint thinking and whether a CHECK constraint enforces `effective_from` < `effective_to`.)