207 lines
8.1 KiB
SQL
207 lines
8.1 KiB
SQL
-- Sample E-commerce Database Schema
|
|
-- Demonstrates various normalization levels and common patterns
|
|
|
|
-- Users table - well normalized
|
|
CREATE TABLE users (
|
|
id INTEGER PRIMARY KEY,
|
|
email VARCHAR(255) NOT NULL UNIQUE,
|
|
username VARCHAR(50) NOT NULL UNIQUE,
|
|
password_hash VARCHAR(255) NOT NULL,
|
|
first_name VARCHAR(100),
|
|
last_name VARCHAR(100),
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
status VARCHAR(20) DEFAULT 'active'
|
|
);
|
|
|
|
-- Categories table - hierarchical structure
|
|
CREATE TABLE categories (
|
|
id INTEGER PRIMARY KEY,
|
|
name VARCHAR(100) NOT NULL,
|
|
slug VARCHAR(100) NOT NULL UNIQUE,
|
|
parent_id INTEGER REFERENCES categories(id),
|
|
description TEXT,
|
|
is_active BOOLEAN DEFAULT true,
|
|
sort_order INTEGER DEFAULT 0,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Products table - potential normalization issues
|
|
CREATE TABLE products (
|
|
id INTEGER PRIMARY KEY,
|
|
name VARCHAR(255) NOT NULL,
|
|
sku VARCHAR(50) NOT NULL UNIQUE,
|
|
description TEXT,
|
|
price DECIMAL(10,2) NOT NULL,
|
|
cost DECIMAL(10,2),
|
|
weight DECIMAL(8,2),
|
|
dimensions VARCHAR(50), -- Potential 1NF violation: "10x5x3 inches"
|
|
category_id INTEGER REFERENCES categories(id),
|
|
category_name VARCHAR(100), -- Redundant with categories.name (3NF violation)
|
|
brand VARCHAR(100), -- Should be normalized to separate brands table
|
|
tags VARCHAR(500), -- Potential 1NF violation: comma-separated tags
|
|
inventory_count INTEGER DEFAULT 0,
|
|
reorder_point INTEGER DEFAULT 10,
|
|
supplier_name VARCHAR(100), -- Should be normalized
|
|
supplier_contact VARCHAR(255), -- Should be normalized
|
|
is_active BOOLEAN DEFAULT true,
|
|
featured BOOLEAN DEFAULT false,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Addresses table - good normalization
|
|
CREATE TABLE addresses (
|
|
id INTEGER PRIMARY KEY,
|
|
user_id INTEGER REFERENCES users(id),
|
|
address_type VARCHAR(20) DEFAULT 'shipping', -- 'shipping', 'billing'
|
|
street_address VARCHAR(255) NOT NULL,
|
|
street_address_2 VARCHAR(255),
|
|
city VARCHAR(100) NOT NULL,
|
|
state VARCHAR(50) NOT NULL,
|
|
postal_code VARCHAR(20) NOT NULL,
|
|
country VARCHAR(50) NOT NULL DEFAULT 'US',
|
|
is_default BOOLEAN DEFAULT false,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Orders table - mixed normalization issues
|
|
CREATE TABLE orders (
|
|
id INTEGER PRIMARY KEY,
|
|
order_number VARCHAR(50) NOT NULL UNIQUE,
|
|
user_id INTEGER REFERENCES users(id),
|
|
user_email VARCHAR(255), -- Denormalized for performance/historical reasons
|
|
user_name VARCHAR(200), -- Denormalized for performance/historical reasons
|
|
status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
|
total_amount DECIMAL(10,2) NOT NULL,
|
|
tax_amount DECIMAL(10,2) NOT NULL,
|
|
shipping_amount DECIMAL(10,2) NOT NULL,
|
|
discount_amount DECIMAL(10,2) DEFAULT 0,
|
|
payment_method VARCHAR(50), -- Should be normalized to payment_methods
|
|
payment_status VARCHAR(50) DEFAULT 'pending',
|
|
shipping_address_id INTEGER REFERENCES addresses(id),
|
|
billing_address_id INTEGER REFERENCES addresses(id),
|
|
-- Denormalized shipping address for historical preservation
|
|
shipping_street VARCHAR(255),
|
|
shipping_city VARCHAR(100),
|
|
shipping_state VARCHAR(50),
|
|
shipping_postal_code VARCHAR(20),
|
|
shipping_country VARCHAR(50),
|
|
notes TEXT,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
shipped_at TIMESTAMP,
|
|
delivered_at TIMESTAMP
|
|
);
|
|
|
|
-- Order items table - properly normalized
|
|
CREATE TABLE order_items (
|
|
id INTEGER PRIMARY KEY,
|
|
order_id INTEGER REFERENCES orders(id),
|
|
product_id INTEGER REFERENCES products(id),
|
|
product_name VARCHAR(255), -- Denormalized for historical reasons
|
|
product_sku VARCHAR(50), -- Denormalized for historical reasons
|
|
quantity INTEGER NOT NULL,
|
|
unit_price DECIMAL(10,2) NOT NULL,
|
|
total_price DECIMAL(10,2) NOT NULL, -- Calculated field (could be computed)
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Shopping cart table - session-based data
|
|
CREATE TABLE shopping_cart (
|
|
id INTEGER PRIMARY KEY,
|
|
user_id INTEGER REFERENCES users(id),
|
|
session_id VARCHAR(255), -- For anonymous users
|
|
product_id INTEGER REFERENCES products(id),
|
|
quantity INTEGER NOT NULL DEFAULT 1,
|
|
added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE(user_id, product_id),
|
|
UNIQUE(session_id, product_id)
|
|
);
|
|
|
|
-- Product reviews - user-generated content
|
|
CREATE TABLE product_reviews (
|
|
id INTEGER PRIMARY KEY,
|
|
product_id INTEGER REFERENCES products(id),
|
|
user_id INTEGER REFERENCES users(id),
|
|
rating INTEGER NOT NULL CHECK (rating BETWEEN 1 AND 5),
|
|
title VARCHAR(200),
|
|
review_text TEXT,
|
|
verified_purchase BOOLEAN DEFAULT false,
|
|
helpful_count INTEGER DEFAULT 0,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE(product_id, user_id) -- One review per user per product
|
|
);
|
|
|
|
-- Coupons table - promotional data
|
|
CREATE TABLE coupons (
|
|
id INTEGER PRIMARY KEY,
|
|
code VARCHAR(50) NOT NULL UNIQUE,
|
|
description VARCHAR(255),
|
|
discount_type VARCHAR(20) NOT NULL, -- 'percentage', 'fixed_amount'
|
|
discount_value DECIMAL(8,2) NOT NULL,
|
|
minimum_amount DECIMAL(10,2),
|
|
maximum_discount DECIMAL(10,2),
|
|
usage_limit INTEGER,
|
|
usage_count INTEGER DEFAULT 0,
|
|
valid_from TIMESTAMP NOT NULL,
|
|
valid_until TIMESTAMP NOT NULL,
|
|
is_active BOOLEAN DEFAULT true,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Audit log table - for tracking changes
|
|
CREATE TABLE audit_log (
|
|
id INTEGER PRIMARY KEY,
|
|
table_name VARCHAR(50) NOT NULL,
|
|
record_id INTEGER NOT NULL,
|
|
action VARCHAR(20) NOT NULL, -- 'INSERT', 'UPDATE', 'DELETE'
|
|
old_values TEXT, -- JSON format
|
|
new_values TEXT, -- JSON format
|
|
user_id INTEGER REFERENCES users(id),
|
|
ip_address VARCHAR(45),
|
|
user_agent TEXT,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Problematic table - multiple normalization violations
|
|
CREATE TABLE user_preferences (
|
|
user_id INTEGER PRIMARY KEY REFERENCES users(id),
|
|
preferred_categories VARCHAR(500), -- CSV list - 1NF violation
|
|
email_notifications VARCHAR(255), -- "daily,weekly,promotions" - 1NF violation
|
|
user_name VARCHAR(200), -- Redundant with users table - 3NF violation
|
|
user_email VARCHAR(255), -- Redundant with users table - 3NF violation
|
|
theme VARCHAR(50) DEFAULT 'light',
|
|
language VARCHAR(10) DEFAULT 'en',
|
|
timezone VARCHAR(50) DEFAULT 'UTC',
|
|
currency VARCHAR(3) DEFAULT 'USD',
|
|
date_format VARCHAR(20) DEFAULT 'YYYY-MM-DD',
|
|
newsletter_subscribed BOOLEAN DEFAULT true,
|
|
sms_notifications BOOLEAN DEFAULT false,
|
|
push_notifications BOOLEAN DEFAULT true,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Create some basic indexes (some missing, some redundant for demonstration)
|
|
CREATE INDEX idx_users_email ON users (email);
|
|
CREATE INDEX idx_users_username ON users (username); -- Redundant due to UNIQUE constraint
|
|
CREATE INDEX idx_products_category ON products (category_id);
|
|
CREATE INDEX idx_products_brand ON products (brand);
|
|
CREATE INDEX idx_products_sku ON products (sku); -- Redundant due to UNIQUE constraint
|
|
CREATE INDEX idx_orders_user ON orders (user_id);
|
|
CREATE INDEX idx_orders_status ON orders (status);
|
|
CREATE INDEX idx_orders_created ON orders (created_at);
|
|
CREATE INDEX idx_order_items_order ON order_items (order_id);
|
|
CREATE INDEX idx_order_items_product ON order_items (product_id);
|
|
-- Missing index on addresses.user_id
|
|
-- Missing composite index on orders (user_id, status)
|
|
-- Missing index on product_reviews.product_id
|
|
|
|
-- Constraints that should exist but are missing
|
|
-- ALTER TABLE products ADD CONSTRAINT chk_price_positive CHECK (price > 0);
|
|
-- ALTER TABLE products ADD CONSTRAINT chk_inventory_non_negative CHECK (inventory_count >= 0);
|
|
-- ALTER TABLE order_items ADD CONSTRAINT chk_quantity_positive CHECK (quantity > 0);
|
|
-- ALTER TABLE orders ADD CONSTRAINT chk_total_positive CHECK (total_amount > 0); |