Skip to content

Relationships

Relationships (refs) define foreign key connections between tables. They are rendered as lines with arrows on the ERD canvas, and produce FOREIGN KEY constraints or ALTER TABLE statements in the generated SQL.

Standalone Syntax

Ref: table1.column > table2.column

A standalone Ref is defined outside of any table block. It connects a column in one table to a column in another.

Relationship Types

SymbolTypeMeaning
>Many-to-oneMany records in the left table reference one record in the right table
<One-to-manyOne record in the left table is referenced by many records in the right table
-One-to-oneExactly one record on each side
<>Many-to-manyMultiple records on both sides

Many-to-One (>)

The most common relationship type. Use this when a column holds a foreign key pointing to another table's primary key.

// Each post belongs to one user, but a user can have many posts
Ref: posts.user_id > users.id

On the canvas, the arrow points from posts.user_id toward users.id, with a "many" indicator on the posts side and a "one" indicator on the users side.

One-to-Many (<)

The reverse of many-to-one. Functionally equivalent — it just reads in the opposite direction:

// One user has many posts
Ref: users.id < posts.user_id

This produces the same foreign key as Ref: posts.user_id > users.id. Choose whichever direction reads more naturally for your use case.

One-to-One (-)

Use this when each record in one table corresponds to exactly one record in another:

// Each user has exactly one profile
Ref: users.id - profiles.user_id

A one-to-one relationship is typically enforced by adding a UNIQUE constraint on the foreign key column (profiles.user_id).

Many-to-Many (<>)

Represents an association where records on both sides can relate to multiple records on the other side:

// Students can enroll in many courses; courses have many students
Ref: students.id <> courses.id

TIP

In practice, most databases implement many-to-many relationships through a junction (join) table. You may prefer to model this explicitly:

Table enrollments {
  student_id int [ref: > students.id]
  course_id int [ref: > courses.id]
}

Named Relationships

Give a ref a name for clarity in complex schemas:

Ref user_posts: posts.user_id > users.id
Ref post_comments: comments.post_id > posts.id

The name appears as a label and makes it easier to identify specific relationships in large diagrams.

Inline Relationships

Instead of standalone Ref statements, you can define relationships directly inside a table's column settings using the ref: setting:

Table posts {
  id int [pk, increment]
  user_id int [not null, ref: > users.id]
  category_id int [ref: > categories.id]
}

This is equivalent to writing separate Ref: posts.user_id > users.id and Ref: posts.category_id > categories.id statements.

When to Use Inline vs Standalone

Inline refs are convenient when:

  • The relationship is simple (single column)
  • You want to see the foreign key right next to the column definition
  • You prefer keeping related information together

Standalone refs are better when:

  • You need named relationships
  • You want to add delete/update actions
  • You have composite foreign keys
  • You prefer a separate section listing all relationships for easy scanning

Self-Referential Relationships

A table can reference itself. This is common for hierarchical data:

Table employees {
  id int [pk, increment]
  name varchar(100) [not null]
  manager_id int [ref: > employees.id]
}

// Equivalent standalone syntax:
Ref: employees.manager_id > employees.id

Another common example is a categories tree:

Table categories {
  id int [pk, increment]
  name varchar(100) [not null]
  parent_id int
}

Ref: categories.parent_id > categories.id

On the canvas, self-referential relationships render as a line that loops back to the same table.

Composite Foreign Keys

When a foreign key spans multiple columns, use parentheses to group them:

Table order_items {
  order_id int [not null]
  product_id int [not null]
  warehouse_id int [not null]
  quantity int [not null]
}

Table inventory {
  product_id int [not null]
  warehouse_id int [not null]
  stock int [not null]
}

// Composite foreign key: both columns must match together
Ref: order_items.(product_id, warehouse_id) > inventory.(product_id, warehouse_id)

The column order matters — the first column on the left maps to the first column on the right, and so on.

Delete / Update Actions

Control what happens to referencing rows when the referenced row is deleted or updated:

Ref: posts.user_id > users.id [delete: cascade, update: no action]
Ref: comments.post_id > posts.id [delete: cascade]
Ref: orders.customer_id > customers.id [delete: set null]

Supported Actions

ActionOn DeleteOn Update
no actionRaise an error if referencing rows exist (checked at end of transaction). This is the default behavior.Raise an error if the referenced key changes.
restrictRaise an error immediately if referencing rows exist. Similar to no action but checked immediately.Raise an error immediately if the referenced key changes.
cascadeDelete all rows that reference the deleted row.Update the foreign key in all referencing rows to match the new value.
set nullSet the foreign key column(s) to NULL in all referencing rows. The column must be nullable.Set the foreign key column(s) to NULL.
set defaultSet the foreign key column(s) to their default value. The column must have a default defined.Set the foreign key column(s) to their default value.

WARNING

set null requires that the foreign key column allows NULL values. If the column is defined with not null, the database will raise an error at runtime.

TIP

cascade on delete is useful for parent-child relationships (e.g., deleting an order also deletes its line items). Be careful using it on user-facing tables — accidentally deleting a user could cascade and remove all their data.

Canvas Rendering

Relationships are displayed as lines connecting the related columns on the ERD canvas:

  • Many-to-one (>) — Line with a "crow's foot" (many) on the source side and a single line (one) on the target side
  • One-to-one (-) — Single line on both sides
  • Many-to-many (<>) — Crow's foot on both sides

You can drag and rearrange tables on the canvas to keep relationship lines clean and readable. The lines automatically re-route as you move tables around.

Complete Example

Table users {
  id int [pk, increment]
  email varchar(255) [not null, unique]
}

Table posts {
  id int [pk, increment]
  user_id int [not null, ref: > users.id]
  title varchar(200) [not null]
}

Table comments {
  id int [pk, increment]
  post_id int [not null]
  user_id int [not null]
  parent_id int
  body text [not null]
}

// Standalone refs with delete actions
Ref: comments.post_id > posts.id [delete: cascade]
Ref: comments.user_id > users.id [delete: cascade]

// Self-referential: threaded comments
Ref: comments.parent_id > comments.id [delete: cascade]

This example combines inline and standalone refs, includes delete actions, and demonstrates a self-referential relationship for threaded comments.