Your Schema Is Really a Theory of What Can Change
A customer updates their company name.
Should every invoice they have ever received change too?
A product manager renames a subscription plan. Should last year’s revenue reports display the new name or the name customers actually saw when they paid?
An employee moves from one department to another. Should the database remember the transfer, or simply replace the old department with the new one?
These questions look like edge cases until a real application encounters them. Then they become support tickets, reporting disputes, broken audits, awkward migrations, and features that are suddenly much harder to build than anyone expected.
The deeper issue is rarely a missing table or an incorrectly drawn line in an ER diagram. It is that the schema was built around what the business has, rather than what the business allows to change.
That distinction matters. Good database design is not merely a catalog of customers, products, orders, and teams. It is a theory about which facts are stable, which facts evolve, which changes need history, and which pieces of information must remain frozen after a particular event.
Once you see schema design through that lens, many difficult modeling decisions become easier to reason about.
Most Schemas Describe Objects but Ignore Time
Imagine a simple subscription product. The first ER diagram might contain three familiar entities:
- Customer
- Plan
- Subscription
The subscription points to a customer and a plan. The plan stores a name and a monthly price. Everything appears clean.
Then the business changes the Pro plan from $29 to $39 per month.
If the price exists only on the Plan record, every subscription now appears to cost $39. That may be correct for new customers, but what about existing customers who were promised the old rate? What about an invoice issued three months ago? What about a financial report that must explain the amount actually charged?
The original model described the objects accurately. There really are customers, plans, and subscriptions. But it failed to describe how their facts behave over time.
The design quietly assumed that a plan has one permanent price and that every reference to the plan should always inherit its current value. That was not a technical decision alone. It was a business assumption embedded in the schema.
A stronger model might separate:
- the current advertised price of a plan,
- the price agreed to for a particular subscription,
- the amount charged on a specific invoice,
- and the history of pricing changes.
Those values may happen to match today. They are not the same fact.
The Most Important Question Is Often “Can These Change Independently?”
When two pieces of information are stored together, the schema suggests that they belong to the same lifecycle. When they are separated, the schema allows them to evolve independently.
Consider a company account with a billing address.
A naive design might place the address directly on the company:
- company name,
- street,
- city,
- postal code,
- country.
This works until the company has multiple offices, separate billing and shipping addresses, or an address that must be preserved on old orders.
The problem is not simply that the address needs its own table. The real problem is that several different concepts were treated as if they changed together:
- where the company is currently located,
- where an order should be delivered,
- where an invoice should be addressed,
- and what address was used when a transaction occurred.
Splitting data into more tables does not automatically solve this. A single reusable Address record referenced by every order may still be dangerous. If someone edits that shared record, yesterday’s order could appear to have been shipped somewhere else.
The useful design question is not “Should address be an entity?”
It is: Which address facts are allowed to change without rewriting the meaning of past events?
A Common Assumption Worth Reversing
Database design is often presented as the process of reducing duplication. That is useful advice in some contexts, but it can become misleading when treated as a universal goal.
Sometimes duplication is exactly what protects meaning.
An order may copy the product name, unit price, tax amount, and shipping address that applied at checkout. At first glance, this looks redundant because the same information also exists on the product and customer records.
But the copied values answer a different question.
The product table answers, “What is this product called now?”
The order item answers, “What was this product called when the customer purchased it?”
The customer profile answers, “Where should we contact this person today?”
The order answers, “Where did they ask us to ship this purchase?”
These are not duplicate facts. They are facts with different time boundaries.
A well-designed schema does not eliminate repeated values blindly. It distinguishes current truth from historical truth.
This is why database design cannot be reduced to tidy diagrams or rules about avoiding repetition. The central task is deciding what each stored value means and how long that meaning must remain valid.
State, History, and Events Are Different Kinds of Truth
Suppose a support ticket has a status field:
open, in_progress, waiting, or resolved.
A single status column is useful when the application only needs to know the ticket’s current condition.
But requirements rarely stop there.
Someone eventually asks:
- How long do tickets remain in progress?
- Who reopened this case?
- How many tickets were waiting on customers last week?
- What was the ticket’s status when a service-level deadline expired?
A current status value cannot answer those questions. Updating waiting to resolved destroys the previous fact unless history is stored elsewhere.
There are several legitimate ways to model this:
- Current state: Store the latest status directly on the ticket for simple reads.
- Status history: Store each transition with timestamps and actors.
- Events: Record actions such as
ticket_assigned,customer_replied, andticket_resolved.
These approaches are related, but they are not interchangeable.
State tells you what is true now. History tells you what was true during specific periods. Events tell you what happened.
A mature schema may use all three because applications often need all three views of reality.
Weak Change Boundaries Create Product Problems
Consider a project management application where every task has an owner_id.
At first, ownership appears straightforward. One task belongs to one user.
Later, the product adds teams. A task can now belong to a team while an individual remains responsible for completing it. Then contractors are invited to collaborate. Then tasks can move between workspaces. Then managers want reports showing who owned a task at the moment it became overdue.
The original owner_id begins carrying too many meanings:
- the person responsible for the work,
- the account that created the task,
- the team with administrative control,
- the workspace where the task is visible,
- or the user currently assigned to it.
This is not merely an untidy column name. It produces visible product confusion.
Permissions become difficult because “ownership” controls both access and responsibility. Reporting becomes unreliable because reassignment overwrites earlier ownership. Notifications go to the wrong person because the application cannot distinguish who manages the task from who performs it.
The schema did not just store data badly. It collapsed several business relationships into one ambiguous relationship, and the application inherited that ambiguity.
Clear boundaries might require separate concepts such as:
- created_by for authorship,
- assigned_to for responsibility,
- team_id for organizational grouping,
- workspace_id for access scope,
- and an assignment history for past responsibility.
Once those meanings are separated, product rules become easier to explain and enforce.
Relationships Also Have Lifecycles
ER diagrams often emphasize whether a relationship is one-to-one, one-to-many, or many-to-many. That is necessary, but it is only the beginning.
A relationship may also have:
- a start date,
- an end date,
- a status,
- a source,
- a role,
- or rules about whether it can overlap with another relationship.
Take the relationship between an employee and a department.
Placing department_id on the employee record answers one question: “Which department is this employee in now?”
It does not answer:
- When did they join that department?
- Which department were they in last quarter?
- Did they temporarily belong to two departments?
- Who approved the transfer?
When a relationship gains its own attributes or history, it may deserve to become a first-class record.
An EmployeeDepartmentAssignment table can represent the relationship itself, including its effective period and role. The shift is subtle but powerful: the database no longer treats department membership as a permanent property of a person. It treats membership as an arrangement that begins, changes, and ends.
This is one of the most useful things to look for while reviewing schema design: relationships that appear simple only because their lifecycle has been ignored.
Names Are Clues About Hidden Change
Confusing names often signal confused boundaries.
Columns such as status, type, owner, value, and date are not automatically wrong. But they deserve suspicion because they can hide multiple meanings behind convenient labels.
Ask what kind of status is being stored. Approval status? Payment status? Delivery status? Publication status?
Ask which date is meant. Creation date? Effective date? Due date? Settlement date? Cancellation date?
Ask whether “customer” means the person who uses the product, the company that pays, the account that signed the contract, or the legal party named on the invoice.
Precise naming does more than improve readability. It forces the design to admit when two concepts are different.
Tools for creating ER diagrams can help expose these distinctions because visual relationships are harder to ignore than scattered columns in migration files. A designer might sketch an early model in erd.dbdesigner.net, then use the broader workspace at dbdesigner.net to inspect how entities, relationships, and naming choices fit together.
The value of the diagram is not that it makes the schema look polished. It gives the team a surface on which assumptions can be challenged before they become production behavior.
Test a Schema by Imagining Specific Changes
Before accepting a design, avoid asking only whether it supports today’s screens. Walk through concrete changes instead.
- A name changes. Which historical records should display the old name, and which should display the new one?
- A relationship ends. Is the record deleted, deactivated, or given an end date?
- A value is corrected. Must the system preserve the original value, the corrected value, or both?
- A rule changes. Do existing records inherit the new rule, or remain governed by the rule that applied when they were created?
- Two concepts diverge. What happens when the payer is no longer the user, the owner is no longer the assignee, or the current price differs from the contracted price?
These thought experiments reveal more than a checklist of entities ever could.
They expose accidental coupling. They reveal where history is being overwritten. They show which records represent living profiles and which represent completed transactions. They also uncover places where one field is being asked to carry several meanings.
This does not mean designing for every hypothetical future. Overengineering a small system around imagined complexity can be just as harmful as ignoring obvious change.
The goal is to identify changes that are natural to the domain. Prices change. People move. teams reorganize. Contracts expire. Orders must remain explainable. Permissions evolve. Names are corrected. Relationships begin and end.
A schema should not predict every feature. It should avoid denying the ordinary behavior of the reality it represents.
The Best Schemas Preserve Meaning Through Change
A database is often described as a place where an application stores information. That description is technically true and practically incomplete.
A database also decides which version of reality survives.
When a field is updated, something may disappear. When two concepts share one column, an important distinction may be lost. When a relationship has no history, the system may know where things are but not how they arrived there. When transactional data points only to editable profiles, the past can quietly rewrite itself.
Strong database design begins by noticing those risks.
It asks which facts belong to the present, which belong to an event, which describe an agreement, and which must remain unchanged even when the surrounding world moves on. It uses schema design to create boundaries between those meanings. It uses ER diagrams not merely to document tables, but to make the consequences of those boundaries visible.
The most useful question is therefore not, “Does this schema represent the business?”
Almost any schema can represent the business for a moment.
The harder and more revealing question is:
When the business changes, will the database still remember what its data meant?

Recent Comments