S SCHEMA.BIZ

Last updated:

SQL to Diagram

Paste your CREATE TABLE statements and instantly generate an interactive entity-relationship diagram. Auto-detects PostgreSQL, MySQL, SQLite, and SQL Server syntax.

What this tool does

Most production databases were not built from a diagram. They started with a single table, grew an INSERT trigger, sprouted a junction table, absorbed a side project, and arrived at the present as a set of migration files that nobody has read end-to-end since 2019. SQL to Diagram is the inverse operation: paste the DDL — every CREATE TABLE statement, in any order, with whatever inline or table-level constraints exist — and the tool reconstructs the entity-relationship graph that has been implicit in the SQL all along.

The parser is purpose-built for the messy SQL you actually have rather than the textbook SQL examples take you to. It understands inline references on a column, named foreign-key constraints declared at the table level, composite primary keys, parameterized data types like NUMERIC(10,2), dialect-specific identity declarations, both quoting styles, and embedded comments. The layout engine (ELK) then arranges the tables to minimize edge crossings, so even a 30-table schema stays readable on a single page. Like the rest of the site, none of your DDL leaves the browser — paste schemas with regulated table names or proprietary domain models without a network round-trip.

When to use SQL to Diagram

Onboarding to a database the previous owner never documented. Pull every CREATE TABLE from the migration history, paste them in, and you have a starting map of the domain in under a minute. Use the diagram to identify the central tables (the ones with the most incoming foreign keys) and start your reading from there.

Producing a diagram for a project handover. A README with an embedded ER diagram is more durable than a Confluence page that nobody updates. Generate the SVG from the DDL, commit it to the repo alongside the schema, and let CI regenerate it whenever the schema changes.

Reverse-engineering a third-party schema. When you have credentials to a vendor's reporting database and need to build dashboards on top of it, dumping the schema as DDL and visualizing the relationships is faster than clicking through every table in their admin UI.

Reviewing a migration pull request. A migration that adds two tables and a junction table is hard to evaluate from the SQL diff alone. Paste the proposed final state of the affected tables to see the new relationships rendered, and confirm the join cardinalities match the design intent.

Walkthrough with a real example

Paste this DDL for a minimal blog backend with users, posts, tags (many-to-many), and threaded comments:

