Entity-Relationship Diagrams
Visualize your database design with entity-relationship diagrams for clearer communication and better architecture.
- Entities & Attributes β Define the core objects and their properties
- Relationships β Map connections between entities with cardinality rules
- Visual Communication β Bridge the gap between developers, DBAs, and stakeholders A picture is worth a thousand rows β ER diagrams make database design intuitive.
What is an ER Diagram?
DfEntity-Relationship Diagram (ERD)
An Entity-Relationship Diagram is a visual representation of a database's logical structure. It shows entities (tables), their attributes (columns), and the relationships (foreign keys) between them using standardized notation.
ER diagrams serve as the blueprint for database construction. Before writing a single CREATE TABLE statement, an ER diagram helps you think through the structure, spot design flaws, and communicate with your team.
| ER Diagram Component | SQL Equivalent | Description |
|---|---|---|
| Entity | Table | A thing or concept that stores data |
| Attribute | Column | A property of an entity |
| Primary Key | PRIMARY KEY | Unique identifier for each row |
| Foreign Key | FOREIGN KEY | Column linking to another table |
| Relationship | JOIN condition | How entities relate to each other |
Entity Types
DfEntity
An entity is a person, place, thing, or concept about which data is stored. Each entity becomes a table in the database, and each instance of the entity becomes a row.
-- Entities represented as tables
CREATE TABLE customers (
customer_id INTEGER PRIMARY KEY,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
phone TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE products (
product_id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
price DECIMAL(10,2) NOT NULL,
stock_quantity INTEGER NOT NULL DEFAULT 0,
category TEXT NOT NULL
);
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL,
order_date DATE NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
total DECIMAL(12,2) NOT NULL,
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);
Strong vs Weak Entities
DfStrong Entity
A strong entity can exist independently and has its own primary key. It does not depend on any other entity for identification.
DfWeak Entity
A weak entity cannot exist without a related strong entity. It uses a partial key (discriminator) combined with the strong entity's primary key to form its own identity.
-- Strong entity: customers
CREATE TABLE customers (
customer_id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
-- Weak entity: customer_addresses (depends on customers)
CREATE TABLE customer_addresses (
customer_id INTEGER NOT NULL,
address_type TEXT NOT NULL, -- Partial key (discriminator)
street TEXT NOT NULL,
city TEXT NOT NULL,
state TEXT NOT NULL,
zip_code TEXT NOT NULL,
PRIMARY KEY (customer_id, address_type),
FOREIGN KEY (customer_id) REFERENCES customers(customer_id) ON DELETE CASCADE
);
-- Weak entity: order_items (depends on orders)
CREATE TABLE order_items (
order_id INTEGER NOT NULL,
line_number INTEGER NOT NULL, -- Partial key
product_id INTEGER NOT NULL,
quantity INTEGER NOT NULL,
unit_price DECIMAL(10,2) NOT NULL,
PRIMARY KEY (order_id, line_number),
FOREIGN KEY (order_id) REFERENCES orders(order_id) ON DELETE CASCADE
);
Relationship Types
Cardinality
DfCardinality
Cardinality defines the numerical relationship between two entities β how many instances of one entity can relate to instances of another entity.
| Cardinality | Notation | Description | Example |
|---|---|---|---|
| One-to-One (1:1) | 1 βββ 1 | Each row in A maps to exactly one row in B | Person β Passport |
| One-to-Many (1:N) | 1 βββ N | Each row in A maps to many rows in B | Customer β Orders |
| Many-to-Many (M:N) | M βββ N | Rows in A map to many rows in B, and vice versa | Students β Courses |
-- One-to-One relationship
CREATE TABLE users (
user_id INTEGER PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
email TEXT NOT NULL UNIQUE
);
CREATE TABLE user_profiles (
user_id INTEGER PRIMARY KEY,
bio TEXT,
avatar_url TEXT,
theme_preference TEXT DEFAULT 'light',
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE
);
-- One-to-Many relationship
CREATE TABLE categories (
category_id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE products (
product_id INTEGER PRIMARY KEY,
category_id INTEGER NOT NULL,
name TEXT NOT NULL,
FOREIGN KEY (category_id) REFERENCES categories(category_id)
);
-- Many-to-Many relationship (junction table)
CREATE TABLE students (
student_id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE courses (
course_id INTEGER PRIMARY KEY,
title TEXT NOT NULL
);
CREATE TABLE enrollments (
student_id INTEGER NOT NULL,
course_id INTEGER NOT NULL,
enrolled_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
grade TEXT,
PRIMARY KEY (student_id, course_id),
FOREIGN KEY (student_id) REFERENCES students(student_id) ON DELETE CASCADE,
FOREIGN KEY (course_id) REFERENCES courses(course_id) ON DELETE CASCADE
);
Participation
DfTotal Participation
Total participation (mandatory) means every instance of an entity must participate in the relationship. Represented by double lines in ER notation or NOT NULL constraints.
DfPartial Participation
Partial participation (optional) means some instances of an entity may not participate in the relationship. Represented by single lines in ER notation or nullable columns.
-- Total participation: every order MUST have a customer
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL, -- NOT NULL enforces total participation
order_date DATE NOT NULL,
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);
-- Partial participation: not every customer has a loyalty card
CREATE TABLE loyalty_cards (
card_id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL UNIQUE,
points INTEGER DEFAULT 0,
issued_date DATE NOT NULL,
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
-- customer_id is UNIQUE but could be nullable for partial participation
);
ER Diagram Notation Reference
Chen Notation
| Symbol | Meaning |
|---|---|
| Rectangle | Entity |
| Diamond | Relationship |
| Oval | Attribute |
| Underlined text | Primary key |
| Dashed oval | Derived attribute |
| Double rectangle | Weak entity |
Crow's Foot Notation (Industry Standard)
| Symbol | Meaning |
|---|---|
| ` | |
| ` | o` |
| `> | ` |
>o | Optional many |
| Single line | Total participation |
| Double line | Partial participation |
-- Crow's Foot notation example:
-- Customer ||--|{ Order : places
-- Order ||--|{ OrderItem : contains
-- Product ||--o{ OrderItem : referenced in
CREATE TABLE customers (
customer_id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL,
order_date DATE NOT NULL,
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
-- ||--|{ : Mandatory Many (every order has exactly one customer)
);
CREATE TABLE products (
product_id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE order_items (
order_item_id INTEGER PRIMARY KEY,
order_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
quantity INTEGER NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(order_id),
FOREIGN KEY (product_id) REFERENCES products(product_id)
-- ||--o{ : Optional Many (product might not be in any orders yet)
);
Real-World ER Design: Hospital Management
-- Entities
CREATE TABLE patients (
patient_id INTEGER PRIMARY KEY,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
date_of_birth DATE NOT NULL,
gender TEXT,
phone TEXT,
insurance_id TEXT
);
CREATE TABLE doctors (
doctor_id INTEGER PRIMARY KEY,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
specialization TEXT NOT NULL,
license_number TEXT NOT NULL UNIQUE
);
CREATE TABLE departments (
department_id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
floor_number INTEGER NOT NULL
);
CREATE TABLE appointments (
appointment_id INTEGER PRIMARY KEY,
patient_id INTEGER NOT NULL,
doctor_id INTEGER NOT NULL,
appointment_date TIMESTAMP NOT NULL,
reason TEXT,
status TEXT DEFAULT 'scheduled',
FOREIGN KEY (patient_id) REFERENCES patients(patient_id),
FOREIGN KEY (doctor_id) REFERENCES doctors(doctor_id)
);
-- Many-to-Many: doctors belong to departments
CREATE TABLE doctor_departments (
doctor_id INTEGER NOT NULL,
department_id INTEGER NOT NULL,
role TEXT DEFAULT 'member',
PRIMARY KEY (doctor_id, department_id),
FOREIGN KEY (doctor_id) REFERENCES doctors(doctor_id),
FOREIGN KEY (department_id) REFERENCES departments(department_id)
);
-- Weak entity: medical records depend on patients
CREATE TABLE medical_records (
patient_id INTEGER NOT NULL,
record_date TIMESTAMP NOT NULL,
diagnosis TEXT NOT NULL,
treatment TEXT,
notes TEXT,
doctor_id INTEGER NOT NULL,
PRIMARY KEY (patient_id, record_date),
FOREIGN KEY (patient_id) REFERENCES patients(patient_id) ON DELETE CASCADE,
FOREIGN KEY (doctor_id) REFERENCES doctors(doctor_id)
);
-- One-to-One: patient emergency contact
CREATE TABLE emergency_contacts (
patient_id INTEGER PRIMARY KEY,
contact_name TEXT NOT NULL,
relationship TEXT NOT NULL,
phone TEXT NOT NULL,
FOREIGN KEY (patient_id) REFERENCES patients(patient_id) ON DELETE CASCADE
);
ER Design Process
| Step | Action | Output |
|---|---|---|
| 1 | Identify entities | List of nouns from requirements |
| 2 | Define attributes | Properties for each entity |
| 3 | Identify relationships | Connections between entities |
| 4 | Determine cardinality | 1:1, 1:N, M:N for each relationship |
| 5 | Apply normalization | Eliminate redundancy |
| 6 | Review and iterate | Validate with stakeholders |
Common ER Diagram Mistakes
| Mistake | Problem | Solution |
|---|---|---|
| No junction table for M:N | Violates 1NF, data integrity issues | Create intersection entity |
| Using natural keys everywhere | Keys can change, causing cascade updates | Use surrogate keys (UUID, SERIAL) |
| Over-normalization | Excessive JOINs degrade performance | Balance with denormalization |
| Missing foreign keys | No referential integrity enforcement | Always declare FOREIGN KEY constraints |
| Ambiguous relationships | Unclear business rules | Document cardinality and participation |
-- Bad: Storing multiple values in one column (violates 1NF)
CREATE TABLE products_bad (
product_id INTEGER PRIMARY KEY,
tags TEXT -- "electronics, sale, new" -- BAD!
);
-- Good: Many-to-many with junction table
CREATE TABLE tags (
tag_id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE
);
CREATE TABLE product_tags (
product_id INTEGER NOT NULL,
tag_id INTEGER NOT NULL,
PRIMARY KEY (product_id, tag_id),
FOREIGN KEY (product_id) REFERENCES products(product_id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags(tag_id) ON DELETE CASCADE
);
Key Takeaways
- ER diagrams are blueprints β Design before coding to avoid costly refactors
- Entities become tables, attributes become columns β Direct mapping to SQL
- Always use junction tables for M:N relationships β Never store comma-separated IDs
- Choose the right key strategy β Surrogate keys for stability, natural keys for simplicity
- Validate with real queries β Ensure your design supports actual business requirements
Quick Reference: Relationship Patterns
| Pattern | Use Case | Implementation |
|---|---|---|
| User β Profile | One-to-one extension | Shared primary key |
| Customer β Orders | One-to-many ownership | Foreign key in child |
| Student β Course | Many-to-many enrollment | Junction table |
| Product β Variants | One-to-many with discriminator | Composite key on child |
| Invoice β Line Items | Parent with weak children | Composite FK + ON DELETE CASCADE |