In the previous lesson we closed requirements document v1.0 for the BiblioRed extension: fourteen functional requirements, ten business rules, twelve queries and a bounded scope. It is an excellent text for discussing things with the client and absolutely useless for programming. Between those two things one step is missing: turning the prose into a drawing.
The entity-relationship model is that drawing. Peter Chen proposed it in 1976 —we placed it in lesson 01-03— and fifty years later it is still the standard way of thinking through a domain before touching the keyboard. It is not a decorative diagram you produce at the end for documentation: it is a reasoning tool. Drawing forces you to decide things that prose lets you leave vague, and that is why half the useful questions in a project turn up while you are tracing a line between two boxes.
This lesson teaches the complete language —entities, attributes, relationships, cardinality, participation, hierarchies— and applies it, decision by decision, to the BiblioRed commission. The deliverable is the complete ER diagram of the extended BiblioRed, integrating the seven tables that already existed with everything new. There is still not a single CREATE TABLE statement: that is the next lesson, and there is a reason for the order.
Contents
- What a conceptual model is and why it is drawn before any table exists
- Entities: strong and weak
- Attributes: simple, composite, multivalued, derived and identifiers
- Relationships: binary, reflexive, ternary and with attributes of their own
- Cardinality: 1:1, 1:N and N:M
- Total or partial participation: the other half everybody confuses
- The notations: Chen versus crow's foot
- The extended ER model: generalization and specialization
- Drawing BiblioRed, decision by decision
- Deliverable: the complete ER diagram of the extended BiblioRed
- Tools for drawing diagrams
- Common Mistakes and Tips
- Exercises
- Conclusion
- What a conceptual model is and why it is drawn before any table exists
A conceptual model is a representation of what exists in a domain and how it is related, without committing to any technology. It says nothing about tables or documents or columns or types. It says: here there are members, here there are events, and a member registers for many events.
Being technology independent is not academic purism: it is what makes it useful. Three practical consequences:
It can be discussed with people who are not in IT. BiblioRed's activities coordinator is not going to review a CREATE TABLE, but she can look at a line between "event" and "room" and say "that's wrong, we hold the summer storytelling session in the courtyard". That remark, made at the drawing stage, costs you erasing a line. Made three months later, it costs you a migration.
It works equally well for relational and for document databases. The same BiblioRed diagram can be derived into a PostgreSQL schema (04-03) or into MongoDB collections with the techniques from 03-03. The decision to embed or reference is taken afterwards, on top of an already closed conceptual model. An ER diagram is, in that sense, more durable than either of the two schemas.
It makes the holes visible. Prose tolerates ambiguity; a drawing does not. The moment you trace the line between EVENT and ROOM you are obliged to decide three things: how many rooms per event, how many events per room, and whether there can be an event with no room. The prose of R4 did not answer the third one.
The quality criterion for a conceptual model is not that it looks nice, but that every line is a verifiable statement about the business that somebody can confirm or deny.
- Entities: strong and weak
An entity is a thing in the domain with an existence of its own and about which we want to store information. An entity set (what we colloquially call "the entity") is the type: MEMBER, EVENT, ROOM. An instance is a specific individual: the member Marta Alsina, the book club on May 14.
Strong entity
A strong entity has an identifier of its own and exists by itself. MEMBER is strong: Marta Alsina exists even if she never takes out a loan. ROOM is strong, EVENT is strong, SPEAKER is strong.
Weak entity
A weak entity cannot identify itself: it needs the identity of another entity, called the owner or identifying entity. Two unmistakable signals:
- Its instances make no sense without their owner.
- Its identifier is partial: it distinguishes instances only within one owner.
The canonical example in BiblioRed is the copy. According to R2, copies are numbered within their material: "copy 3 of The Map of Time". The number 3 identifies nothing on its own —there is a copy 3 of every material in the catalog—; what identifies it is the pair (material, number). That "3" is the partial identifier or discriminant.
| Entity | Type | Identifier | Why |
|---|---|---|---|
MATERIAL |
Strong | Its own identifier | A material exists in the catalog even if no copy has been bought yet |
COPY |
Weak of MATERIAL |
(material, copy no.) | "Copy 3" means nothing unless you say of what |
REGISTRATION |
Weak of EVENT and MEMBER |
(event, member) | A registration does not exist without both |
PHONE |
Weak of MEMBER |
(member, number) | A phone number on its own is not an entity of the domain |
PAYMENT |
Weak of FINE |
(fine, payment no.) | A payment only makes sense against a specific fine |
One clarification that avoids a lot of confusion: in the conceptual model, an entity is weak for a semantic reason (it does not exist without the other one), not because we are going to give it a composite key. The latter is a common consequence, but it is a decision belonging to the logical phase, and in 04-03 we will see that it is sometimes solved with a surrogate key for convenience while remaining conceptually weak.
The question that settles it: if the owner entity disappeared, would it make sense to keep this one? If "The Map of Time" disappears from the catalog, does it make sense to keep its copy 3? No. It is weak.
- Attributes: simple, composite, multivalued, derived and identifiers
An attribute is a property of an entity or of a relationship. There are five kinds and telling them apart matters, because each one is transformed differently in 04-03.
Simple (or atomic)
A value that is indivisible as far as the business is concerned: capacity, title, registration_date. This is the normal case.
Composite
An attribute that breaks down into parts with a meaning of their own. The BiblioRed case is R13: a branch's address, today a single text field, which the website needs broken down into street, number, postal code and city.
The question for deciding whether to break it down is always the same: is anybody going to search, filter or sort by one of the parts? R13 explicitly says yes (searching by postal code), so it gets broken down. If nobody were going to do it, a single text field would be the correct and simpler option.
Multivalued
An attribute that can have several values at the same time for the same instance. In BiblioRed there are two, and both are detected by a plural in the statement of requirements:
- The subtitle languages of a DVD (R1).
- The phone numbers of a member, up to three (R12).
It is important not to confuse this with an attribute that changes over time: a copy's status changes, but at any given moment it has only one. Multivalued means simultaneous.
Derived
An attribute whose value is computed from others and therefore does not need to be stored. BiblioRed has several:
| Derived attribute | Computed from | Requirement |
|---|---|---|
| Free seats of an event | offered_seats − confirmed registrations (with companions) |
Q2, BR2 |
| Outstanding amount of a fine | amount − sum of its payments |
R11 |
| Days late for a loan | return_date − due_date |
R10 |
| Branch of an event | The branch of its room | R4 |
| No. of copies of a material | Count of its copies | BR10 |
In a diagram they are marked (classically, with a dashed oval) precisely to place on record that they are not stored. That one of them sometimes ends up being stored for performance reasons is a later, conscious decision, which has a name —denormalization— and is studied in 05-04.
Identifier (or key)
The attribute, or set of attributes, that tells one instance apart from another within the set. In the conceptual model it is called a candidate key; when one is chosen, it is the primary key. We are picking up here exactly what we saw in 02-01 and the design decision from section 10 of 04-01.
In BiblioRed the room illustrates a fine nuance: according to R3, a room name is unique only within its branch. That is, name is not an identifier of ROOM, but the pair (branch, name) is. An identifier that needs the related entity is the classic mark of a weak entity… and indeed, a room is conceptually weak with respect to its branch. We will take it that way and in 04-03 we will see what to do about it.
Summary table
| Attribute type | Signal for spotting it | Example in BiblioRed | How it is transformed (04-03) |
|---|---|---|---|
| Simple | One value, indivisible | capacity, title |
One column |
| Composite | It can be meaningfully sliced | branch address |
Several columns, or one if nobody searches by the parts |
| Multivalued | It appears in the plural | a DVD's subtitles, phone numbers | A separate table |
| Derived | "It is computed", "it is the total of" | free seats, outstanding debt | Not stored (or view / generated column) |
| Identifier | "Each X has its own number" | ISBN, copy code | Primary key or UNIQUE |
- Relationships: binary, reflexive, ternary and with attributes of their own
A relationship is an association between instances of entities. In the statement of requirements it almost always corresponds to a verb: a member registers for an event, an event is held in a room, a loan generates a fine.
Degree of the relationship
The degree is how many entities take part:
| Degree | Name | Example in BiblioRed |
|---|---|---|
| 2 | Binary | MEMBER — registers_for — EVENT |
| 1 | Reflexive (or recursive) | A material is the sequel to another material |
| 3 | Ternary | EVENT — SPEAKER — ROLE: who takes part, in which event, in what role (R7) |
The overwhelming majority of useful relationships are binary. Higher-degree ones are rare, hard to read and almost always three binary ones in disguise; we will come back to this in section 9 and more forcefully in 04-03.
Reflexive relationship
It is a relationship of an entity with itself. It is always read with two different roles, and without naming them the diagram is incomprehensible.
In BiblioRed it is not part of the v1.0 requirements, but it is useful to see because it appears in almost every domain: employee/manager, category/subcategory, material/sequel.
Relationship with attributes of its own
This is the most important concept in the section. There is data that belongs to neither of the two entities, but to their crossing.
Think about the registration from R6. Is the registration date an attribute of the member? No: a member does not have "a registration date", they have many, one per event. Is it an attribute of the event? No either, for the same reason. It is an attribute of the relationship: it exists only when that specific member crosses with that specific event.
| Relationship | Own attributes | Requirement |
|---|---|---|
MEMBER — registers_for — EVENT |
registration date, status, companions | R6 |
SPEAKER — takes_part_in — EVENT |
role, fee | R7 |
EVENT — covers — MATERIAL |
role (main / recommended) | R8 |
The infallible rule for spotting them: if an attribute needs to know both keys in order to have a value, it belongs to the relationship. "What is Elena Roig's fee?" has no answer; "what is Elena Roig's fee for the workshop on June 12?" does.
N:M relationships always admit attributes of their own; 1:N ones can have them too, although in practice they end up being absorbed into the N side.
- Cardinality: 1:1, 1:N and N:M
Cardinality (or cardinality ratio) answers this: how many instances on the other side can one instance on this side be associated with?
| Type | Meaning | Example in BiblioRed | Requirement |
|---|---|---|---|
| 1:1 | Each A with at most one B, and each B with at most one A | EVENT — has — REPORT |
R9 |
| 1:N | Each A with many Bs; each B with a single A | ROOM — hosts — EVENT |
R4 |
| N:M | Each A with many Bs and each B with many As | MEMBER — registers_for — EVENT |
R6 |
How it is determined, in practice
You ask two questions, one per direction, and write down each answer:
Question 1: An event, in how many rooms is it held? → In one (R4). Question 2: A room, how many events does it host? → Many.
Conclusion:
ROOM1 — NEVENT.
Repeated for BiblioRed's new relationships:
| Relationship | How many on the right-hand side? | How many on the left-hand side? | Cardinality |
|---|---|---|---|
BRANCH – has – ROOM |
A branch has 1..6 rooms | A room is in 1 branch | 1:N |
EVENT_TYPE – classifies – EVENT |
A type classifies many events | An event has 1 type | 1:N |
ROOM – hosts – EVENT |
A room hosts many events | An event uses 1 room | 1:N |
MEMBER – registers_for – EVENT |
A member registers for many | An event accepts many members | N:M |
SPEAKER – takes_part_in – EVENT |
A speaker takes part in many | An event has several speakers | N:M |
EVENT – covers – MATERIAL |
An event covers several materials | A material is covered in several events | N:M |
EVENT – has – REPORT |
An event has 0 or 1 report | A report belongs to 1 event | 1:1 |
LOAN – generates – FINE |
A loan generates 0..3 fines (one per reason) | A fine comes from 1 loan | 1:N |
FINE – is_settled_by – PAYMENT |
A fine receives several payments | A payment belongs to 1 fine | 1:N |
MATERIAL – has – COPY |
A material has 0..N copies | A copy belongs to 1 material | 1:N |
Minimum and maximum cardinality
The above is the maximum cardinality (1 or "many"). There is also the minimum one (0 or 1), and it is the one that expresses obligation. The full notation, widely used in Europe, writes both: (0,1), (1,1), (0,N), (1,N).
It reads: an event is held in one room at minimum and at maximum (mandatory, exactly one); a room hosts between zero and many events. That (0,N) says that a room can exist with no events at all, something that the 1:N cardinality alone did not say.
A detail that confuses many people: in the (min,max) notation the numbers are written on the side of the entity they describe, whereas in crow's foot notation the symbols are drawn next to the entity at the other end. It is the most frequent cause of diagrams being read backwards.
- Total or partial participation: the other half everybody confuses
Participation answers a different question from cardinality: does every instance of this entity have to take part in the relationship, or can there be some that do not?
- Total participation (mandatory): every instance takes part. It is drawn with a double line in Chen, or with a perpendicular stroke
|in crow's foot. - Partial participation (optional): there may be instances that do not take part. Circle
oin crow's foot.
That these are two different things is best seen with an example where they do not coincide:
| Relationship | Cardinality | Participation of A | Participation of B |
|---|---|---|---|
EVENT (A) — has — REPORT (B) |
1:1 | Partial: there are events with no report (R9) | Total: there is no report without an event |
MATERIAL (A) — has — COPY (B) |
1:N | Partial: a freshly cataloged material may not have copies yet (R2) | Total: every copy belongs to some material |
BRANCH (A) — has — ROOM (B) |
1:N | Total: every branch has at least one room (R3, "between 1 and 6") | Total: every room is in a branch |
MEMBER (A) — registers_for — EVENT (B) |
N:M | Partial: most members do not register for anything | Partial: a newly created event has nobody registered |
The classic mistake is to say "it is 1:N" and believe that says it all. Cardinality says how many; participation says whether at least one is required. They are independent axes and they combine freely:
| Partial participation | Total participation | |
|---|---|---|
| Max. 1 | 0 or 1 (optional) | Exactly 1 (mandatory) |
| Max. N | 0 or many | 1 or many |
The consequences in the logical phase are extremely direct, and that is why it is worth pinning this down now:
| Conceptual decision | Consequence in 04-03 / 04-04 |
|---|---|
| Total participation of the N side in a 1:N | The foreign key is NOT NULL |
| Partial participation of the N side | The foreign key allows NULL |
| Total participation of the 1 side ("every branch has ≥1 room") | It cannot be expressed with a simple constraint: it needs a trigger or validation in the application |
That last row is a real limitation of the relational model that is worth knowing from the outset: "every branch has at least one room" is guaranteed by no foreign key. It is documented as a business rule and you decide where it lives; we will settle the general criterion in 04-04.
- The notations: Chen versus crow's foot
The ER model has the same content in any notation; what changes is the drawing. You need to know two of them.
Chen notation (1976)
The original one. Rectangles for entities, diamonds for relationships, ovals for attributes, hanging off their entity.
┌──────────┐ ┌──────────┐
(name)────│ │ │ │────(title)
│ ROOM │ │ EVENT │
(capacity)────│ │◇────────────◇│ │────(start)
└──────────┘ hosts └──────────┘
1 NIt is very expressive —you can see the attributes, derived attributes carry a dashed oval, multivalued ones a double oval— and that is why it is used in teaching. And it is impracticable in a real domain: a diagram with twenty entities and all their attributes in ovals does not fit on any screen or on any wall.
Crow's foot notation (crow's foot, Bachman/Barker)
The one used in industry today and the one every tool implements. Entities are boxes with the list of attributes inside, relationships are lines, and the symbols at the ends encode cardinality and participation at the same time.
The symbols are read at the end that touches the entity, and they describe how many instances of that entity correspond to one on the other side:
| Symbol | ASCII | Minimum | Maximum | It reads as |
|---|---|---|---|---|
| Stroke + stroke | || |
1 | 1 | Exactly one |
| Circle + stroke | o| |
0 | 1 | Zero or one |
| Stroke + foot | |{ |
1 | N | One or many |
| Circle + foot | o{ |
0 | N | Zero or many |
Equivalence table between notations
| Concept | Chen | Crow's foot | Mermaid erDiagram |
|---|---|---|---|
| Strong entity | Plain rectangle | Box | ENTITY { ... } |
| Weak entity | Double rectangle | Box, identifying line | -- (solid line) |
| Relationship | Diamond | Labeled line | A ||--o{ B : "verb" |
| Identifying relationship | Double diamond | Solid line | -- |
| Non-identifying relationship | — | Dashed line | .. |
| Attribute | Oval | Row inside the box | Row with type and name |
| Key attribute | Underlined oval | PK mark |
PK |
| Multivalued attribute | Double oval | Does not exist: it is pulled out into another entity | Separate entity |
| Derived attribute | Dashed oval | Does not exist: it is annotated or omitted | Comment |
| Composite attribute | Oval with child ovals | Does not exist: separate columns | Separate rows |
| Max. cardinality 1 | 1 next to the diamond |
|| or o| |
|| / o| |
| Max. cardinality N | N next to the diamond |
|{ or o{ |
|{ / o{ |
| Total participation | Double line | Stroke | |
| |
| Partial participation | Single line | Circle o |
o |
| Ternary relationship | Diamond with three lines | Does not exist: it becomes an entity | Intermediate entity |
Look at the four rows that say "does not exist". Crow's foot loses expressiveness compared with Chen in multivalued, derived and composite attributes and in ternary relationships. It is not a casual defect: crow's foot sits halfway between the conceptual and the logical model, and those four concepts disappear in the transformation into tables (we will watch them die one by one in 04-03). That is why the usual practice is: think in Chen, draw in crow's foot and write down in text whatever the notation does not capture. It is exactly what we will do with BiblioRed.
A warning about mermaid
Mermaid implements crow's foot. It is an excellent tool because the diagram lives as text in the repository, next to the code, and is versioned along with it. But it inherits the four limitations above and adds one of its own: it does not draw generalization hierarchies. When they show up, in section 8, we will explain them with tables and ASCII.
- The extended ER model: generalization and specialization
The basic ER model had no way of expressing that "a book is a type of material". The extended ER model (EER) adds type hierarchies, and that is exactly what R1 needs.
BiblioRed's problem
R1 says the catalog must accommodate books, DVDs, magazines and audiobooks. They all share title, language, publisher, year and the date they were added. Each one has its own: ISBN and pages for the book; duration, format and region for the DVD; ISSN, number and frequency for the magazine; duration, narrator and format for the audiobook.
Modeling it as four independent entities breaks everything else: a copy would have to point at one of the four and there would be no telling which; nor would a loan; and query Q10 ("the ten most borrowed materials, broken down by type") would need to union four queries. Modeling it as a single entity with all the attributes leaves narrator at NULL in 90% of the rows and makes it impossible to require a book to have an ISBN.
The solution is generalization: a super-entity MATERIAL with what is common and four sub-entities with what is specific.
┌───────────────────────┐
│ MATERIAL │
│ material_id (PK) │
│ title, language, │
│ publisher, year, │
│ added_date, author │
└───────────┬───────────┘
│
╱d╲ d = disjoint
╱ ╲ double line = total
══════════╧════╧══════════
│ │ │ │
┌───────┴──┐ ┌──────┴───┐ ┌─────┴────┐ ┌────┴──────┐
│ BOOK │ │ DVD │ │ MAGAZINE │ │ AUDIOBOOK │
│ isbn │ │ duration │ │ issn │ │ duration │
│ pages │ │ format │ │ number │ │ narrator │
│ binding │ │ region │ │ frequency│ │ format │
└──────────┘ │{subtitle}│ └──────────┘ └───────────┘
└──────────┘The two axes you have to decide
Every hierarchy is characterized by two independent properties, and you have to pin them down explicitly because they change the design:
Axis 1 — Disjointness versus overlap
| Meaning | Example | |
|---|---|---|
Disjoint (disjoint, d) |
An instance belongs to at most one sub-entity | A material is a book or a DVD or a magazine or an audiobook |
Overlapping (overlapping, o) |
An instance can belong to several | A person can be a member and a speaker at the same time |
Axis 2 — Totality versus partiality
| Meaning | Example | |
|---|---|---|
| Total (double line) | Every instance of the super-entity belongs to some sub-entity | Every material is one of the four types |
| Partial (single line) | There may be instances that belong to no subtype | An employee who is neither sales nor technical |
Combining them gives four cases, and each one is implemented differently in 04-03:
| Combination | What it means | Consequence in the schema |
|---|---|---|
| Total and disjoint | Each instance is in exactly one sub-entity | Any of the three strategies can be used; "table per concrete class" is only viable here |
| Total and overlapping | Each instance is in one or more | Forces single table or table per subclass |
| Partial and disjoint | Zero or one sub-entity | Single table or table per subclass; the super-entity must be able to exist on its own |
| Partial and overlapping | The most flexible and most expensive case | Table per subclass |
BiblioRed's decision
The material hierarchy is total and disjoint. It was discussed with the client and written down:
- Total: everything that gets cataloged is one of the four types. If maps or sheet music arrive tomorrow, a subtype is added. There are no "generic" materials.
- Disjoint: a specific material is a book or an audiobook, never both. The narrated version of "The Map of Time" is a different material, with its own identifier, not the same material with two natures. This question was asked explicitly and the answer steers the whole design.
A useful counterexample to see that it is not always like this: if BiblioRed decided to model PERSON as the super-entity of MEMBER and SPEAKER, that hierarchy would be overlapping (a member can run a workshop) and partial (there are people in the system who are neither one nor the other). In v1.0 it is not done: R7 says speakers are an entity in their own right, and duplicating the name of the four people who are both is a minor price compared with the complication of an overlapping hierarchy. It is a conscious decision and it goes into the decision log.
The three strategies for taking this hierarchy into tables —single table, table per subclass, table per concrete class— with their comparison table of pros and cons are section 10 of the next lesson.
- Drawing BiblioRed, decision by decision
Now we walk through the requirements document from 04-01 and build the diagram. The value of this section lies in the questions at each step, not in the result.
Step 1 — Starting point: what already exists
Seven entities from the current schema: BRANCH, AUTHOR, MEMBER, BOOK, COPY, LOAN, RESERVATION. They are not touched on a whim, but R1 forces a structural change: BOOK becomes a sub-entity of MATERIAL, and the relationships that pointed at BOOK (COPY and RESERVATION) now point at MATERIAL. It is the deepest change in the whole extension and it is worth identifying early.
Step 2 — Rooms (R3)
- Entity? Yes: it has attributes of its own (capacity, floor, accessible) and a life cycle.
- Relationship with branch?
BRANCH1 — NROOM. - Participation? Total on both sides: every room is in a branch and every branch has between 1 and 6 rooms.
- Fine-grained decision: the name is unique only within the branch (R3). Conceptually,
ROOMis a weak entity ofBRANCHwithnameas its discriminant.
Step 3 — Event types (R5)
We apply the criteria from section 8 of 04-01: it has attributes of its own (description, standard duration), the user manages the set of values. Entity, with the relationship EVENT_TYPE 1 — N EVENT. Total participation on the event side (every event has a type), partial on the type side (there may be a freshly created type with no events).
Step 4 — Events (R4)
Strong entity, with title, description, start_time, end_time, offered_seats, status, published.
- Relationship with
ROOM: 1:N (one room, many events). - The obligatory participation question: can there be an event with no room? This is where the coordinator mentioned the summer storytelling session in the courtyard. Decision: partial participation —the room is optional—, but BR8 requires that if the event is published, it must have a room. It is recorded as a business rule for 04-04.
- Derived attribute: the event's branch is the branch of its room (R4). It is not drawn as a relationship with
BRANCH: that would be redundancy. It is noted as derived. - Derived attribute: the free seats (Q2). Not stored either.
Step 5 — Registrations (R6)
Here is the heart of the extension.
- Cardinality? A member for many events, an event with many members: N:M.
- Does it have attributes of its own? Yes: date, status, companions. N:M relationship with attributes.
- Identifier? The pair
(event, member), which is literally the rule from R6 ("a member cannot register twice for the same event"). That is: a weak entity dependent on two owners. - Participation? Partial on both sides.
This is the point where the conceptual model pays its price: in Chen it would be a diamond with three ovals hanging off it; in crow's foot you already have to draw it as an intermediate box. It is not a flaw in the notation, it is a preview of the transformation in 04-03.
Step 6 — Speakers and participations (R7)
SPEAKER: strong entity (first name, last name, email, biography, whether external).- Relationship with
EVENT: N:M with attributes (role, fee). - The trap: R7 says that in the same event a person may play more than one role. That means the pair
(event, speaker)does not identify the participation: you need the role too. Conceptually it is a ternary relationship betweenEVENT,SPEAKERandROLE.
Is ROLE a real entity? Applying the criteria from 04-01: a small set (moderator, workshop leader, guest author, presenter), stable, with no attributes of its own, nobody manages it. It is an attribute, not an entity. So instead of a pure ternary we have an N:M identified by (event, speaker, role), with role as part of the identifier. It is the usual way apparent ternaries get resolved in practice, and in 04-03 we will come back to it with rule 9.
Step 7 — Materials covered in an event (R8)
A clean N:M between EVENT and MATERIAL, with one attribute of its own: role (main / recommended). Partial participation on both sides. It is the simplest N:M example in the diagram and serves as a contrast with the two previous ones.
Step 8 — Event report (R9)
- Cardinality 1:1: an event has at most one report, a report belongs to one event.
- Participation: partial on the event side (many events have no report), total on the report side.
- The obligatory question: why not put the report's three fields inside
EVENT? Because they only have a value for events that have already been held; inEVENTthey would beNULLin most rows and there would be no way to tell "not written yet" from "zero attendees". The complete discussion of the three options for a 1:1 is rule 6 of 04-03.
Step 9 — Fines and payments (R10, R11)
FINEis related toLOAN: 1:N, because a loan can generate up to three fines, one per reason.- A debatable and therefore interesting decision: is the fine also related to the member? The member can be obtained through the loan, so that would be derived. But R10 allows fines for loss that might not come from a loan (a member who loses a material consulted in the reading room). The decision is to keep the direct relationship with
MEMBERand make the relationship withLOANa partial participation one. It goes on record as a decision, with its reason. PAYMENTis a weak entity ofFINE(1:N): a payment does not exist without its fine. Total participation on the payment side, partial on the fine side (a freshly issued fine has no payments).- Derived attribute: the outstanding amount (R11). Not stored.
Step 10 — Multivalued attributes (R12, R1)
The two plurals in the statement of requirements become weak entities:
PHONEweak ofMEMBER, withnumberas discriminant andtypeas an attribute.SUBTITLEweak ofDVD, withlanguageas discriminant.
Step 11 — Composite attribute (R13)
The branch's address is broken down into street, number, postal code and city. It generates neither an entity nor a relationship: it is a change inside the BRANCH box.
Summary of the recorded decisions
| # | Decision | Discarded alternative | Reason |
|---|---|---|---|
| D1 | BOOK becomes a subtype of MATERIAL |
Four independent entities | Copies, loans and reservations need a common entity (R1, Q10) |
| D2 | Total and disjoint hierarchy | Overlapping | An audiobook is a different material, not the same one with two faces |
| D3 | EVENT_TYPE is an entity |
A text attribute | The user must be able to add types (R5) |
| D4 | The event's room is optional | Mandatory | Outdoor events; BR8 requires it only if it is published |
| D5 | ROLE is an attribute, not an entity |
Pure ternary with a ROLE entity |
A stable set with no attributes of its own |
| D6 | FINE is related to MEMBER and to LOAN |
Only to LOAN |
There can be fines with no loan (loss in the reading room) |
| D7 | The report goes in a separate entity | Columns inside EVENT |
Massive nulls and ambiguity between "not written" and "zero" |
| D8 | PERSON is not generalized over MEMBER and SPEAKER |
Partial overlapping hierarchy | Disproportionate complexity for four cases |
| D9 | Free seats, outstanding debt and the event's branch are derived | Stored columns | Redundancy; it will be reviewed for performance in 05-04 |
- Deliverable: the complete ER diagram of the extended BiblioRed
This is the result. It is in crow's foot notation with mermaid, it integrates what exists and what is new, and it comes with the annotations that the notation cannot express.
erDiagram
BRANCHES ||--|{ ROOMS : "provides"
BRANCHES ||--o{ MEMBERS : "registers"
BRANCHES ||--o{ COPIES : "holds"
MEMBERS ||--o{ MEMBER_PHONES : "has"
MEMBERS ||--o{ LOANS : "takes out"
MEMBERS ||--o{ RESERVATIONS : "requests"
MEMBERS ||--o{ REGISTRATIONS : "registers for"
MEMBERS ||--o{ FINES : "accrues"
AUTHORS ||--o{ MATERIALS : "writes"
MATERIALS ||--o{ COPIES : "is embodied in"
MATERIALS ||--o{ RESERVATIONS : "is reserved in"
MATERIALS ||--o{ EVENTS_MATERIALS : "is covered in"
MATERIALS ||--o| MATERIALS_BOOK : "is a"
MATERIALS ||--o| MATERIALS_DVD : "is a"
MATERIALS ||--o| MATERIALS_MAGAZINE : "is a"
MATERIALS ||--o| MATERIALS_AUDIOBOOK : "is a"
MATERIALS_DVD ||--o{ DVD_SUBTITLES : "offers"
COPIES ||--o{ LOANS : "is loaned in"
LOANS ||--o{ FINES : "generates"
FINES ||--o{ PAYMENTS : "is settled with"
BRANCHES {
int branch_id PK
varchar name UK
varchar addr_street "composite R13"
varchar addr_number
char addr_postal_code
varchar addr_city
varchar phone
date opening_date
}
ROOMS {
int room_id PK
int branch_id FK "weak: name unique per branch"
varchar name
int capacity
smallint floor
boolean accessible
}
MEMBERS {
int member_id PK
varchar first_name
varchar last_name
varchar email UK
date join_date
int branch_id FK
boolean active
}
MEMBER_PHONES {
int member_id PK,FK "weak of MEMBERS"
varchar number PK
varchar type "mobile / landline / work"
}
AUTHORS {
int author_id PK
varchar first_name
varchar last_name
varchar nationality
smallint birth_year
}
MATERIALS {
int material_id PK
varchar material_type "book / dvd / magazine / audiobook"
varchar title
int author_id FK "optional"
varchar publisher
smallint publication_year
varchar language
date added_date
}
MATERIALS_BOOK {
int material_id PK,FK
varchar isbn UK
int page_count
varchar binding
}
MATERIALS_DVD {
int material_id PK,FK
int duration_min
varchar video_format
smallint region_code
}
MATERIALS_MAGAZINE {
int material_id PK,FK
varchar issn
varchar number
varchar frequency
}
MATERIALS_AUDIOBOOK {
int material_id PK,FK
int duration_min
varchar narrator
varchar audio_format
}
DVD_SUBTITLES {
int material_id PK,FK "multivalued R1"
varchar language PK
}
COPIES {
int copy_id PK
varchar code UK
int material_id FK "weak: copy_number unique per material"
smallint copy_number
int branch_id FK
varchar status
date acquisition_date
}
LOANS {
int loan_id PK
int member_id FK
int copy_id FK
date loan_date
date due_date
date return_date "null if not returned"
numeric surcharge "OBSOLETE, see FINES"
}
RESERVATIONS {
int reservation_id PK
int member_id FK
int material_id FK
date reservation_date
date expiry_date
varchar status
}
FINES {
int fine_id PK
int member_id FK
int loan_id FK "optional D6"
varchar reason "late_return / damage / loss"
numeric amount
date issue_date
varchar status
}
PAYMENTS {
int payment_id PK
int fine_id FK "weak of FINES"
timestamptz payment_date
numeric amount
varchar method
varchar reference
}
EVENT_TYPES ||--o{ EVENTS : "classifies"
ROOMS ||--o{ EVENTS : "hosts"
EVENTS ||--o{ REGISTRATIONS : "receives"
EVENTS ||--o| EVENT_REPORTS : "is documented in"
EVENTS ||--o{ PARTICIPATIONS : "counts on"
SPEAKERS ||--o{ PARTICIPATIONS : "takes part in"
EVENTS ||--o{ EVENTS_MATERIALS : "covers"
EVENT_TYPES {
int event_type_id PK
varchar code UK
varchar name
varchar description
int standard_duration_min
}
EVENTS {
int event_id PK
varchar title
varchar description
int event_type_id FK
int room_id FK "optional D4"
timestamptz start_time
timestamptz end_time
int offered_seats
varchar status
boolean published
}
REGISTRATIONS {
int event_id PK,FK "N:M with attributes R6"
int member_id PK,FK
timestamptz registration_date
varchar status
smallint companions
}
EVENT_REPORTS {
int event_id PK,FK "1:1 relationship R9"
int actual_attendees
numeric average_rating
varchar notes
date report_date
}
SPEAKERS {
int speaker_id PK
varchar first_name
varchar last_name
varchar email UK
varchar biography
boolean external
}
PARTICIPATIONS {
int event_id PK,FK "ternary resolved D5"
int speaker_id PK,FK
varchar role PK
numeric fee
}
EVENTS_MATERIALS {
int event_id PK,FK "N:M R8"
int material_id PK,FK
varchar role "main / recommended"
}
What the diagram cannot say (mandatory annotations)
As we warned in section 7, there are four concepts that crow's foot does not represent. They go here, and they are as much part of the deliverable as the drawing:
A. The generalization hierarchy. The four MATERIALS ||--o| MATERIALS_* relationships are not four independent 1:1 relationships: they are a total and disjoint hierarchy. The real constraint, which no line expresses, is:
Every material belongs to exactly one of the four sub-entities, and the sub-entity must match the value of
material_type.
B. The derived attributes. They deliberately appear in no box:
| Derived | Formula |
|---|---|
| Free seats of an event | offered_seats − Σ(1 + companions) over the confirmed registrations |
| Outstanding amount of a fine | amount − Σ payments.amount |
| Branch of an event | events → rooms → branch_id |
| Total debt of a member | Σ outstanding on their fines with status pending |
| Days late | return_date − due_date |
C. The weak entities. Mermaid draws ROOMS as a normal entity. The real statement is that a room's name is unique only within its branch, just as copy_number is within its material. Annotated in the box comments and picked up in 04-03.
D. Business rules BR1–BR10. None of them is drawable. They stay alive in the 04-01 document and become constraints in 04-04.
Validating the diagram against the queries
Before calling a conceptual model good, you walk through the list of queries in the requirements document and check that each one has a path in the diagram:
| Query | Path in the diagram | Answerable? |
|---|---|---|
| Q1 Month's agenda by branch | EVENTS → ROOMS → BRANCHES, filtering on published |
Yes |
| Q2 Free seats | EVENTS → REGISTRATIONS (derived) |
Yes |
| Q3 Registered members with phone | EVENTS → REGISTRATIONS → MEMBERS → MEMBER_PHONES |
Yes |
| Q4 History of a member | MEMBERS → REGISTRATIONS → EVENTS |
Yes |
| Q5 Occupancy by room and quarter | ROOMS → EVENTS → REGISTRATIONS |
Yes |
| Q6 Revenue by month and method | PAYMENTS |
Yes |
| Q7 Members with debt > €20 | MEMBERS → FINES → PAYMENTS (derived) |
Yes |
| Q8 Catalog by type and language | MATERIALS |
Yes |
| Q9 DVDs with Catalan subtitles | MATERIALS_DVD → DVD_SUBTITLES |
Yes |
| Q10 Most borrowed by type | LOANS → COPIES → MATERIALS |
Yes |
| Q11 Speakers with > 3 events | SPEAKERS → PARTICIPATIONS |
Yes |
| Q12 Events with no report | EVENTS anti-join with EVENT_REPORTS |
Yes |
Twelve out of twelve. If any of them had failed, the diagram would be incomplete, and it is infinitely cheaper to find that out here than after writing the DDL.
- Tools for drawing diagrams
A brief review; the detail is in lesson 09-03.
| Tool | Type | Note |
|---|---|---|
| Mermaid | Diagram as text | The one we have used. It lives in the repository, it is versioned with the code, it renders on GitHub and in editors. Limited on hierarchies and ternaries. |
| dbdiagram.io / DBML | Diagram as text, on the web | Database-specific syntax; exports to SQL. |
| PlantUML | Diagram as text | More expressive than mermaid; supports hierarchies. |
| draw.io / diagrams.net | Free-form drawing | Total control over appearance, no control over consistency. |
| pgModeler, MySQL Workbench, DBeaver | Modeling with reverse engineering | They read an existing database and draw its schema; useful for documenting what is already there. |
| Paper and whiteboard | — | Still the best option for the first two hours. |
The selection criterion, more important than the tool: for the conceptual model, prioritize speed of change (paper, whiteboard, mermaid), because you are going to redraw it ten times. For the final diagram that accompanies the schema, prioritize that it lives next to the code and is versioned with it, because a diagram on somebody's hard disk is obsolete in three weeks.
Common Mistakes and Tips
Confusing cardinality with participation. Mistake number one. "It is 1:N" does not say whether the N side can be empty. The two questions are different and you have to ask both, always, for every relationship.
Putting the crow's foot symbol on the wrong side. The symbols go next to the entity they describe, counting how many instances of that entity there are for each one on the other side. In (min,max) notation it is the other way round. A diagram read backwards produces foreign keys on the wrong side, which is one of the most expensive mistakes to correct.
Modeling an N:M relationship without asking whether it has attributes. Almost all of them do. If REGISTRATIONS had been drawn as a plain N:M with no box, there would be nowhere to put the date, the status or the companions, and R6 would have been left uncovered.
Creating entities for things that are attributes. A languages table with two columns, a statuses table with four rows, a nationalities table... They multiply the JOINs without contributing anything. Apply the five criteria from 04-01 before creating each box.
Drawing redundant relationships. If EVENT → ROOM → BRANCH, drawing EVENT → BRANCH on top of that creates a cycle in which the two routes can give different answers. Every time a cycle shows up in the diagram, it has to be justified or removed: almost always one of the edges is derived.
Forgetting the time dimension. "A member belongs to a branch" is 1:N today. If you need to know which one they belonged to in 2024, it is an N:M with dates. Asking "do you need the history?" for each relationship costs five seconds.
Putting performance attributes into the conceptual model. registered_count in EVENTS is the answer to a problem that does not exist yet. In the conceptual model it goes in as derived; if measurement proves it is needed, it is stored in the physical phase with your eyes open (05-04).
Tip: name relationships with a verb and in a single direction. "Hosts", "generates", "is settled with". A diagram with unnamed relationships, or ones called "has", is a diagram you cannot read out loud, and reading it out loud is the best validation available.
Tip: draw it first without attributes. Ten boxes and the lines between them. The structure is much easier to see and the attributes, which are the easy part, are added afterwards.
Tip: count the lines coming out of each entity. An entity with seven relationships is usually doing two jobs and is a candidate for splitting. An entity with none is usually superfluous or badly connected.
Exercises
Exercise 1 — Cardinality and participation
For each statement, determine the cardinality and the participation of each side, and express it in crow's foot notation with mermaid (A ||--o{ B : "verb"). Justify the participation by citing the requirement.
- A copy belongs to a branch; a branch holds many copies.
- A payment settles a fine; a fine is settled with several payments.
- An event type classifies many events; an event has one type.
- A member takes out many loans; a loan belongs to one member.
- An event has at most one report; a report belongs to one event.
Exercise 2 — Modeling a new extension
BiblioRed adds the following requirement to v1.1:
R15 — Lending equipment to associations. Neighborhood associations (with a name, tax ID, contact person and phone number) can borrow technical equipment (projectors, speakers, screens). Each piece of equipment has an inventory code, a description, the branch where it is kept and its status. A lending docket can include several pieces of equipment at once, and has a checkout date, an expected return date and an actual date. If a piece of equipment comes back damaged, an incident is recorded with a date, a description and an estimated repair cost. A piece of equipment can have several incidents over its lifetime.
Draw the ER diagram fragment in mermaid. State for each relationship its cardinality and participation, point out whether there are weak entities, multivalued or derived attributes, and record at least two design decisions with their reason.
Exercise 3 — Generalization hierarchy
BiblioRed is considering unifying MEMBERS and SPEAKERS under a super-entity PEOPLE with the common data (first name, last name, email, phone).
- Determine whether the hierarchy would be disjoint or overlapping and total or partial, justifying it with the requirements.
- List two concrete advantages and two concrete drawbacks of doing it.
- Decide whether you would do it in v1.0 and write the decision-log entry (decision / discarded alternative / reason).
Solutions
Solution to Exercise 1
erDiagram
BRANCHES ||--o{ COPIES : "holds"
FINES ||--o{ PAYMENTS : "is settled with"
EVENT_TYPES ||--o{ EVENTS : "classifies"
MEMBERS ||--o{ LOANS : "takes out"
EVENTS ||--o| EVENT_REPORTS : "is documented in"
| # | Cardinality | Left participation | Right participation | Justification |
|---|---|---|---|---|
| 1 | 1:N | Partial (o{): a new branch may not have any copies yet |
Total (||): every copy is in some branch |
R2 |
| 2 | 1:N | Partial: a freshly issued fine has no payments | Total: no payment exists without a fine (weak entity) | R11 |
| 3 | 1:N | Partial: a freshly created type may have no events | Total: every event has a type | R4, R5 |
| 4 | 1:N | Partial: most members have no active loans, and there are members with none at all | Total: every loan belongs to a member | Existing schema |
| 5 | 1:1 | Partial (o|): most events have no report |
Total: there is no report without an event | R9 |
A note on case 5: o| at the right-hand end is what distinguishes an optional 1:1 from a mandatory 1:1, and it is precisely what will justify putting the report in a separate table in 04-03.
Solution to Exercise 2
erDiagram
ASSOCIATIONS ||--o{ EQUIPMENT_DOCKETS : "requests"
BRANCHES ||--o{ EQUIPMENT : "keeps"
BRANCHES ||--o{ EQUIPMENT_DOCKETS : "processes"
EQUIPMENT_DOCKETS ||--|{ DOCKET_LINES : "includes"
EQUIPMENT ||--o{ DOCKET_LINES : "is lent in"
EQUIPMENT ||--o{ EQUIPMENT_INCIDENTS : "suffers"
ASSOCIATIONS {
int association_id PK
varchar name
varchar tax_id UK
varchar contact_name
varchar contact_phone
}
EQUIPMENT {
int equipment_id PK
varchar inventory_code UK
varchar description
int branch_id FK
varchar status
}
EQUIPMENT_DOCKETS {
int docket_id PK
int association_id FK
int branch_id FK
date checkout_date
date due_date
date return_date "null if not returned"
}
DOCKET_LINES {
int docket_id PK,FK "weak of EQUIPMENT_DOCKETS"
int equipment_id PK,FK
varchar return_status
}
EQUIPMENT_INCIDENTS {
int incident_id PK
int equipment_id FK
int docket_id FK "optional"
date incident_date
varchar description
numeric estimated_cost
}
| Relationship | Cardinality | Participation | Note |
|---|---|---|---|
ASSOCIATIONS – EQUIPMENT_DOCKETS |
1:N | Partial / Total | An association may never have requested anything |
EQUIPMENT_DOCKETS – DOCKET_LINES |
1:N | Total on both sides (||--|{) |
A docket with no equipment on it makes no sense |
EQUIPMENT – DOCKET_LINES |
1:N | Partial / Total | A piece of equipment may never have been lent |
EQUIPMENT – EQUIPMENT_INCIDENTS |
1:N | Partial / Total | Most equipment has no incidents |
- Weak entity:
DOCKET_LINES, identified by(docket_id, equipment_id). It is the N:M between docket and equipment, and it appears because "a docket includes several pieces of equipment" (the plural in the statement). - Multivalued attribute: none explicitly. If several contact phone numbers per association were requested, another one would appear.
- Derived attributes: days late on the docket (
return_date − due_date), total incident cost for a piece of equipment.
Recorded decisions:
| Decision | Discarded alternative | Reason |
|---|---|---|
ASSOCIATIONS is an entity of its own, not a type of MEMBERS |
Reusing members with an is_association field |
The attributes are different (tax ID, contact person) and so are the lending rules; mixing them would produce massive nulls and conditional CHECKs |
EQUIPMENT_INCIDENTS hangs off EQUIPMENT, not off DOCKET_LINES |
Hanging it off the docket | A piece of equipment can break outside a loan; the link to the docket is kept as optional so we know who returned it in that state |
| The contact person is an attribute, not an entity | A CONTACTS entity |
In v1.1 it has no attributes of its own and no independent life cycle; if several contacts per association are needed tomorrow, it gets promoted |
Solution to Exercise 3
1. Nature of the hierarchy.
- Overlapping: nothing stops a BiblioRed member from running a workshop. R7 explicitly says speakers can be "own staff or external professionals", and neither category rules out also being a member. A person could be a member and a speaker at the same time.
- Partial: if the super-entity
PEOPLEalso covered, for example, association contacts or administrative staff, there would be people who are neither members nor speakers. Even limiting it to the two current subtypes, it is partial in the sense that nothing guarantees every registered person is of one of the two types.
Therefore: a partial and overlapping hierarchy, the most flexible combination and also the most expensive to implement.
2. Advantages and drawbacks.
| Advantages | Drawbacks |
|---|---|
| The contact details of a person who is both a member and a speaker are in a single place: "one thing, one place" is satisfied and an email change is made once | Every query about members now needs an extra JOIN: Q3, Q4 and Q7 get more complicated without gaining anything |
| The email address would be unique at person level, not per table, preventing the same person from appearing with two different addresses | An overlapping hierarchy rules out the table-per-concrete-class strategy and forces you to check consistency between subtypes |
| It makes future subtypes easier (staff, association contact) without duplicating fields | Over-engineering for four real cases of overlap: permanent complexity paid for a marginal benefit |
3. Decision for v1.0: do not do it. Log entry:
| Decision | Discarded alternative | Reason |
|---|---|---|
MEMBERS and SPEAKERS stay as independent entities; the PEOPLE super-entity is not created |
A partial, overlapping generalization hierarchy over PEOPLE |
Coordination estimates four cases of overlap out of 12,000 members. The cost is an extra JOIN in every member query (Q3, Q4, Q7) plus consistency checks between subtypes, against the benefit of avoiding four duplicates. It will be revisited if the catalog of people grows with more subtypes (staff, association contacts). |
This last row illustrates the criterion from section 12.5 of lesson 04-01: generalization is correct from the theoretical point of view and over-engineering from the practical one. The design is not the purest model, but the one best suited to the real problem.
Conclusion
This lesson has turned fourteen requirements written in prose into a complete conceptual model.
- A conceptual model describes what exists and how it is related, without committing to any technology. That lets it be discussed with people outside IT, serve equally well for relational and document databases, and —most valuable of all— make visible the holes that prose tolerates.
- Entities are strong or weak. A weak entity cannot identify itself: it needs its owner's identity, and its identifier is partial. In BiblioRed the copies, the registrations, the phone numbers, the payments and the rooms are weak.
- Attributes come in five kinds and each one will be transformed differently: simple (a column), composite (several columns), multivalued (a separate table), derived (not stored) and identifier.
- Relationships are detected in the verbs. Their degree is almost always binary; reflexive ones need named roles and ternary ones almost always hide something simpler. The decisive point: relationships can have attributes of their own, and the infallible rule for spotting them is that they need both keys in order to have a value.
- Cardinality (1:1, 1:N, N:M) is determined with two questions, one per direction. Participation (total or partial) answers a different question —whether there can be instances that do not take part— and is an independent axis. Confusing them is the most frequent modeling mistake.
- Two notations are covered: Chen, more expressive and only viable in small domains, and crow's foot, the industry one, which loses four concepts (multivalued, derived and composite attributes and ternaries) precisely because they are about to disappear in the transformation into tables. The correct practice is to think in Chen, draw in crow's foot and write down in text whatever the drawing does not capture.
- The extended ER model contributes generalization/specialization, with two axes that must always be pinned down: disjoint or overlapping and total or partial. BiblioRed's material hierarchy is total and disjoint, and that decision conditions everything that comes afterwards.
- The BiblioRed diagram was built in eleven steps, with nine recorded decisions together with their reason and their discarded alternative.
- The deliverable is the complete ER diagram in mermaid, with twenty-one entities, plus the four annotations the notation cannot express: the hierarchy, the derived attributes, the weak entities and the ten business rules.
- The diagram was validated against the twelve queries in the requirements document, and all twelve have a path. That check is what separates a pretty drawing from a usable model.
We have the drawing, we have the annotations and we have the validation. What we still do not have is a single table. In the next lesson, 04-03 Transforming ER Diagrams into Relational Schemas, we will learn the algorithm that turns this diagram into CREATE TABLE: ten mechanical rules —strong entity, composite attribute, multivalued, derived, 1:N, 1:1, N:M, weak entity, ternary and hierarchy— each with its BiblioRed example and its SQL. That is where diamonds become foreign keys, multivalued attributes become tables and that total, disjoint material hierarchy is finally resolved by choosing between three strategies with very different consequences.
Database Fundamentals
Module 1: Introduction to Databases
- Basic Database Concepts
- Types of Databases
- History and Evolution of Databases
- Database Management Systems and Architecture
Module 2: Relational Databases
- The Relational Model
- The SQL Language
- Basic SQL Operations
- Multi-Table Queries: JOINs and Subqueries
- Data Aggregation and Grouping
- Referential Integrity
Module 3: Non-Relational Databases
- Introduction to NoSQL
- Types of NoSQL Databases
- Data Modeling in NoSQL
- Comparing Relational and Non-Relational Databases
Module 4: Schema Design
- Schema Design Principles
- Entity-Relationship (ER) Diagrams
- Transforming ER Diagrams into Relational Schemas
- Data Types and Constraints
Module 5: Normalization
Module 6: Transactions, Performance, and Security
- Transactions and ACID Properties
- Concurrency and Isolation Levels
- Indexes and Query Optimization
- Security, Permissions, and Backups
Module 7: Practical Exercises
- SQL Exercises
- Schema Design Exercises
- Normalization Exercises
- Advanced Query and Transaction Exercises
Module 8: Case Studies
- Case Study: Relational Database
- Case Study: Non-Relational Database
- Case Study: Polyglot Persistence
