Normalizing the Store Schema: A Beginner’s Guide to 1NF & 2NF
Learn how to apply normalization principles to your PostgreSQL schema. Eliminate redundancy and improve data integrity by splitting orders into headers and items.

Previously in this course, we covered implementing foreign keys to link tables and using CHECK constraints to enforce business rules. In this lesson, we take those concepts further by applying normalization to our store schema.
Normalization is the process of organizing a database to reduce data redundancy and improve data integrity. Without it, your tables become cluttered, prone to update anomalies, and inefficient to query.
What is Normalization?
Normalization is a series of "forms" (rules). For a beginner, the most critical are the first two:
- First Normal Form (1NF): Eliminate repeating groups and ensure each column contains atomic values.
- Second Normal Form (2NF): Ensure all non-key columns depend on the entire primary key.
Think of it as tidying up your digital workspace. By separating concerns, we ensure that a change in one piece of data doesn't require us to update multiple rows in different places.
Applying 1NF: Atomic Values
A table is in 1NF if every cell contains only one value and there are no "repeating groups."
Imagine an orders table where you store items as a comma-separated string: product_ids = '101, 105, 202'. This is a classic 1NF violation. You cannot easily join this to a products table or calculate the sum of prices. To achieve 1NF, you must break these out into individual rows.
Applying 2NF: Separating Order Headers from Items
2NF requires that all data in a table relates directly to the primary key. If you have an orders table that includes customer_name, order_date, product_id, and quantity, you have a problem.
If a customer buys three different products, you end up repeating the customer_name and order_date three times. This is redundant and dangerous—what if you mistype the name on one of those rows?
We solve this by splitting the data into two tables:
orders(The Header): Stores information about the transaction itself (who, when).order_items(The Details): Stores the line items belonging to that order (what, how many, at what price).
Worked Example: Refactoring the Schema
Let’s transform our draft schema into a normalized one.
SQL-- 1. The Header Table CREATE TABLE orders ( order_id SERIAL PRIMARY KEY, customer_id INT REFERENCES customers(customer_id), order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 2. The Items Table CREATE TABLE order_items ( item_id SERIAL PRIMARY KEY, order_id INT REFERENCES orders(order_id), product_id INT REFERENCES products(product_id), quantity INT CHECK (quantity > 0), unit_price NUMERIC(10, 2) );
By separating these, the orders table stays lean. If you need to know what was in an order, you join order_items using the order_id as we will explore in upcoming lessons.
Hands-on Exercise
To advance our running project, I want you to perform a mental (or scratchpad) refactor of a hypothetical shipping_addresses table.
If you currently have a customers table that includes shipping_address, shipping_city, and shipping_zip, consider this: what happens if a customer has multiple addresses?
- Task: Sketch out how you would create a separate
addressestable that links back tocustomersusing a foreign key. How does this prevent you from having to copy-paste the address every time a customer places a new order?
Common Pitfalls
- Over-normalizing: Don't split tables so much that you need a 10-table join just to see a basic order summary. Normalization is a balance; see Data Modeling for Scalable Systems for more on when to stop.
- Forgetting Data Types: When splitting tables, ensure your Foreign Key column (e.g.,
order_idinorder_items) matches the data type of the Primary Key in the source table exactly. - Ignoring Constraints: Normalization doesn't replace constraints. Always keep your
NOT NULLandCHECKconstraints on the new, smaller tables.
FAQ
Q: Does normalization make queries slower? A: Usually, it makes them faster because the database scans less redundant data. However, for massive datasets, you might occasionally "denormalize" (combine tables) for specific read-heavy reports.
Q: Is 3NF necessary for beginners? A: 3NF (removing transitive dependencies) is important, but focus on 1NF and 2NF first. Get comfortable with header-item structures before worrying about more complex dependencies.
Recap
Normalization isn't just theory; it’s the foundation of a robust database design. By applying 1NF (atomic values) and 2NF (separating order headers from line items), you've ensured that your store database is efficient, scalable, and easy to maintain. We’ve successfully moved from a flat, messy data structure to a professional, relational design.
Up next, we will learn how to populate these new tables using INSERT statements.
Work with me

Laravel SaaS MVP & Multi-Tenant App Development
Launch your SaaS MVP on Laravel — multi-tenant, subscription-ready, and built by the engineer behind a platform serving 10,000+ paying users.

Next.js E-commerce Store Development
Turn your Facebook page or small shop into a real online store — fast, mobile-first, and built to sell. Own your storefront, not just a social page.