CREATE TABLE users (
  id            BIGSERIAL PRIMARY KEY,
  username      VARCHAR(64) NOT NULL UNIQUE,
  email         VARCHAR(255) NOT NULL UNIQUE,
  created_at    TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE posts (
  id            BIGSERIAL PRIMARY KEY,
  author_id     BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  title         VARCHAR(200) NOT NULL,
  body          TEXT NOT NULL,
  published_at  TIMESTAMPTZ
);

CREATE TABLE tags (
  id    SERIAL PRIMARY KEY,
  slug  VARCHAR(64) NOT NULL UNIQUE
);

CREATE TABLE post_tags (
  post_id  BIGINT NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
  tag_id   INTEGER NOT NULL REFERENCES tags(id) ON DELETE RESTRICT,
  PRIMARY KEY (post_id, tag_id)
);

CREATE TABLE comments (
  id           BIGSERIAL PRIMARY KEY,
  post_id      BIGINT NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
  author_id    BIGINT NOT NULL REFERENCES users(id),
  parent_id    BIGINT REFERENCES comments(id) ON DELETE CASCADE,
  body         TEXT NOT NULL,
  created_at   TIMESTAMPTZ NOT NULL DEFAULT now()
);

The parser identifies five tables. Five rectangular nodes appear: users, posts, tags, post_tags, and comments. Each row inside a node shows a column name with its type and badges for constraints — PK in amber for primary keys, FK in blue for foreign keys, NN for NOT NULL, UQ for UNIQUE. So users.id is marked PK; users.username and users.email get NN and UQ; posts.author_id is marked FK and a blue edge runs from it to users.id; both columns of post_tags are simultaneously PK and FK because the table uses a composite primary key over its two foreign keys.

The edges encode more than identity. The edge from posts.author_id to users.id carries the ON DELETE CASCADE annotation, which is the single most important piece of metadata for understanding what happens when a user record is removed: every post by that user goes with it, then every comment on those posts cascades again, and so on through the graph. The comments.parent_id column produces a self-referencing edge that loops from the table back to itself — the visual signature of a tree-of-comments structure. The post_tags table sits between posts and tags with an edge to each, and its rectangle is visually narrower than the others because it has no payload columns of its own — a shape that experienced readers immediately recognize as a junction table for a many-to-many relationship. None of this analysis is in the original SQL text; it emerges from the parsed structure.

Database schema concepts you should know

DDL versus DML. Data Definition Language statements (CREATE, ALTER, DROP, TRUNCATE) define the structure of the database. Data Manipulation Language statements (INSERT, UPDATE, DELETE, SELECT) operate on the data inside that structure. SQL to Diagram only reads DDL; the data is irrelevant to the shape.

Surrogate versus natural primary keys. A surrogate key is a meaningless identifier (a BIGSERIAL, a UUID) generated by the database. A natural key is a real-world identifier (an email address, an SKU, an ISBN) that uniquely identifies the row in its domain. Surrogate keys are easier to evolve — you can change a customer's email without rewriting every foreign key — but natural keys make joins more readable. Most modern schemas use surrogate keys with a UNIQUE constraint on the natural key.

Foreign keys and referential actions. A foreign key constraint says "the value in this column must exist as a primary key in another table." The ON DELETE and ON UPDATE clauses say what to do when the referenced row is removed or its key changes: CASCADE propagates the change, RESTRICT blocks it, SET NULL nullifies the referencing column, SET DEFAULT falls back to the column default, and NO ACTION (the default) defers the check to commit time. Choosing the right action is a domain decision — CASCADE is correct for child rows that have no meaning without their parent (line items of a deleted order) and dangerous everywhere else.

Junction tables and many-to-many relationships. A junction table (also called an associative entity or link table) materializes a many-to-many relationship as two foreign keys. The convention is to make the composite of those two foreign keys the primary key of the junction table itself. Adding a payload column (a created_at timestamp, a role label, an ordering integer) turns it from a pure association into a relationship-with-attributes, which is a common evolution.

Constraints, named and anonymous. Every constraint can be given a name with the CONSTRAINT keyword. Named constraints are easier to drop and recreate during migrations and produce more informative error messages when violated. Anonymous constraints work but get auto-generated names like posts_author_id_fkey that change between database versions and dialects, which makes migrations across environments harder to reason about.

Data type selection. Choose the narrowest type that fits: SMALLINT for small enums, INTEGER for most counts, BIGINT for surrogate keys you expect to outlive 2 billion rows. Use VARCHAR with a real maximum length only when the length is a domain constraint (an ISO country code is two characters); use TEXT otherwise. NUMERIC for money. TIMESTAMPTZ rather than TIMESTAMP for anything time-zone-aware. Type choices are visible in the diagram and influence both storage cost and JOIN performance.

Common mistakes

Using TEXT for every string column. TEXT works in PostgreSQL and SQLite but loses domain meaning. A column intentionally bounded to 64 characters carries information that downstream code can rely on; an unbounded TEXT invites a future row that breaks an HTML form's max-length attribute. Pick a real maximum when one exists.

Forgetting indexes on foreign-key columns. Most databases automatically index the primary key but not the columns that reference it. The first time you delete a parent row, the database has to scan the entire child table to enforce the constraint. Always index FK columns explicitly.

Circular foreign-key references with NOT NULL on both sides. Two tables that each require a row in the other create a chicken-and-egg problem at insert time. Either make one side nullable and populate it after both rows exist, or use deferrable constraints with a single transaction.

ON DELETE CASCADE on tables you cannot afford to lose. CASCADE propagates silently. A misclick that deletes a tenant row can take the tenant's entire dataset with it before anyone has time to react. Reserve CASCADE for child-of-parent relationships where the child is meaningless without the parent, and use RESTRICT or NO ACTION elsewhere.

Reserved words as table or column names.

-- Will fail in PostgreSQL: 'user' is a reserved keyword
CREATE TABLE user (
  id INT PRIMARY KEY,
  name TEXT
);

-- Quote it, or pick a less ambiguous name
CREATE TABLE "user" (
  id INT PRIMARY KEY,
  name TEXT
);

-- Better: rename to avoid the dance entirely
CREATE TABLE app_user (
  id INT PRIMARY KEY,
  name TEXT
);

Quoting works but turns every reference to the table into a dance with quotes for the rest of the project. Pick a name that does not collide with the SQL grammar to begin with.

FAQ

Does the parser handle partitioned tables and table inheritance?

Partitioned tables (PostgreSQL declarative partitioning, MySQL PARTITION BY) render as a parent node with each partition listed as a child grouping; PostgreSQL inheritance (INHERITS) draws a dashed edge from child to parent. Neither pattern affects the foreign-key relationships, so the diagram remains accurate for query-planning purposes.

What does it do with views, stored procedures, triggers, and indexes?

CREATE VIEW statements are recognized and rendered as a separate node type with its derived columns, but procedural code (functions, triggers, stored procedures) is parsed enough to be skipped without breaking the surrounding DDL. Index definitions show up as annotations on the relevant table — useful for spotting missing foreign-key indexes.

How large a schema can this handle in the browser?

The parser and ELK layout engine handle several hundred tables comfortably. Around a thousand tables you will start to see the layout step take a few seconds and pan-zoom slow down on lower-end machines. For databases that large the diagram is usually only useful as a per-domain subset anyway, so paste in just the tables for one bounded context.

Can I paste a partial schema with foreign keys to tables I have not included?

Yes. Foreign-key references to tables that are not present in the input are rendered as a dashed edge to a placeholder node, labeled with the missing table name. This is how to focus on one slice of a large schema without having to dump the whole thing — paste the tables you care about and the cross-domain links remain visible.

Which SQL dialects does the auto-detection actually distinguish?

PostgreSQL (SERIAL, BIGSERIAL, JSONB, ARRAY types), MySQL (AUTO_INCREMENT, ENGINE=, backticked identifiers, UNSIGNED), SQLite (INTEGER PRIMARY KEY autoincrement, type affinity), and SQL Server (IDENTITY, NVARCHAR, bracketed identifiers). Auto-detection scores the input against each dialect's signature keywords; you can override it from the dropdown when the input mixes styles.

Related tools and guides

  • Visual Schema Designer — design schemas with drag-and-drop tables instead of writing DDL by hand. Useful when you are starting from a whiteboard sketch rather than an existing database.
  • SQL Query Visualizer — once you have the schema diagrammed, paste a SELECT query in here to highlight which tables and columns the query touches.
  • Schema Diff — compare two snapshots of a schema and produce the migration SQL that transforms one into the other, with safety checks on destructive changes.
  • Database Design Fundamentals — covers normalization, key strategies, and indexing patterns that produce the kind of schemas that diagram cleanly.
  • Complete Guide to JSON Schema — for when your database stores JSON columns, the schema for those columns is its own modeling problem.