Skip to content

Mermaid cardinalities

db2md does not hardcode ||--o{ for every foreign key. Each relationship operator is inferred from the child table’s constraints:

  1. Uniqueness of the FK columns (UNIQUE constraint, unique index, or PK exactly equal to the FK columns)
  2. 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).

PARENT  <operator>  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:

"courses" }o--o{ "students" : "enrollments"

(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.