| Lesson 9 | Denormalization |
| Objective | Explain when denormalization is justified and how to maintain and verify a derived read structure. |
Denormalization deliberately stores duplicated or precomputed information to improve a specific access pattern. It can reduce repeated joins or aggregation work, but adds storage, write work, and consistency responsibilities. It is justified by measured workload benefits, not simply by the presence of joins.
The preceding lessons explained how normalization separates facts according to dependencies. Denormalization should begin with that understanding. Know which facts are authoritative, which copies are derived, and how disagreement will be detected and repaired.
Combining relations or adding repeated descriptive attributes can reduce a table's normal form. However, a practical design can retain normalized base tables and add a separate reporting table, stored summary, or materialized result. The base relations do not lose their normal form merely because another structure duplicates their information.
An ordinary view can simplify a query interface without storing a second copy of the result. A materialized result stores data and therefore needs a maintenance policy. Product support and refresh behavior vary; do not assume that creating a view automatically caches its rows.
Identify the exact query, its frequency, the amount of data processed, and the response time users require. Inspect its execution plan and test representative data volumes. A query with several joins can be efficient; a query against one very wide table can still be expensive.
For example, a dashboard refreshed every few minutes might benefit from a stored summary. A small lookup queried occasionally may gain little from duplication. These are hypotheses to test rather than universal rules.
A media company needs a frequently viewed list of employees, their genres, and the manager of each genre. Use these explicit rules:
The normalized source design is:
Employee(EmployeeID PK, EmployeeName, GenreID FK)
Genre(GenreID PK, GenreName, ManagerID FK)
Manager(ManagerID PK, ManagerName)
Employee.GenreID references Genre.GenreID
Genre.ManagerID references Manager.ManagerID
This is schema notation. The required foreign keys are non-null for the stated participation rules. The directory query is:
SELECT e.EmployeeID,
e.EmployeeName,
g.GenreID,
g.GenreName,
m.ManagerID,
m.ManagerName
FROM Employee AS e
JOIN Genre AS g ON g.GenreID = e.GenreID
JOIN Manager AS m ON m.ManagerID = g.ManagerID;
Suppose profiling shows this access pattern remains expensive after suitable tuning, and users accept a directory that can lag behind changes by up to five minutes. A separate read table is one option:
GenreTeamDirectory(
EmployeeID PK, EmployeeName, GenreID, GenreName,
ManagerID, ManagerName
)
EmployeeID remains unique because the example permits one genre per employee. If employees could belong to multiple teams, the read table's grain and key would need to change. Always define what one row represents before building a reporting structure.
After creating an empty destination with appropriate column types, this illustrative SQL copies the current directory:
INSERT INTO GenreTeamDirectory
(EmployeeID, EmployeeName, GenreID, GenreName,
ManagerID, ManagerName)
SELECT e.EmployeeID,
e.EmployeeName,
g.GenreID,
g.GenreName,
m.ManagerID,
m.ManagerName
FROM Employee AS e
JOIN Genre AS g ON g.GenreID = e.GenreID
JOIN Manager AS m ON m.ManagerID = g.ManagerID;
This is an initial population statement, not a complete refresh system. Running it repeatedly against the populated table would collide with existing EmployeeID keys. Production refresh logic must handle additions, changes, and deletions deliberately.
The read query can now use the stored result:
SELECT EmployeeID, EmployeeName, GenreName, ManagerName
FROM GenreTeamDirectory;
GenreName and ManagerName repeat for employees who share a team or manager. A manager rename no longer changes just one stored occurrence across the whole design: every affected directory copy must eventually reflect it. The potential reduction in read work has created additional maintenance work.
| Approach | Benefit | Responsibility |
|---|---|---|
| Synchronous maintenance in the source transaction | Source and derived changes commit together. | Handle every write path; measure extra writes, locking, and transaction duration. |
| Asynchronous change processing | Moves derived maintenance away from the immediate source write. | Handle lag, ordering, retries, deletions, and duplicate event delivery. |
| Scheduled rebuild or refresh | Provides a straightforward batch maintenance model. | Budget refresh time and publish a complete, consistent result. |
A trigger or stored procedure is an implementation mechanism, not a complete consistency policy by itself. Likewise, two separate application writes can leave mismatched data if the first succeeds and the second fails. Define the transaction or delivery guarantees that connect the writes.
For the directory, build a new result from a consistent source snapshot, validate it, and publish it using a mechanism supported by the chosen DBMS. Avoid exposing an empty or partially populated directory while rebuilding. Record the source snapshot time and successful publication time so the displayed freshness has a clear meaning.
A five-minute schedule alone does not guarantee data is at most five minutes old. Queue delays, rebuild duration, and failures also contribute. Monitor the actual age, define what readers should do if the limit is exceeded, and provide a way to rebuild from the authoritative tables.
Keep the directory read-only to ordinary application users. Corrections should go through the source tables and the maintenance process. Deleting the last employee in a genre then removes a directory row without erasing the authoritative Genre record.
An order line's agreed unit price is not necessarily a redundant copy of a product's current list price. The agreed price describes a transaction at a particular time; updating it whenever the catalog price changes would destroy history. Identify each attribute's meaning before classifying it as denormalization.
The same distinction applies to names and addresses saved on issued documents. Decide whether the stored value is a current display copy, an immutable historical fact, or something else. These choices require different maintenance rules.
Prefer the simpler normalized design when it already meets the service target, when duplicated facts change frequently, or when the proposed refresh process costs more than it saves. Strict freshness requirements may rule out an asynchronous copy, although a carefully designed transactional alternative may still be possible.
Preserving normalized base tables also makes the optimization reversible: readers can return to the original query if the derived structure stops providing value. Review the decision after workload or infrastructure changes rather than treating redundancy as permanent.
Record the target query, measured baseline, expected improvement, source tables, row grain, key, refresh mechanism, freshness limit, owner, validation checks, and rollback procedure. Specify which component is authoritative for each fact. A future maintainer should be able to distinguish a deliberate performance copy from accidental duplicate data.
Denormalization can improve selected reads by storing work in advance, but fewer joins do not guarantee better overall performance. Retain clear ownership of facts, design consistency and recovery before deployment, and verify the benefit under realistic workloads. Continue to the module conclusion to review normalization and its tradeoffs.