Mermaid cardinalities¶
db2md does not hardcode ||--o{ for every foreign key. Each relationship operator is inferred from the child table’s constraints:
- Uniqueness of the FK columns (UNIQUE constraint, unique index, or PK exactly equal to the FK columns)
- Nullability of those same columns
The left side of the operator is the referenced (parent) table; the right side is the table that owns the FK (child).
Per the Mermaid syntax, the left marker is the cardinality of the first entity with respect to the second, and the right marker the reverse. With the layout above:
- left marker — how many parent rows a child row points to → set by the FK nullability
- right marker — how many child rows a parent row owns → upper bound set by the FK uniqueness, lower bound always zero
Quick reference¶
| Case | Operator | Meaning |
|---|---|---|
| Unique + NOT NULL | ||--o| |
1 – 0..1 — every child has exactly one parent; a parent has at most one child |
| Unique + nullable | o|--o| |
0..1 – 0..1 — optional one-to-one |
| Non-unique + NOT NULL | ||--o{ |
1 – 0..N — every child has exactly one parent; a parent has any number of children |
| Non-unique + nullable | o|--o{ |
0..1 – 0..N — optional many side |
| Junction table (2 FKs) | }o--o{ |
N–N — many-to-many between the two referred tables |
Why the child side never says “one or more”
A foreign key constrains the child only. Nothing in SQL DDL can force a parent row to own at least one child — that is a mandatory-participation constraint, which no dialect expresses. Emitting ||--|{ would claim more than the schema guarantees, so db2md keeps the zero on the child side.
Note
These are schema heuristics. Real cardinalities in data can be narrower (e.g. a non-unique FK that is always unique in practice). The diagram reflects what the constraints guarantee, never more.
How uniqueness is detected¶
An FK is treated as unique when its constrained columns match any of:
- the table’s primary key (exactly the same column set)
- a UNIQUE constraint on exactly those columns
- a unique index on exactly those columns
Otherwise the FK is non-unique → many side on the child.
How nullability is detected¶
An FK is treated as nullable if any constrained column is nullable.
Nullability drives the parent end only:
| Nullable? | Parent end (left) |
|---|---|
| No | || (exactly one) |
| Yes | o| (zero or one) |
Uniqueness drives the child end:
| Unique? | Child end (right) |
|---|---|
| No | o{ (zero or more) |
| Yes | o| (zero or one) |
Examples¶
One-to-many (required)¶
CREATE TABLE users (
id INTEGER PRIMARY KEY
);
CREATE TABLE notes (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users (id)
-- no UNIQUE on user_id
);
→ "users" ||--o{ "notes"
Every note has exactly one user; a user may have any number of notes — including none, which is why the child end is o{ and not |{.
One-to-one¶
CREATE TABLE users (
id INTEGER PRIMARY KEY
);
CREATE TABLE profiles (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL UNIQUE REFERENCES users (id)
);
→ "users" ||--o| "profiles"
Every profile belongs to exactly one user; a user has at most one profile (the UNIQUE), possibly none.
Optional many¶
CREATE TABLE authors (
id INTEGER PRIMARY KEY
);
CREATE TABLE posts (
id INTEGER PRIMARY KEY,
author_id INTEGER NULL REFERENCES authors (id)
);
→ "authors" o|--o{ "posts"
A post may have no author; an author may have zero or more posts.
Many-to-many (junction table)¶
A table is treated as a junction when it has exactly two foreign keys and either:
- its primary key is exactly the union of those FK columns, or
- it has no columns outside those FK columns
CREATE TABLE students (id INTEGER PRIMARY KEY);
CREATE TABLE courses (id INTEGER PRIMARY KEY);
CREATE TABLE enrollments (
student_id INTEGER NOT NULL REFERENCES students (id),
course_id INTEGER NOT NULL REFERENCES courses (id),
PRIMARY KEY (student_id, course_id)
);
db2md still draws the two FK edges to enrollments, and adds:
(order of table names is sorted alphabetically for stability).
erDiagram
"students" {
INTEGER id PK
}
"courses" {
INTEGER id PK
}
"enrollments" {
INTEGER student_id PK, FK
INTEGER course_id PK, FK
}
"students" ||--o{ "enrollments" : "student_id"
"courses" ||--o{ "enrollments" : "course_id"
"courses" }o--o{ "students" : "enrollments"
Operator cheat sheet (Mermaid)¶
| Left form | Right form | Multiplicity | Used by db2md |
|---|---|---|---|
|| |
|| |
Exactly one | left end only |
|o |
o| |
Zero or one | both ends |
}o |
o{ |
Zero or more | right end, and both ends for N–N |
}| |
|{ |
One or more | never — see the note above |
Marker orientation
Mermaid lexes o| and |o as the same ZERO_OR_ONE token (likewise o{/}o
and |{/}|), so o|--o{ and |o--o{ render identically. db2md writes the
o| form on the left; rewriting it as |o is cosmetic only.
See the Mermaid ER diagram docs for the full syntax.