normalization and denormalization
Normalization and Denormalization
Learn how normalization organizes relational data to reduce redundancy and prevent modification anomalies, and how deliberate denormalization can improve measured read performance while introducing additional consistency responsibilities.
Introduction
A relational database can store the correct information and still have a poor structure. When unrelated facts are combined in one table, the same values are often repeated across many rows.
Repeated data wastes storage, complicates updates, and creates opportunities for contradictory information.
Normalization organizes data into related tables so that each important fact has an appropriate and controlled place.
Denormalization deliberately introduces selected duplication or precomputed data to support specific read-performance requirements.
Core idea: Normalize first to establish a reliable source of truth. Denormalize later only when measured workload evidence shows that a particular read path needs a different physical structure.
In your System Design curriculum, Normalization and Denormalization is Topic 5.2 under Relational Data Modeling & SQL. It follows entities and relationships and precedes keys and constraints, ACID, transactions, isolation, indexes, query plans, and connection pools.
Prerequisites
| # | Prerequisite | Why It Is Needed |
|---|---|---|
| 1 | Entities and relationships | Normalization depends on identifying distinct entities, attributes, and associations. |
| 2 | Primary and foreign keys | Decomposed tables are connected through keys. |
| 3 | Basic SQL | The examples use table definitions, joins, inserts, updates, and aggregate queries. |
| 4 | Functional dependencies | Normal forms examine how one set of attributes determines another. |
| 5 | Database transactions | Denormalized copies often require coordinated updates. |
| 6 | Query-performance basics | Denormalization should respond to measured workload and query-plan evidence. |
What Is Normalization?
Normalization is a systematic process for organizing relational data to reduce unnecessary duplication and prevent insertion, update, and deletion anomalies.
The process usually decomposes a large relation into smaller related relations based on keys and functional dependencies.
The principal goals are:
- Store each fact in an appropriate place
- Reduce unnecessary repetition
- Prevent contradictory values
- Support reliable inserts, updates, and deletes
- Clarify entity and relationship ownership
- Improve maintainability
- Preserve data integrity
Data Redundancy
Data redundancy occurs when the same fact is stored repeatedly in several rows or columns.
Redundant Order Data
| Order | Customer | Product | Product Name | Quantity | |
|---|---|---|---|---|---|
| ORD-1001 | CUS-42 | customer@example.com | PRD-10 | Keyboard | 1 |
| ORD-1001 | CUS-42 | customer@example.com | PRD-20 | Mouse | 2 |
| ORD-1002 | CUS-42 | customer@example.com | PRD-30 | Monitor | 1 |
The customer email is repeated for every purchased product. Product names can also repeat across many orders.
If one repeated value is updated and another is missed, the database can contain contradictory versions of the same fact.
Modification Anomalies
Poorly structured relations commonly produce three types of anomalies.
| Anomaly | Meaning |
|---|---|
| Insertion anomaly | A fact cannot be stored without inserting an unrelated fact |
| Update anomaly | One fact must be updated in several places |
| Deletion anomaly | Deleting one fact unintentionally removes another fact |
Insertion Anomaly
Problem:
Product data exists only inside order rows.
A new product cannot be stored
until somebody places an order.
Update Anomaly
Problem:
A customer's email occurs in 200 order rows.
The email changes.
All 200 rows must be updated.
If one row is missed,
two email values exist for one customer.
Deletion Anomaly
Problem:
A product appears only in one order row.
Deleting that order removes
the only stored product information.
Functional Dependencies
A functional dependency exists when the value of one attribute or attribute set determines the value of another attribute.
If attribute set \(X\) determines attribute set \(Y\), the dependency is written as:
\[ X \rightarrow Y \]
Customer Dependency
customer_id -> display_name
customer_id -> email_address
customer_id -> customer_status
For one customer ID, the model expects one corresponding display name, email address, and status at a given point in the modeled state.
Order-line Dependency
(order_id, line_number)
->
product_id
quantity
unit_price
The complete composite key determines the attributes of one order line.
Determinants
In the dependency \(X \rightarrow Y\), \(X\) is called the determinant.
Dependency:
product_id -> product_name
Determinant:
product_id
Dependent attribute:
product_name
Understanding determinants helps identify which facts belong together in one relation.
Normal Forms
Normal forms are progressive relational-design conditions. Each level addresses particular dependency and redundancy problems.
| Normal Form | Primary Concern |
|---|---|
| First Normal Form | Atomic values and no repeating groups |
| Second Normal Form | No partial dependency on part of a composite candidate key |
| Third Normal Form | No inappropriate transitive dependency through non-key attributes |
| Boyce-Codd Normal Form | Every determinant is a candidate key |
| Fourth Normal Form | No problematic independent multivalued dependencies |
| Fifth Normal Form | No problematic join dependencies requiring further lossless decomposition |
First Normal Form
A relation in First Normal Form, abbreviated as 1NF, stores one value belonging to the declared domain in each row-and-column intersection and avoids repeating groups.
First Normal Form Violation
| Customer ID | Name | Phone Numbers |
|---|---|---|
| CUS-42 | Example Customer | 1111111111, 2222222222, 3333333333 |
The telephone column contains a list rather than one value from its intended telephone-number domain.
First Normal Form Design
CREATE TABLE customers
(
customer_id BIGINT PRIMARY KEY,
display_name VARCHAR(200) NOT NULL
);
CREATE TABLE customer_phone_numbers
(
phone_number_id BIGINT PRIMARY KEY,
customer_id BIGINT NOT NULL,
phone_number VARCHAR(30) NOT NULL,
phone_type VARCHAR(20) NOT NULL,
CONSTRAINT fk_customer_phone_customer
FOREIGN KEY (customer_id)
REFERENCES customers (customer_id),
CONSTRAINT uq_customer_phone
UNIQUE
(
customer_id,
phone_number
)
);
Each telephone number is represented by a separate row associated with the customer.
Repeating Groups
product_1 VARCHAR(100),
quantity_1 INT,
product_2 VARCHAR(100),
quantity_2 INT,
product_3 VARCHAR(100),
quantity_3 INT
This design imposes an arbitrary maximum number of products and requires structural changes when another item is added.
CREATE TABLE order_lines
(
order_id BIGINT NOT NULL,
line_number INT NOT NULL,
product_id BIGINT NOT NULL,
quantity INT NOT NULL,
PRIMARY KEY
(
order_id,
line_number
)
);
Second Normal Form
A relation is in Second Normal Form, abbreviated as 2NF, when it is in 1NF and every non-key attribute depends on the complete candidate key rather than only part of a composite key.
Partial dependency is relevant when a candidate key contains more than one attribute.
Relation with Partial Dependencies
ORDER_PRODUCT
Composite key:
(order_id, product_id)
Attributes:
order_date
customer_id
product_name
current_price
quantity
Dependencies include:
order_id
->
order_date
customer_id
product_id
->
product_name
current_price
(order_id, product_id)
->
quantity
The order attributes depend only on order_id. Product
attributes depend only on product_id. Only quantity depends on
the entire composite key.
Decomposition into Second Normal Form
CREATE TABLE orders
(
order_id BIGINT PRIMARY KEY,
customer_id BIGINT NOT NULL,
order_date DATE NOT NULL
);
CREATE TABLE products
(
product_id BIGINT PRIMARY KEY,
product_name VARCHAR(200) NOT NULL,
current_price DECIMAL(18, 2) NOT NULL
);
CREATE TABLE order_lines
(
order_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
quantity INT NOT NULL,
PRIMARY KEY
(
order_id,
product_id
),
FOREIGN KEY (order_id)
REFERENCES orders (order_id),
FOREIGN KEY (product_id)
REFERENCES products (product_id)
);
Second Normal Form rule: When a table has a composite key, verify that every non-key attribute describes the complete key, not only one component of it.
Third Normal Form
A relation is in Third Normal Form, abbreviated as 3NF, when it satisfies 2NF and does not contain an inappropriate transitive dependency in which a non-key fact depends on another non-key fact.
Transitive Dependency Example
EMPLOYEE
employee_id
employee_name
department_id
department_name
department_location
Dependencies include:
employee_id
->
employee_name
department_id
department_id
->
department_name
department_location
Department name and location describe the department, not the employee.
They depend on the employee key indirectly through
department_id.
Third Normal Form Decomposition
CREATE TABLE departments
(
department_id BIGINT PRIMARY KEY,
department_name VARCHAR(200) NOT NULL,
department_location VARCHAR(200) NOT NULL
);
CREATE TABLE employees
(
employee_id BIGINT PRIMARY KEY,
employee_name VARCHAR(200) NOT NULL,
department_id BIGINT NOT NULL,
CONSTRAINT fk_employees_department
FOREIGN KEY (department_id)
REFERENCES departments (department_id)
);
Department facts are stored once in the department relation. Employees reference the appropriate department.
The Key, the Whole Key, and Nothing but the Key
A memorable informal summary of the early normal forms is:
Every non-key fact should depend on:
The key
The whole key
And nothing but the key
| Phrase | Related Concern |
|---|---|
| The key | The row has an identifiable meaning |
| The whole key | No partial dependency on part of a composite key |
| Nothing but the key | No inappropriate dependency through a non-key attribute |
This phrase is a learning aid rather than a complete formal definition. Formal normalization requires identifying all candidate keys and functional dependencies.
Boyce-Codd Normal Form
Boyce-Codd Normal Form, abbreviated as BCNF, is a stricter condition under which every determinant in a nontrivial functional dependency is a candidate key.
Example Scenario
TEACHING_ASSIGNMENT
student_id
course_id
instructor_id
Business rules:
A student can take a course.
Each instructor teaches one course.
A course can have several instructors.
Possible dependencies include:
(student_id, course_id)
->
instructor_id
instructor_id
->
course_id
The instructor determines the course, but the instructor alone is not necessarily a candidate key for the complete assignment relation. This kind of dependency can require further decomposition.
Possible Decomposition
CREATE TABLE instructor_courses
(
instructor_id BIGINT PRIMARY KEY,
course_id BIGINT NOT NULL,
FOREIGN KEY (course_id)
REFERENCES courses (course_id)
);
CREATE TABLE student_instructors
(
student_id BIGINT NOT NULL,
instructor_id BIGINT NOT NULL,
PRIMARY KEY
(
student_id,
instructor_id
),
FOREIGN KEY (student_id)
REFERENCES students (student_id),
FOREIGN KEY (instructor_id)
REFERENCES instructor_courses (instructor_id)
);
The exact decomposition depends on the complete business rules and candidate keys. Do not normalize from a simplified example without confirming the real dependencies.
Fourth Normal Form
Fourth Normal Form, abbreviated as 4NF, addresses independent multivalued dependencies stored in one relation.
Independent Multi-valued Facts
EMPLOYEE_SKILL_LANGUAGE
employee_id
skill
language
Employee 1 skills:
SQL
Python
Employee 1 languages:
English
Bengali
If skills and languages are independent, storing both in one relation produces all combinations:
Employee 1, SQL, English
Employee 1, SQL, Bengali
Employee 1, Python, English
Employee 1, Python, Bengali
The combinations imply no meaningful relationship between one skill and one language.
Fourth Normal Form Decomposition
CREATE TABLE employee_skills
(
employee_id BIGINT NOT NULL,
skill_id BIGINT NOT NULL,
PRIMARY KEY
(
employee_id,
skill_id
)
);
CREATE TABLE employee_languages
(
employee_id BIGINT NOT NULL,
language_code VARCHAR(20) NOT NULL,
PRIMARY KEY
(
employee_id,
language_code
)
);
Fifth Normal Form
Fifth Normal Form, abbreviated as 5NF, addresses complex join dependencies where a relation can be decomposed further into smaller relations and reconstructed through joins without introducing incorrect combinations.
These scenarios are less common in ordinary transactional application design. They require careful analysis of the complete business rules.
Conceptual example involving:
Supplier
Product
Region
Possible facts:
Supplier can supply Product.
Supplier operates in Region.
Product is approved in Region.
Question:
Does the combination of these binary facts
always imply a valid supplier-product-region fact?
The answer depends on the domain. A decomposition is correct only when its joins preserve exactly the valid original facts.
Lossless Decomposition
A decomposition is lossless when joining the decomposed relations reconstructs the original valid information without losing rows or creating false combinations.
Original relation
|
v
Decompose into smaller relations
|
v
Join smaller relations
|
v
Recover exactly the original facts
Decomposition rule: Splitting a table is useful only when the resulting relations can be joined correctly through preserved keys and dependencies.
Dependency Preservation
A decomposition preserves dependencies when important business dependencies can be enforced directly on the decomposed relations without requiring expensive joins for every validation.
Desired outcome:
- Decomposition is lossless.
- Important dependencies remain enforceable.
- Keys and constraints remain understandable.
- Queries can reconstruct required business views.
A theoretically more normalized decomposition can be less practical when critical constraints become difficult to enforce. The final design requires careful trade-off analysis.
Running Example: Unnormalized Order Data
ORDER_DATA
order_id
order_date
customer_id
customer_name
customer_email
customer_city
product_ids
product_names
quantities
unit_prices
salesperson_id
salesperson_name
This structure contains several problems:
- Product attributes contain lists
- Customer data repeats across orders
- Product data repeats across order lines
- Salesperson data repeats across orders
- Order-line facts are mixed with order-level facts
- Customer facts are mixed with transaction facts
Convert the Example to First Normal Form
Create one row for every order line and use atomic values.
ORDER_DATA_1NF
order_id
product_id
order_date
customer_id
customer_name
customer_email
customer_city
product_name
quantity
unit_price
salesperson_id
salesperson_name
A possible composite key is (order_id, product_id) when one
product appears at most once per order. If repeated products are allowed,
an order-line number is required.
The table now has atomic values, but customer, product, salesperson, and order facts still repeat.
Convert the Example to Second Normal Form
Remove facts that depend on only part of the composite order-line key.
ORDERS
order_id
order_date
customer_id
customer_name
customer_email
customer_city
salesperson_id
salesperson_name
PRODUCTS
product_id
product_name
ORDER_LINES
order_id
product_id
quantity
unit_price
Order-level attributes move to ORDERS. Product-level attributes
move to PRODUCTS. Order-line facts remain in
ORDER_LINES.
Convert the Example to Third Normal Form
Customer and salesperson details do not directly describe the order key. They describe customer and salesperson entities.
CUSTOMERS
customer_id
customer_name
customer_email
customer_city
SALESPEOPLE
salesperson_id
salesperson_name
ORDERS
order_id
order_date
customer_id
salesperson_id
PRODUCTS
product_id
product_name
ORDER_LINES
order_id
line_number
product_id
quantity
unit_price
Normalized SQL Schema
CREATE TABLE customers
(
customer_id BIGINT PRIMARY KEY,
customer_name VARCHAR(200) NOT NULL,
customer_email VARCHAR(254) NOT NULL,
customer_city VARCHAR(100) NOT NULL,
CONSTRAINT uq_customers_email
UNIQUE (customer_email)
);
CREATE TABLE salespeople
(
salesperson_id BIGINT PRIMARY KEY,
salesperson_name VARCHAR(200) NOT NULL
);
CREATE TABLE products
(
product_id BIGINT PRIMARY KEY,
product_name VARCHAR(200) NOT NULL
);
CREATE TABLE orders
(
order_id BIGINT PRIMARY KEY,
order_date DATE NOT NULL,
customer_id BIGINT NOT NULL,
salesperson_id BIGINT NOT NULL,
CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id)
REFERENCES customers (customer_id),
CONSTRAINT fk_orders_salesperson
FOREIGN KEY (salesperson_id)
REFERENCES salespeople (salesperson_id)
);
CREATE TABLE order_lines
(
order_id BIGINT NOT NULL,
line_number INT NOT NULL,
product_id BIGINT NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(18, 2) NOT NULL,
PRIMARY KEY
(
order_id,
line_number
),
CONSTRAINT fk_order_lines_order
FOREIGN KEY (order_id)
REFERENCES orders (order_id),
CONSTRAINT fk_order_lines_product
FOREIGN KEY (product_id)
REFERENCES products (product_id),
CONSTRAINT ck_order_lines_quantity
CHECK (quantity > 0),
CONSTRAINT ck_order_lines_price
CHECK (unit_price >= 0)
);
Historical Snapshots Are Not Always Denormalization
Repeating data is not automatically a normalization mistake. Sometimes two similar-looking values represent different facts.
products.current_price:
The product's current selling price.
order_lines.unit_price:
The price agreed when the order was placed.
The values can differ legitimately because they describe different points in time and different business facts.
Transaction Snapshot
CREATE TABLE order_lines
(
order_id BIGINT NOT NULL,
line_number INT NOT NULL,
product_id BIGINT NOT NULL,
product_description_snapshot VARCHAR(200) NOT NULL,
quantity INT NOT NULL,
unit_price_at_order DECIMAL(18, 2) NOT NULL,
PRIMARY KEY
(
order_id,
line_number
)
);
Historical rule: A transaction-time snapshot is a separate fact when the business requires the historical record to remain unchanged after the current master data changes.
What Is Denormalization?
Denormalization deliberately stores selected duplicated, aggregated, or prejoined data to support a defined access pattern.
Denormalization can reduce joins or repeated calculations for high-volume reads, but it introduces additional write and consistency responsibilities.
Normalization vs Denormalization
| Area | Normalization | Denormalization |
|---|---|---|
| Primary goal | Integrity and reduction of unnecessary redundancy | Performance or read-path simplification |
| Data repetition | Minimized where facts have one source | Introduced deliberately |
| Read queries | Can require joins | Can avoid selected joins or calculations |
| Writes | Update the authoritative fact | Can require updating several representations |
| Consistency | Usually easier to preserve | Requires synchronization and reconciliation |
| Storage | Less repeated data | Additional storage for copies or summaries |
| Recommended starting point | Default transactional design | Measured and justified optimization |
When to Consider Denormalization
Denormalization can be considered when:
- A measured critical read query remains expensive after appropriate indexing
- A frequently requested view requires many repeated joins
- An aggregate is calculated continuously from a large dataset
- A reporting workload needs a read-optimized model
- A distributed read path cannot synchronously join data across services
- A historical snapshot must remain stable
- A precomputed projection improves a defined user workflow
Denormalization should not be the first response to a slow query. First inspect the query plan, indexes, selected columns, filters, pagination, and database configuration.
When Not to Denormalize
Avoid denormalization when:
- No performance bottleneck has been measured
- The duplicated values change frequently
- No synchronization owner exists
- Strong immediate consistency is mandatory
- The normalized query can be fixed with an appropriate index
- The team cannot monitor and repair data drift
- The additional write complexity exceeds the read benefit
Precomputed Aggregate
An order total can be calculated from its lines whenever the order is retrieved.
SELECT
order_id,
SUM(quantity * unit_price)
AS calculated_total
FROM order_lines
WHERE
order_id = :order_id
GROUP BY
order_id;
A denormalized design can store the total on the order row:
ALTER TABLE orders
ADD COLUMN total_amount DECIMAL(18, 2) NOT NULL;
The stored total must be updated whenever a price, quantity, discount, tax, fee, or line membership changes.
Transactional Maintenance
Begin transaction
1. Insert or update order line.
2. Recalculate order total.
3. Update orders.total_amount.
4. Validate order invariants.
Commit transaction
Duplicated Display Value
A frequently displayed customer name can be copied into an order read model to avoid a repeated join.
CREATE TABLE order_read_model
(
order_id BIGINT PRIMARY KEY,
order_number VARCHAR(30) NOT NULL,
customer_id BIGINT NOT NULL,
customer_display_name VARCHAR(200) NOT NULL,
order_status VARCHAR(30) NOT NULL,
total_amount DECIMAL(18, 2) NOT NULL,
ordered_at TIMESTAMP NOT NULL
);
The design must answer:
- Is the copied name a historical snapshot or a current projection?
- Which table is authoritative?
- When is the copy updated?
- What temporary staleness is acceptable?
- How is drift detected and repaired?
Materialized Read Models
A read model stores data shaped for a specific query or screen.
Normalized write model:
Customers
Orders
Order Lines
Payments
Shipments
Read model:
Order Summary
- Order ID
- Customer display name
- Current order status
- Payment status
- Shipment status
- Total amount
The normalized model remains the authoritative transactional store. The read model is rebuilt or updated from authoritative changes.
Materialized Views
Some database systems support materialized views that store query results physically and refresh them according to a defined policy.
CREATE MATERIALIZED VIEW customer_order_summary AS
SELECT
c.customer_id,
c.customer_name,
COUNT(o.order_id) AS order_count,
SUM(o.total_amount) AS lifetime_order_value
FROM customers AS c
LEFT JOIN orders AS o
ON o.customer_id = c.customer_id
GROUP BY
c.customer_id,
c.customer_name;
Syntax and refresh capabilities differ between database platforms. The refresh frequency determines how current the stored results are.
Synchronization Strategies
| Strategy | Behaviour | Consideration |
|---|---|---|
| Same database transaction | Updates normalized and denormalized values atomically | Works when both are inside one transaction boundary |
| Database trigger | Updates derived data after database changes | Logic can become hidden from application code |
| Background job | Refreshes copies periodically | Introduces bounded staleness |
| Event-driven projection | Updates read models from published changes | Requires reliable event processing and replay |
| Scheduled rebuild | Recreates the derived model from source data | Can consume significant resources |
Eventual Consistency
An asynchronously updated read model can temporarily differ from the authoritative normalized data.
Authoritative customer name changes
|
v
Transaction commits
|
v
Change event is published
|
v
Projection consumer processes event
|
v
Order read model is updated
Between the source commit and projection update, readers can observe the old value.
The contract should define:
- Which source is authoritative
- Expected staleness
- Update ordering
- Retry behaviour
- Duplicate-event handling
- Rebuild and reconciliation procedures
Detecting Data Drift
Data drift occurs when a denormalized representation no longer agrees with its authoritative source.
SELECT
orm.order_id,
orm.customer_display_name
AS copied_name,
c.customer_name
AS authoritative_name
FROM order_read_model AS orm
INNER JOIN orders AS o
ON o.order_id = orm.order_id
INNER JOIN customers AS c
ON c.customer_id = o.customer_id
WHERE
orm.customer_display_name
<> c.customer_name;
A reconciliation job can measure differences and repair them according to the read model's synchronization policy.
Read and Write Amplification
| Concept | Meaning |
|---|---|
| Read amplification | One logical read requires several lookups, joins, or service calls |
| Write amplification | One logical change requires several stored representations to be updated |
Normalized designs can increase read joins. Denormalized designs can increase write amplification. The correct design depends on workload, consistency, maintainability, and failure requirements.
Transactional vs Analytical Models
| Workload | Typical Characteristics | Common Modeling Direction |
|---|---|---|
| Transactional | Frequent inserts and updates, point lookups, strong integrity | Normalized operational model |
| Analytical | Large scans, aggregations, historical reporting | Read-optimized dimensional or denormalized model |
| Operational reporting | Current summaries across several entities | Indexed query, materialized view, or projection |
One application can use a normalized transactional model and a separate denormalized analytical model rather than forcing one schema to optimize every workload.
Star-schema Example
Analytical systems commonly organize measurable events in a fact table and descriptive information in dimension tables.
FACT_SALES
|
+-- DATE_DIMENSION
|
+-- CUSTOMER_DIMENSION
|
+-- PRODUCT_DIMENSION
|
+-- STORE_DIMENSION
Dimension tables can intentionally contain descriptive attributes optimized for analytical queries. This is a different workload and design objective from normalized transactional processing.
Join Cost Is Not Automatically a Problem
A normalized query containing joins is not necessarily slow. Relational databases are designed to join tables using indexes and optimized execution plans.
SELECT
o.order_number,
c.customer_name,
o.order_status,
o.total_amount
FROM orders AS o
INNER JOIN customers AS c
ON c.customer_id = o.customer_id
WHERE
o.order_id = :order_id;
Before denormalizing, verify:
- The foreign-key columns are indexed appropriately
- The query predicates are selective
- The query retrieves only required columns
- The execution plan uses appropriate access paths
- Statistics are current
- The request is paginated where applicable
- The actual latency is a user-facing bottleneck
Integrity after Denormalization
Every denormalized value needs a documented integrity contract.
| Question | Required Decision |
|---|---|
| What is authoritative? | Identify the source of truth |
| Who updates the copy? | Assign one synchronization owner |
| When is it updated? | Define synchronous or asynchronous propagation |
| How stale may it be? | Define an acceptable consistency window |
| How are failures retried? | Define durable and idempotent processing |
| How is drift detected? | Implement reconciliation and metrics |
| How is it rebuilt? | Maintain a repeatable rebuild procedure |
Security and Denormalized Data
Duplicating data expands the number of locations that require access control, encryption, retention, auditing, and deletion handling.
Review whether denormalization duplicates:
- Personal information
- Payment information
- Authentication or authorization data
- Confidential business data
- Tenant identifiers
- Deletion-controlled information
Data-minimization rule: Do not copy sensitive data into a read model merely because it is convenient. Include only the fields required for the documented use case.
Observability
Useful normalization and denormalization metrics include:
- Duplicate-value conflict count
- Foreign-key violation count
- Read-model update delay
- Projection processing failures
- Source-to-copy drift count
- Materialized-view refresh duration
- Normalized query latency
- Denormalized query latency
- Write amplification
- Reconciliation repair count
- Read-model rebuild duration
- Storage consumed by duplicated data
Normalization Workflow
- Identify the relation's intended meaning.
- Identify candidate keys.
- List functional and multivalued dependencies.
- Identify repeating groups and non-atomic attributes.
- Move independent repeated values into related rows.
- Identify partial dependencies on composite-key components.
- Move partial dependencies into their own relations.
- Identify transitive dependencies through non-key attributes.
- Move separate entity facts into their own relations.
- Verify lossless decomposition.
- Verify dependency preservation.
- Add primary, foreign, unique, and check constraints.
- Test realistic inserts, updates, deletes, and joins.
Denormalization Workflow
- Begin with a correct normalized model.
- Measure the production or representative workload.
- Identify one verified read bottleneck.
- Inspect indexes, query plans, filters, and pagination first.
- Define the exact duplicated or precomputed value.
- Identify the authoritative source.
- Choose a synchronization mechanism.
- Define acceptable staleness.
- Add reconciliation and rebuild procedures.
- Protect duplicated sensitive data.
- Load-test read and write paths.
- Document the reason and ownership.
Common Normalization and Denormalization Mistakes
Storing Lists in One Column
Comma-separated or encoded lists are difficult to validate, join, constrain, and index relationally.
Confusing Atomic with Physically Indivisible
Atomicity depends on the modeled domain and required operations. An address can be one value in one model and several attributes in another.
Ignoring Candidate Keys
Normalization must consider all relevant candidate keys, not only the selected primary key.
Leaving Partial Dependencies in Junction Tables
Attributes describing only one side of a composite key belong with that entity.
Leaving Transitive Dependencies in Entity Tables
Department, region, category, or supplier facts should not be repeated in every related entity row.
Splitting Tables without Lossless Joins
An incorrect decomposition can lose original facts or create false combinations when the tables are joined.
Over-normalizing without Workload Awareness
Excessive fragmentation can make important constraints and access paths unnecessarily complex.
Denormalizing before Measuring
Additional copies and synchronization logic should not be introduced without evidence that they solve a significant problem.
Using Denormalization instead of Indexing
A slow normalized query might need an appropriate index or improved query rather than duplicated data.
Not Defining the Source of Truth
Two editable copies of one fact can diverge when neither is declared authoritative.
Ignoring Projection Failures
Asynchronous read models require retries, idempotency, monitoring, replay, and reconciliation.
Calling Historical Snapshots Redundancy Errors
A current product price and the price recorded on an earlier order are separate business facts.
Recommended Test Cases
| Test | Expected Evidence |
|---|---|
| Atomic values | One relational value is stored in each attribute position |
| Repeating groups | Repeated items are represented as related rows |
| Partial dependency | Every non-key order-line fact depends on the complete line key |
| Transitive dependency | Department facts are stored separately from employee facts |
| Lossless join | Joining decomposed tables reconstructs valid original facts |
| Insertion anomaly | A product can be stored before it appears in an order |
| Update anomaly | A customer email is changed in one authoritative location |
| Deletion anomaly | Deleting an order does not remove the customer or product |
| Historical snapshot | Existing order prices remain unchanged after the current product price changes |
| Denormalized update | The copied value follows the documented synchronization policy |
| Projection failure | The update is retried or repaired without silent data loss |
| Reconciliation | Drift between authoritative and copied values is detected |
Best Practices
Recommended Practices
- Begin with entities, relationships, candidate keys, and business dependencies.
- Keep one authoritative location for each current fact.
- Use related rows instead of lists or repeating columns.
- Remove partial dependencies from composite-key relations.
- Remove inappropriate transitive dependencies.
- Verify all candidate keys, not only the primary key.
- Require lossless decomposition.
- Preserve important dependencies where practical.
- Implement keys, foreign keys, unique constraints, and checks.
- Distinguish current master data from historical transaction snapshots.
- Use normalization as the default for transactional models.
- Measure query performance before denormalizing.
- Optimize indexes, queries, and pagination before adding copies.
- Define one authoritative source for every duplicated value.
- Document synchronization and acceptable staleness.
- Make asynchronous projection updates idempotent and replayable.
- Implement drift detection and reconciliation.
- Protect every duplicated copy of sensitive information.
- Document the reason and owner for each denormalization.
- Retest integrity and performance after schema changes.
Practice Exercise
Normalize an online learning-platform enrollment table and design one justified denormalized read model.
Starting Relation
COURSE_ACTIVITY
learner_id
learner_name
learner_email
course_id
course_title
category_id
category_name
instructor_ids
instructor_names
chapter_id
chapter_title
lesson_id
lesson_title
enrollment_date
completion_percentage
latest_quiz_score
Requirements
- Identify candidate keys and functional dependencies.
- Remove repeating instructor values.
- Create separate learner, course, category, instructor, chapter, and lesson entities.
- Create an enrollment associative entity.
- Create a course-instructor junction table.
- Define chapter-to-course and lesson-to-chapter relationships.
- Create a quiz-attempt table that preserves attempt history.
- Verify First, Second, and Third Normal Forms.
- Define primary, foreign, unique, and check constraints.
- Write a query returning learner course progress.
- Measure the query using representative data.
- Create a course-progress read model only if justified.
- Define the normalized source of truth.
- Define projection synchronization and acceptable staleness.
- Create a reconciliation query for the read model.
Suggested Normalized Tables
| Table | Purpose | Primary Relationship |
|---|---|---|
| Learners | Stores learner facts | One learner has many enrollments |
| Categories | Stores course-category facts | One category has many courses |
| Courses | Stores course facts | One course has many chapters and enrollments |
| Instructors | Stores instructor facts | Many instructors can teach many courses |
| Course Instructors | Associates courses and instructors | Many-to-many junction |
| Chapters | Stores chapter facts | Each chapter belongs to one course |
| Lessons | Stores lesson facts | Each lesson belongs to one chapter |
| Enrollments | Stores learner-course relationship facts | Many-to-many associative entity |
| Quiz Attempts | Stores immutable attempt history | One learner can make several attempts |
Frequently Asked Questions
What is database normalization?
Normalization is the process of organizing relational data to reduce unnecessary redundancy and prevent modification anomalies.
What is First Normal Form?
First Normal Form requires relational values without repeating groups, such as several telephone numbers stored in one field.
What is Second Normal Form?
Second Normal Form requires 1NF and removes dependencies in which a non-key attribute depends on only part of a composite candidate key.
What is Third Normal Form?
Third Normal Form requires 2NF and removes inappropriate transitive dependencies through non-key attributes.
What is Boyce-Codd Normal Form?
Boyce-Codd Normal Form requires every determinant in a nontrivial functional dependency to be a candidate key.
What is an update anomaly?
An update anomaly occurs when one fact exists in several locations and must be updated consistently in all of them.
What is lossless decomposition?
A decomposition is lossless when joining the resulting relations reconstructs exactly the valid original facts.
What is denormalization?
Denormalization deliberately stores duplicated, aggregated, or prejoined data to optimize a defined access pattern.
Does normalization always improve performance?
Normalization primarily improves integrity and maintainability. Read performance depends on queries, indexes, data volume, execution plans, and workload.
Should every database be fully normalized?
Transactional schemas commonly target a well-normalized design, but the appropriate final form depends on dependencies, constraints, workloads, and maintainability requirements.
Is a historical snapshot denormalization?
Not necessarily. A transaction-time value can represent a distinct historical fact rather than an unnecessary copy of current master data.
What comes after normalization and denormalization?
The next topic is keys and constraints, followed by ACID and transactions.
Key Takeaway
Normalization organizes relational facts according to keys and dependencies. First Normal Form removes repeating groups, Second Normal Form removes partial dependencies, Third Normal Form removes inappropriate transitive dependencies, and BCNF requires every determinant to be a candidate key. Decomposition should be lossless and preserve important dependencies. Denormalization is a deliberate read optimization, not a shortcut for poor modeling. Introduce it only after measurement, identify one authoritative source, define synchronization and acceptable staleness, detect drift, protect duplicated sensitive data, and retain a reliable normalized model from which projections can be repaired or rebuilt.