ER Model in DBMS Explained — Entities, Attributes, and Relationships
ER Model in DBMS explained from scratch — entities, attributes, relationships, cardinality, and how to draw your first ER diagram with real-world examples..
20+ years shipping production systems from the metal up. Written from production experience, not tutorials.
- ✓Basic programming fundamentals
- ✓A computer with internet access
- ✓Willingness to follow along with examples
- ER Model is a conceptual framework for designing database schemas visually before writing SQL
- Entities (rectangles) represent real-world objects; Attributes (ovals) describe them
- Relationships (diamonds) connect entities with cardinality (1:1, 1:N, M:N)
- Performance insight: Getting cardinality wrong leads to costly schema redesigns that take weeks
- Production insight: Inconsistent relationship participation can cause orphaned data or NULL constraint violations
- Biggest mistake: Treating M:N relationships as directly storable instead of creating a junction table
The Entity-Relationship (ER) model is a conceptual data modeling technique that represents the structure of a database as a collection of entities, their attributes, and the relationships between them. It was introduced by Peter Chen in 1976 to bridge the gap between real-world business concepts and the logical schema of a relational database.
The ER model exists because relational tables alone don't capture semantics—they just store rows. By modeling at the ER level, you define what 'things' exist (customers, orders, products), what details matter about each (name, date, price), and how they connect (a customer places orders, an order contains products).
This abstraction lets you validate business rules before writing a single CREATE TABLE statement, catching design flaws early when they cost nothing to fix.
In practice, the ER model is the precursor to a relational schema. You draw an ER diagram (ERD) using boxes for entities, ovals for attributes, and diamonds for relationships, with cardinality markers like 1:N or M:N. Tools like Lucidchart, draw.io, or pgAdmin support this notation.
The model handles edge cases through weak entities (existence-dependent on a strong entity, like line items on an order) and specialization (subtypes like 'Manager' inheriting from 'Employee'). When NOT to use it: for document stores like MongoDB or key-value stores like Redis, where denormalized or schema-less designs are preferred.
Also skip it for trivial apps with 2-3 tables—the overhead isn't worth it.
The real power of the ER model is its transformation rules. Every entity becomes a table, attributes become columns, and relationships become foreign keys or junction tables. A 1:N relationship adds a foreign key to the 'many' side; an M:N relationship creates a new table with composite keys.
Weak entities get a composite primary key that includes the parent's key. Specialization can be mapped as separate tables or a single table with nullable columns. This systematic mapping is why ER modeling remains standard in enterprise DBMS design—it's the only reliable way to ensure your database schema accurately reflects the business domain without redundancy or anomalies.
Imagine you're building a school's record system on paper before touching any computer. You'd first sketch out: 'What are the THINGS I need to track?' (students, teachers, classes), 'What do I know ABOUT each thing?' (a student has a name, age, ID), and 'How do these things CONNECT?' (a student ENROLLS IN a class). That sketch — those boxes, bubbles, and lines — is literally an ER diagram. The ER Model is just the rulebook for drawing that sketch in a way every database designer on the planet understands.
Every app you've used — Instagram, your bank's website, your school's portal — stores data somewhere. Before a single line of code is written, a database designer sits down and asks: 'What exactly am I storing, and how does it all fit together?' Get that wrong and you're rebuilding the entire database six months later. The ER Model exists precisely to prevent that painful, expensive mistake.
The Entity-Relationship Model, introduced by Dr. Peter Chen in 1976, gives you a visual language to plan a database at a high level — before you worry about tables, SQL, or any specific database software. It's like an architect drawing blueprints before construction begins. You wouldn't build a house without blueprints, and you shouldn't build a database without an ER diagram.
By the end of this article you'll be able to identify entities, attributes, and relationships in any real-world scenario, understand the rules around cardinality and participation, read and draw a basic ER diagram from scratch, and confidently explain all of this in a technical interview. Let's build it up piece by piece.
What an ER Model Actually Captures
An Entity-Relationship (ER) model is a conceptual data model that defines the logical structure of a database as a set of entities, their attributes, and the relationships between them. It serves as a blueprint before any physical schema is written — think of it as the architectural diagram that maps real-world objects (Customer, Order, Product) into a formal graph of nodes and edges. The core mechanic is abstraction: you strip away implementation details (SQL, indexes, storage) and focus purely on what data exists and how it connects.
In practice, an ER model uses three primitives: entities (rectangles), attributes (ovals), and relationships (diamonds). Entities become tables, attributes become columns, and relationships become foreign keys or join tables. The model enforces cardinality constraints — one-to-one, one-to-many, many-to-many — which directly dictate referential integrity in the final database. A well-drawn ER model eliminates ambiguity: every attribute belongs to exactly one entity, and every relationship has a clear direction and multiplicity.
Use an ER model in the design phase of any relational database project — before writing a single CREATE TABLE statement. It forces you to resolve naming conflicts, normalize data, and identify missing entities early. Teams that skip this step routinely discover schema mismatches during integration testing, leading to costly migrations. The model is also the single source of truth for documentation: new engineers can understand the data landscape in minutes, not days.
Entities and Attributes — The 'Things' and Their 'Details'
An entity is any real-world object or concept that you need to store data about and that can be uniquely identified. In a university system, STUDENT, COURSE, and PROFESSOR are all entities. Think of an entity like a category — not a specific student named 'Alice', but the concept of 'a student' in general.
An entity instance is one specific member of that category. Alice (Student ID: 101) is an instance. Bob (Student ID: 102) is another instance.
Attributes are the properties or details that describe an entity. A STUDENT entity might have attributes: StudentID, FirstName, LastName, DateOfBirth, and Email. In an ER diagram, entities are drawn as rectangles and attributes as ovals connected to their entity by a line.
Attributes come in important flavours you need to know. A simple attribute stores a single value (like Age). A composite attribute is made of smaller parts — FullName can be split into FirstName and LastName. A multi-valued attribute can hold more than one value — a student can have multiple PhoneNumbers. A derived attribute is calculated from another — Age can be derived from DateOfBirth, so it's drawn with a dashed oval.
The key attribute uniquely identifies each entity instance. StudentID is a key attribute — no two students share it. In diagrams, key attributes are underlined.
-- ============================================================ -- Translating ER Entities and Attributes into real SQL tables -- Using io_thecodeforge schema for production naming -- ============================================================ -- ENTITY: Student -- KEY ATTRIBUTE: student_id (underlined in ER diagram -- must be unique) -- SIMPLE ATTRS: first_name, last_name, email -- DERIVED ATTR: age (we store date_of_birth and calculate age when needed) -- COMPOSITE ATTR: full_name is split into first_name + last_name -- MULTI-VALUED: phone_numbers gets its OWN separate table (shown below) CREATE TABLE io_thecodeforge.Student ( student_id INT PRIMARY KEY, first_name VARCHAR(50) NOT NULL, last_name VARCHAR(50) NOT NULL, date_of_birth DATE NOT NULL, email VARCHAR(100) UNIQUE NOT NULL ); -- Multi-valued attribute 'PhoneNumbers' becomes its own table. CREATE TABLE io_thecodeforge.StudentPhone ( phone_id INT PRIMARY KEY, student_id INT NOT NULL, phone_number VARCHAR(15) NOT NULL, FOREIGN KEY (student_id) REFERENCES io_thecodeforge.Student(student_id) ); -- ENTITY: Course CREATE TABLE io_thecodeforge.Course ( course_id INT PRIMARY KEY, course_name VARCHAR(100) NOT NULL, credits INT NOT NULL ); -- Insert sample data INSERT INTO io_thecodeforge.Student VALUES (101, 'Alice', 'Morgan', '2001-04-15', 'alice@uni.edu'); INSERT INTO io_thecodeforge.Student VALUES (102, 'Bob', 'Chen', '2000-09-22', 'bob@uni.edu'); INSERT INTO io_thecodeforge.StudentPhone VALUES (1, 101, '555-0101'); INSERT INTO io_thecodeforge.StudentPhone VALUES (2, 101, '555-0202'); INSERT INTO io_thecodeforge.StudentPhone VALUES (3, 102, '555-0303'); INSERT INTO io_thecodeforge.Course VALUES (1, 'Database Systems', 3); INSERT INTO io_thecodeforge.Course VALUES (2, 'Data Structures', 4); -- Query to see Alice's full record including derived age SELECT student_id, first_name || ' ' || last_name AS full_name, EXTRACT(YEAR FROM AGE(date_of_birth)) AS age, email FROM io_thecodeforge.Student WHERE student_id = 101;
Relationships and Cardinality — How Entities Connect to Each Other
Entities don't exist in isolation — they interact. A STUDENT enrolls in a COURSE. A PROFESSOR teaches a COURSE. These interactions are relationships, and in ER diagrams they're drawn as diamonds connecting two or more entity rectangles.
Relationships also have attributes. The ENROLLS_IN relationship between STUDENT and COURSE might have an EnrollmentDate attribute — that date doesn't belong to the student alone or the course alone, it belongs to the act of enrolling.
Cardinality is the most critical concept in ER modeling. It defines HOW MANY instances of one entity can relate to how many instances of another. There are three types.
One-to-One (1:1): One entity instance links to exactly one instance on the other side. A PERSON has exactly one PASSPORT. One passport belongs to exactly one person.
One-to-Many (1:N): One instance on side A relates to many instances on side B, but each instance on side B relates to only one on side A. One PROFESSOR teaches many COURSES, but each course is taught by one professor.
Many-to-Many (M:N): Many instances on side A relate to many on side B. A STUDENT can enroll in many COURSES, and a COURSE can have many STUDENTS enrolled.
Participation constraints tell you whether participation is mandatory. Total participation (double line) means every instance MUST participate. Partial participation (single line) means it's optional.
-- ============================================================ -- Translating ER Relationships and Cardinality into SQL -- ============================================================ -- Relationship: PROFESSOR teaches COURSE (One-to-Many) -- We implement 1:N by placing the foreign key on the 'many' side (Course table) CREATE TABLE io_thecodeforge.Professor ( professor_id INT PRIMARY KEY, full_name VARCHAR(100) NOT NULL, department VARCHAR(50) NOT NULL ); ALTER TABLE io_thecodeforge.Course ADD COLUMN professor_id INT, ADD CONSTRAINT fk_course_professor FOREIGN KEY (professor_id) REFERENCES io_thecodeforge.Professor(professor_id); -- Relationship: STUDENT enrolls in COURSE (Many-to-Many) -- M:N relationships ALWAYS become their own 'junction' table CREATE TABLE io_thecodeforge.Enrollment ( enrollment_id INT PRIMARY KEY, student_id INT NOT NULL, course_id INT NOT NULL, enrollment_date DATE NOT NULL, grade CHAR(2), FOREIGN KEY (student_id) REFERENCES io_thecodeforge.Student(student_id), FOREIGN KEY (course_id) REFERENCES io_thecodeforge.Course(course_id), UNIQUE (student_id, course_id) ); -- Relationship: PERSON has PASSPORT (One-to-One) -- UNIQUE foreign key enforces 1:1 CREATE TABLE io_thecodeforge.Person ( person_id INT PRIMARY KEY, full_name VARCHAR(100) NOT NULL ); CREATE TABLE io_thecodeforge.Passport ( passport_id INT PRIMARY KEY, passport_number VARCHAR(20) UNIQUE NOT NULL, person_id INT UNIQUE NOT NULL, expiry_date DATE NOT NULL, FOREIGN KEY (person_id) REFERENCES io_thecodeforge.Person(person_id) ); -- Sample data INSERT INTO io_thecodeforge.Professor VALUES (1, 'Dr. Sarah Hill', 'Computer Science'); INSERT INTO io_thecodeforge.Professor VALUES (2, 'Dr. James Okafor','Mathematics'); UPDATE io_thecodeforge.Course SET professor_id = 1 WHERE course_id = 1; UPDATE io_thecodeforge.Course SET professor_id = 1 WHERE course_id = 2; INSERT INTO io_thecodeforge.Enrollment VALUES (1, 101, 1, '2024-01-10', 'A'); INSERT INTO io_thecodeforge.Enrollment VALUES (2, 101, 2, '2024-01-10', 'B+'); INSERT INTO io_thecodeforge.Enrollment VALUES (3, 102, 1, '2024-01-11', NULL); -- Query: Alice's courses with grades SELECT s.first_name || ' ' || s.last_name AS student_name, c.course_name, e.enrollment_date, COALESCE(e.grade, 'Not graded') AS grade FROM io_thecodeforge.Enrollment e JOIN io_thecodeforge.Student s ON e.student_id = s.student_id JOIN io_thecodeforge.Course c ON e.course_id = c.course_id WHERE e.student_id = 101 ORDER BY c.course_name;
Weak Entities, Specialisation, and the Full ER Diagram Picture
Most real systems have an important special case: a weak entity. A weak entity is one that cannot be uniquely identified by its own attributes alone — it depends on another entity (its owner entity) to be identified.
Think of ORDER_ITEM. An order item has an item number (item 1, item 2, item 3...), but that number only makes sense WITHIN a specific ORDER. Item 3 of Order #500 is completely different from item 3 of Order #501. ORDER_ITEM depends on ORDER for its identity. In ER diagrams, weak entities use double rectangles, and the relationship connecting them to their owner uses a double diamond.
The partial key of a weak entity (the attribute that identifies it within its owner) is shown with a dashed underline rather than a solid one.
Specialisation and Generalisation add inheritance-like thinking. Suppose you have a PERSON entity. An EMPLOYEE and a STUDENT are both persons but have extra specific attributes. You can generalise EMPLOYEE and STUDENT into PERSON (they share common attributes like Name, Age), and specialise PERSON into the subtypes. This is identical to how class inheritance works in object-oriented programming. In ER diagrams this is drawn as a triangle labelled 'ISA'.
With all these building blocks — entities, attributes, relationships, cardinality, weak entities, and specialisation — you can model virtually any real-world system before writing a single line of SQL.
-- ============================================================ -- Weak Entity: ORDER_ITEM depends on ORDER for its identity -- Specialisation: EMPLOYEE and STUDENT are subtypes of PERSON -- ============================================================ -- Strong entity: ORDER CREATE TABLE io_thecodeforge.CustomerOrder ( order_id INT PRIMARY KEY, order_date DATE NOT NULL, customer_name VARCHAR(100) NOT NULL ); -- Weak entity: ORDER_ITEM -- item_number is the PARTIAL KEY CREATE TABLE io_thecodeforge.OrderItem ( order_id INT NOT NULL, item_number INT NOT NULL, product_name VARCHAR(100) NOT NULL, quantity INT NOT NULL, unit_price DECIMAL(8,2) NOT NULL, PRIMARY KEY (order_id, item_number), FOREIGN KEY (order_id) REFERENCES io_thecodeforge.CustomerOrder(order_id) ON DELETE CASCADE ); -- Specialisation / Generalisation CREATE TABLE io_thecodeforge.Person ( person_id INT PRIMARY KEY, full_name VARCHAR(100) NOT NULL, date_of_birth DATE NOT NULL, email VARCHAR(100) UNIQUE NOT NULL ); CREATE TABLE io_thecodeforge.Employee ( person_id INT PRIMARY KEY, employee_code VARCHAR(20) UNIQUE NOT NULL, hire_date DATE NOT NULL, salary DECIMAL(10,2) NOT NULL, FOREIGN KEY (person_id) REFERENCES io_thecodeforge.Person(person_id) ); CREATE TABLE io_thecodeforge.Student_Person ( person_id INT PRIMARY KEY, student_id VARCHAR(20) UNIQUE NOT NULL, enrollment_year INT NOT NULL, major VARCHAR(50), FOREIGN KEY (person_id) REFERENCES io_thecodeforge.Person(person_id) ); -- Sample data INSERT INTO io_thecodeforge.CustomerOrder VALUES (500, '2024-03-01', 'Greenleaf Supplies'); INSERT INTO io_thecodeforge.CustomerOrder VALUES (501, '2024-03-02', 'BlueStar Corp'); INSERT INTO io_thecodeforge.OrderItem VALUES (500, 1, 'Printer Paper A4', 10, 5.99); INSERT INTO io_thecodeforge.OrderItem VALUES (500, 2, 'Black Ink Cartridge', 3, 18.50); INSERT INTO io_thecodeforge.OrderItem VALUES (500, 3, 'Stapler', 1, 12.00); INSERT INTO io_thecodeforge.OrderItem VALUES (501, 1, 'Whiteboard Markers', 5, 3.25); INSERT INTO io_thecodeforge.Person VALUES (1, 'Carol White', '1985-07-20', 'carol@company.com'); INSERT INTO io_thecodeforge.Employee VALUES (1, 'EMP-00421', '2015-03-01', 72000.00); -- Retrieve a full order with line items SELECT o.order_id, o.customer_name, i.item_number, i.product_name, i.quantity, i.unit_price, (i.quantity * i.unit_price) AS line_total FROM io_thecodeforge.CustomerOrder o JOIN io_thecodeforge.OrderItem i ON o.order_id = i.order_id WHERE o.order_id = 500 ORDER BY i.item_number;
Drawing an ER Diagram from a Real Scenario: The Library System
Let's apply everything to a concrete system: a library. You need to track books, members, and loans.
First, identify the entities. BOOK, MEMBER, and LOAN are clear. But don't stop there — a library may have multiple copies of the same book, so you need BOOK_COPY. Each loan can include multiple copies? No, each loan is for one copy at a time, but a member can borrow many copies over time. So the relationship is MEMBER borrows BOOK_COPY. But a BOOK has many copies, and a copy belongs to one book.
Let's define attributes. BOOK: ISBN (key), Title, Author, Publisher. BOOK_COPY: CopyID (partial key within BOOK), Condition, ShelfLocation. MEMBER: MemberID (key), Name, Email, Phone. LOAN: LoanID (key), LoanDate, DueDate, ReturnDate.
Now relationships: BOOK has many BOOK_COPY (1:N). MEMBER borrows many BOOK_COPY (1:N) — each loan is for one copy, but a member can have multiple active loans. LOAN links MEMBER and BOOK_COPY? Actually a loan is a relationship between MEMBER and BOOK_COPY with attributes (LoanDate, etc). That's a M:N relationship between MEMBER and BOOK_COPY resolved into the LOAN entity (associative entity). So LOAN is both an entity (has its own key) and a relationship.
Cardinalities: A member can have many loans (1:N from MEMBER to LOAN). A book copy can be in many loans over time (1:N from BOOK_COPY to LOAN). But a loan involves exactly one member and one copy. So it's a M:N between MEMBER and BOOK_COPY with extra attributes, often modeled as an associative entity.
Weak entity? Not in this simple model, but if we wanted to track loan items separately, we could. Alternatively, BOOK_COPY is a weak entity dependent on BOOK because CopyID only unique per book.
Now draw the diagram: rectangles for BOOK, BOOK_COPY, MEMBER, LOAN. Ovals for attributes. Diamonds for relationships: BOOK-HAS-BOOK_COPY, MEMBER-borrows-BOOK_COPY via LOAN? Actually LOAN is the relationship entity. Connect MEMBER to LOAN and BOOK_COPY to LOAN.
Specialisation: Maybe MEMBER can be STUDENT_MEMBER or FACULTY_MEMBER with different borrowing limits. Could add an ISA triangle.
This exercise shows how a seemingly simple system reveals entities, weak entities, and relationship entities once you think deeply.
-- ============================================================ -- Library System: Full ER to SQL translation -- ============================================================ -- Strong entity: Book CREATE TABLE io_thecodeforge.Book ( isbn VARCHAR(20) PRIMARY KEY, title VARCHAR(200) NOT NULL, author VARCHAR(100) NOT NULL, publisher VARCHAR(100) ); -- Weak entity: BookCopy (depends on Book) CREATE TABLE io_thecodeforge.BookCopy ( isbn VARCHAR(20) NOT NULL, copy_id INT NOT NULL, condition VARCHAR(50), shelf_location VARCHAR(20), PRIMARY KEY (isbn, copy_id), FOREIGN KEY (isbn) REFERENCES io_thecodeforge.Book(isbn) ON DELETE CASCADE ); -- Strong entity: Member CREATE TABLE io_thecodeforge.Member ( member_id INT PRIMARY KEY, full_name VARCHAR(100) NOT NULL, email VARCHAR(100) NOT NULL, phone VARCHAR(15) ); -- Associative entity: Loan (relationship with attributes) CREATE TABLE io_thecodeforge.Loan ( loan_id INT PRIMARY KEY, member_id INT NOT NULL, isbn VARCHAR(20) NOT NULL, copy_id INT NOT NULL, loan_date DATE NOT NULL, due_date DATE NOT NULL, return_date DATE, FOREIGN KEY (member_id) REFERENCES io_thecodeforge.Member(member_id), FOREIGN KEY (isbn, copy_id) REFERENCES io_thecodeforge.BookCopy(isbn, copy_id), UNIQUE (member_id, isbn, copy_id, loan_date) ); -- Sample data INSERT INTO io_thecodeforge.Book VALUES ('978-0134685991', 'Effective Java', 'Joshua Bloch', 'Addison-Wesley'); INSERT INTO io_thecodeforge.Book VALUES ('978-1491950357', 'Designing Data-Intensive Applications', 'Martin Kleppmann', 'OReilly'); INSERT INTO io_thecodeforge.BookCopy VALUES ('978-0134685991', 1, 'Good', 'A1-01'); INSERT INTO io_thecodeforge.BookCopy VALUES ('978-0134685991', 2, 'Fair', 'A1-02'); INSERT INTO io_thecodeforge.BookCopy VALUES ('978-1491950357', 1, 'Excellent', 'B2-10'); INSERT INTO io_thecodeforge.Member VALUES (201, 'Alice Morgan', 'alice@lib.edu', '555-1111'); INSERT INTO io_thecodeforge.Member VALUES (202, 'Bob Chen', 'bob@lib.edu', '555-2222'); INSERT INTO io_thecodeforge.Loan VALUES (1, 201, '978-0134685991', 1, '2024-04-01', '2024-04-15', NULL); INSERT INTO io_thecodeforge.Loan VALUES (2, 202, '978-1491950357', 1, '2024-04-02', '2024-04-16', NULL); -- Find all current loans for Alice SELECT m.full_name, b.title, l.loan_date, l.due_date FROM io_thecodeforge.Loan l JOIN io_thecodeforge.Member m ON l.member_id = m.member_id JOIN io_thecodeforge.BookCopy bc ON l.isbn = bc.isbn AND l.copy_id = bc.copy_id JOIN io_thecodeforge.Book b ON bc.isbn = b.isbn WHERE m.full_name = 'Alice Morgan' AND l.return_date IS NULL;
From ER Diagram to Relational Schema: The Transformation Rules
Once your ER diagram is complete, you need to transform it into a relational schema. This is a systematic process with well-defined rules.
Rule 1: Each regular entity becomes a table. The key attribute becomes the PRIMARY KEY.
Rule 2: Each composite attribute is split into its component attributes as separate columns.
Rule 3: Each multi-valued attribute becomes a separate table with a foreign key to the original entity's table and the attribute value column. The primary key is typically a composite of the foreign key and a unique value.
Rule 4: Each weak entity becomes a table with a composite primary key: the primary key of the owner entity plus the weak entity's partial key. Include a foreign key to the owner table with ON DELETE CASCADE.
Rule 5: For a 1:1 relationship, add a foreign key in one of the tables (preferably the one with total participation) and make it UNIQUE.
Rule 6: For a 1:N relationship, add a foreign key in the table representing the 'N' side referencing the '1' side.
Rule 7: For an M:N relationship, create a junction table with foreign keys to both entity tables. The primary key is a composite of those two foreign keys (or include a surrogate key if relationship attributes exist).
Rule 8: For specialisation (ISA), create a table for the superclass with its primary key. For each subclass, create a table with the same primary key (shared primary key) and additional attributes. Add foreign key from subclass to superclass.
These rules guarantee a lossless and standardised translation from conceptual model to physical schema.
-- ============================================================ -- Summary: ER to Relational Transformation Rules -- Each rule corresponds to a specific ER construct -- ============================================================ -- Rule 1: Entity -> Table, Key attribute -> PK CREATE TABLE io_thecodeforge.EntityTable ( entity_id INT PRIMARY KEY, -- Key attribute attr1 VARCHAR(100), attr2 VARCHAR(100) ); -- Rule 2: Composite attribute -> multiple columns -- Already done above: Person (full_name split into first_name, last_name) -- Rule 3: Multi-valued attribute -> separate table with FK CREATE TABLE io_thecodeforge.MultiValued ( entity_id INT NOT NULL, value_id INT NOT NULL, -- Partial key or surrogate value VARCHAR(100) NOT NULL, PRIMARY KEY (entity_id, value_id), FOREIGN KEY (entity_id) REFERENCES io_thecodeforge.EntityTable(entity_id) ); -- Rule 4: Weak entity -> composite PK -- Already done: OrderItem (order_id, item_number) -- Rule 5: 1:1 relationship -> UNIQUE FK (shown in Person/Passport) -- Rule 6: 1:N relationship -> FK on N side (shown in Professor/Course) -- Rule 7: M:N relationship -> junction table (shown in Enrollment) -- Rule 8: Specialisation -> shared PK (shown in Person/Employee) -- Example: Applying all rules to a university schema -- This single script creates a full schema from an ER diagram CREATE TABLE io_thecodeforge.Department ( dept_id INT PRIMARY KEY, name VARCHAR(100) NOT NULL ); CREATE TABLE io_thecodeforge.Professor ( prof_id INT PRIMARY KEY, name VARCHAR(100) NOT NULL, dept_id INT NOT NULL, FOREIGN KEY (dept_id) REFERENCES io_thecodeforge.Department(dept_id) -- 1:N: many professors in one department ); CREATE TABLE io_thecodeforge.Course ( course_id INT PRIMARY KEY, title VARCHAR(200) NOT NULL, credits INT NOT NULL, prof_id INT NOT NULL, FOREIGN KEY (prof_id) REFERENCES io_thecodeforge.Professor(prof_id) -- 1:N: one professor teaches many courses ); CREATE TABLE io_thecodeforge.Student ( student_id INT PRIMARY KEY, name VARCHAR(100) NOT NULL, email VARCHAR(100) UNIQUE NOT NULL ); -- M:N: many students enroll in many courses -> junction table CREATE TABLE io_thecodeforge.Enrollment ( enrollment_id INT PRIMARY KEY, student_id INT NOT NULL, course_id INT NOT NULL, grade CHAR(2), semester VARCHAR(10) NOT NULL, FOREIGN KEY (student_id) REFERENCES io_thecodeforge.Student(student_id), FOREIGN KEY (course_id) REFERENCES io_thecodeforge.Course(course_id), UNIQUE (student_id, course_id, semester) -- a student can enroll in same course only once per semester );
- Entity → Table: The raw material becomes a container.
- Attribute → Column: Each property gets a slot in the container.
- Relationship → Foreign Key or Junction Table: Connections between containers are modeled through references.
- Weak Entity → Composite Key: Dependencies are captured by combining identifiers.
- Specialisation → Shared PK: Inheritance is modeled by using the same primary key in multiple tables.
Entity Sets vs. Instances: Why Your ERD Doesn't Show Rows
You'll see junior devs confuse an entity set with an actual row of data. That's how production bugs happen. An entity set is the type definition — like "Student" or "Invoice." It defines the shape, not the data. The ER diagram shows sets and their relationships, not individual records. When you draw a rectangle for "Customer," you're modeling the schema, not populating it. The instances live in your tables as rows. Why does this matter? Because when a PM asks "how many entities do we have?" they might mean rows. You mean entity types. Clarify that early or watch them design a database with 50 tables for 50 customers. Your ERD captures structure. Your DML captures data. Keep them separate.
// io.thecodeforge.db.entities // EntitySet: structural definition, not data public class EntitySet<E> { private final String entityName; private final Map<String, AttributeDef> attributes; public EntitySet(String name, Map<String, AttributeDef> attrs) { this.entityName = name; this.attributes = attrs; } // Represents the entity TYPE, not instances public boolean isValidInstance(Map<String, Object> instance) { return attributes.keySet().containsAll(instance.keySet()); } }
Attributes: Simple, Composite, Multivalued — Pick the Right Abstraction
Your ER model's attribute choice directly impacts your column design, normalization level, and query performance. Simple attributes map to single columns: think "age" or "email." Composite attributes like "Address" (street, city, zip) suggest you need separate columns or a child table. Multivalued attributes—a customer with multiple phone numbers—demand a normalized table or a JSONB column if you're on Postgres. The trap? Copying user requirements literally. A user says "I need a phone number field." They mean "I support multiple phone numbers." Ask WHY they need it before deciding HOW to model it. Your ERD should reflect real data patterns, not naivety. Map composites to tables early. Identify multivalued attributes before your third normal form implementation.
-- io.thecodeforge.er_mapping -- Composite & multivalued attributes -> normalized tables -- Strong entity table CREATE TABLE customer ( customer_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), first_name TEXT NOT NULL, -- simple last_name TEXT NOT NULL, -- simple date_of_birth DATE -- simple ); -- Composite attribute (address) CREATE TABLE customer_address ( address_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), customer_id UUID NOT NULL REFERENCES customer(customer_id), street TEXT NOT NULL, city TEXT NOT NULL, state TEXT NOT NULL, zip TEXT NOT NULL ); -- Multivalued attribute (phone numbers) CREATE TABLE customer_phone ( phone_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), customer_id UUID NOT NULL REFERENCES customer(customer_id), phone_type TEXT CHECK (phone_type IN ('mobile', 'home', 'work')), phone_number TEXT NOT NULL );
The Missing Junction Table That Cost $50,000 in Lost Revenue
- Never shortcut a Many-to-Many relationship — a junction table is mandatory.
- An ER diagram is cheap insurance; skipping it leads to costly rework.
- If data looks like it belongs in a comma-separated list, stop and create a separate table.
SELECT * FROM io_thecodeforge.OrderItem WHERE order_id NOT IN (SELECT order_id FROM io_thecodeforge.CustomerOrder);SELECT COUNT(*) FROM io_thecodeforge.OrderItem WHERE order_id IS NULL;SELECT student_id, course_id, COUNT(*) FROM io_thecodeforge.Enrollment GROUP BY student_id, course_id HAVING COUNT(*) > 1;SHOW CREATE TABLE io_thecodeforge.Enrollment;SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'YourTable';Check for patterns like phone1, phone2, phone3 columns.SELECT table_name, column_name FROM information_schema.columns WHERE column_name LIKE '%_id' AND table_schema = 'io_thecodeforge';Check for missing REFERENCES via SHOW CREATE TABLE.| Concept | ER Diagram Symbol | SQL Equivalent | Real Example |
|---|---|---|---|
| Entity | Rectangle | Table (CREATE TABLE) | STUDENT, COURSE, PROFESSOR |
| Key Attribute | Oval with underline | PRIMARY KEY column | student_id, course_id |
| Simple Attribute | Oval | Regular column | first_name, email |
| Composite Attribute | Oval with sub-ovals | Multiple columns | full_name → first_name + last_name |
| Multi-valued Attribute | Double oval | Separate table with FK | phone_numbers → StudentPhone table |
| Derived Attribute | Dashed oval | Calculated column / formula | age derived from date_of_birth |
| Relationship (1:1) | Diamond + 1 and 1 labels | UNIQUE foreign key | PERSON has one PASSPORT |
| Relationship (1:N) | Diamond + 1 and N labels | Foreign key on 'N' side | PROFESSOR teaches many COURSEs |
| Relationship (M:N) | Diamond + M and N labels | Junction/bridge table | STUDENT enrolls in COURSE |
| Weak Entity | Double rectangle | Composite primary key | ORDER_ITEM depends on ORDER |
| Total Participation | Double line to relationship | NOT NULL foreign key | Every order MUST have a customer |
| Partial Participation | Single line to relationship | Nullable foreign key | A professor MAY have a department |
| Specialisation (ISA) | Triangle with dividing lines | Shared primary key across tables | Person / Employee / Student |
| File | Command / Code | Purpose |
|---|---|---|
| university_entities.sql | CREATE TABLE io_thecodeforge.Student ( | Entities and Attributes |
| university_relationships.sql | CREATE TABLE io_thecodeforge.Professor ( | Relationships and Cardinality |
| weak_entity_and_specialisation.sql | CREATE TABLE io_thecodeforge.CustomerOrder ( | Weak Entities, Specialisation, and the Full ER Diagram Pictu |
| library_system.sql | CREATE TABLE io_thecodeforge.Book ( | Drawing an ER Diagram from a Real Scenario |
| transformation_rules.sql | CREATE TABLE io_thecodeforge.EntityTable ( | From ER Diagram to Relational Schema |
| EntitySetModel.java | public class EntitySet | Entity Sets vs. Instances |
| 005_attribute_modeling.sql | CREATE TABLE customer ( | Attributes: Simple, Composite, Multivalued |
Key takeaways
Common mistakes to avoid
4 patternsTreating a Many-to-Many relationship as directly storable
Confusing an entity with an attribute
Forgetting that a weak entity's identifier is always composite
Using a single table for all subtypes in a specialisation hierarchy
Interview Questions on This Topic
What is the difference between a strong entity and a weak entity? Can you give a real-world example of each?
How do you convert a Many-to-Many relationship from an ER diagram into a relational database schema? Walk me through the steps.
If 'Age' is a derived attribute in an ER diagram, should you store it as a column in your database table? What are the trade-offs?
Explain the difference between total and partial participation with a real example.
How would you represent a ternary relationship (involving three entities) in an ER diagram and then in SQL?
Frequently Asked Questions
The ER Model is the theoretical framework — the set of rules and concepts (entities, attributes, relationships, cardinality) for describing data. An ER diagram is the actual visual drawing you create by applying those rules to a specific system. The model is the language; the diagram is the sentence you write in that language.
Because fixing a bad database design after data is already in it is enormously expensive and painful. An ER diagram lets you spot structural problems — missing relationships, wrongly placed attributes, forgotten entities — at the planning stage when changes cost nothing. It's the same reason architects draw blueprints instead of just starting to pour concrete.
Cardinality answers 'how many?' — specifically, how many instances of entity A can relate to how many instances of entity B (1:1, 1:N, M:N). Participation answers 'is it mandatory?' — total participation means every single instance MUST be in the relationship (e.g., every order item must belong to an order), while partial participation means it's optional (e.g., a professor may or may not advise a student project). Both appear on the same relationship line but describe different things.
Yes, absolutely. For example, a Person can be both an Employee and a Manager. The same two entities can have multiple diamonds between them to represent different associations. In a company database, Employee and Department may have a 'works_in' relationship (1:N) and a 'manages' relationship (1:1). Each relationship is distinct and may have its own attributes.
You'll likely miss entities, misplace attributes, misunderstand cardinality, and create a schema full of redundancy and anomalies. The first time you try to add a feature like 'track which student borrowed which book copy on which date', you'll need a painful migration. ER diagrams are cheap insurance against expensive rework.
20+ years shipping production systems from the metal up. Written from production experience, not tutorials.
That's DBMS. Mark it forged?
7 min read · try the examples if you haven't