sql 192 lines · 2 tabs

Database normalization and schema design patterns

Maria Garcia Feb 2026
2 tabs
-- Unnormalized (0NF): Repeating groups
CREATE TABLE orders_bad (
  order_id INT,
  customer_name VARCHAR(100),
  customer_email VARCHAR(100),
  product1 VARCHAR(100),
  product2 VARCHAR(100),
  product3 VARCHAR(100)
);
-- Problem: Fixed number of products, redundant customer data

-- First Normal Form (1NF): Atomic values
CREATE TABLE orders_1nf (
  order_id INT PRIMARY KEY,
  customer_name VARCHAR(100),
  customer_email VARCHAR(100),
  product VARCHAR(100)
);
-- Better: One product per row, but still redundant customer data

-- Second Normal Form (2NF): No partial dependencies
CREATE TABLE orders (
  order_id SERIAL PRIMARY KEY,
  customer_id INT REFERENCES customers(id),
  order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE customers (
  id SERIAL PRIMARY KEY,
  name VARCHAR(100),
  email VARCHAR(100) UNIQUE
);

CREATE TABLE order_items (
  id SERIAL PRIMARY KEY,
  order_id INT REFERENCES orders(order_id),
  product_id INT REFERENCES products(id),
  quantity INT,
  price DECIMAL(10,2)
);
-- Better: Customer data in separate table

-- Third Normal Form (3NF): No transitive dependencies
CREATE TABLE products (
  id SERIAL PRIMARY KEY,
  name VARCHAR(200),
  category_id INT REFERENCES categories(id),
  -- Don't store category_name here (transitive dependency)
  price DECIMAL(10,2)
);

CREATE TABLE categories (
  id SERIAL PRIMARY KEY,
  name VARCHAR(100),
  description TEXT
);
-- Proper 3NF: Category details in separate table

-- Denormalization for performance
CREATE TABLE products_denormalized (
  id SERIAL PRIMARY KEY,
  name VARCHAR(200),
  category_id INT REFERENCES categories(id),
  category_name VARCHAR(100),  -- Denormalized!
  price DECIMAL(10,2),
  total_orders INT DEFAULT 0,   -- Cached aggregate
  last_ordered_at TIMESTAMP
);
-- Trade: Faster reads, more storage, must maintain consistency

-- Maintain denormalized data with triggers
CREATE OR REPLACE FUNCTION update_product_stats()
RETURNS TRIGGER AS $$
BEGIN
  UPDATE products_denormalized
  SET total_orders = total_orders + 1,
      last_ordered_at = CURRENT_TIMESTAMP
  WHERE id = NEW.product_id;
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_update_product_stats
AFTER INSERT ON order_items
FOR EACH ROW
EXECUTE FUNCTION update_product_stats();
2 files · sql Explain with highlit

Normalization eliminates redundancy and anomalies. 1NF requires atomic values—no arrays in columns. 2NF eliminates partial dependencies—all non-key columns depend on entire primary key. 3NF removes transitive dependencies—non-key columns don't depend on other non-key columns. I normalize to 3NF for most applications. Denormalization trades redundancy for performance—materialized aggregates, caching. Star schema suits data warehouses—fact tables with dimension references. Snowflake schema normalizes dimensions. Understanding when to denormalize is key—read-heavy vs write-heavy workloads. EAV (Entity-Attribute-Value) is usually anti-pattern—use JSONB instead. Proper schema design prevents future pain. Balance normalization with query performance requirements.


Related snips

python
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
from sklearn.linear_model import LogisticRegression

standard_pipeline = Pipeline([
    ('scaler', StandardScaler()),

Scaling and normalization choices for different model families

feature-scaling normalization machine-learning
by Dr. Elena Vasquez 1 tab
ruby
# Vulnerable: user input is concatenated directly into SQL.
email = params[:email]
password = params[:password]

sql = "SELECT * FROM users WHERE email = '#{email}' AND password_hash = '#{password}'"
user = ActiveRecord::Base.connection.execute(sql).first

SQL injection prevention with unsafe and safe query patterns

sql-injection owasp database
by Kai Nakamura 3 tabs
sql
-- EXPLAIN ANALYZE (actual execution statistics)
EXPLAIN ANALYZE
SELECT u.username, COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at >= '2024-01-01'

Advanced query optimization techniques

database optimization query-performance
by Maria Garcia 2 tabs
ruby
module EmailNormalization
  extend ActiveSupport::Concern

  included do
    attr_accessor :soft_warnings

Soft Validation: Normalize + Validate Email

rails activerecord validations
by codesnips 4 tabs
python
from django.db.models import Count, Avg, Sum, Q, F
from django.views.generic import TemplateView
from products.models import Product, Order, OrderItem


class DashboardView(TemplateView):

Django aggregation with annotate for statistics

django python database
by Priya Sharma 1 tab
ruby
class ReportQuery
  SQL = <<~SQL.freeze
    SELECT date_trunc('day', events.created_at) AS day,
           count(*) AS total,
           count(*) FILTER (WHERE events.kind = 'purchase') AS purchases
    FROM events

Safe Raw SQL with exec_query + Binds

rails activerecord sql
by codesnips 2 tabs

Share this code

Here's the card — post it anywhere.

Database normalization and schema design patterns — share card
Link copied