In this guide
FROM SCHEMA TO DIAGRAM
SQL to ERD guide
Turn your database schema into a diagram. Learn supported SQL, preview data, and query your model.
AI-Ready SQL Syntax Contract
Use this page as a compact contract when asking an AI to write or edit SQL for Erd Go. Prefer the syntax patterns below, keep statements complete and semicolon-terminated, and avoid unsupported database objects unless you only need raw SQL outside Erd Go. SQLite, PostgreSQL, MySQL, and MSSQL syntax are supported across the shared subset below. SQL-to-ERD conversion runs entirely in the browser, and its parser and preview engines are locked in by thousands of unit and integration tests covering real-world production schemas (SaaS, banking, social, analytics, EHR).
SQLite
INTEGER PRIMARY KEY rowid rules, typeless columns, AUTOINCREMENT, WITHOUT ROWID, validated STRICT tables, FK actions, and migration replay
PostgreSQL
ENUM & composite types, arrays, JSONB, SERIAL, identity columns, NULLS NOT DISTINCT, expression indexes, schema identity, and quoted-name case sensitivity
MySQL
AUTO_INCREMENT, inline ENUM & SET, backticks, COMMENT, UNSIGNED/SIGNED, inline indexes, CHANGE/MODIFY COLUMN, and FIRST/AFTER placement
MSSQL
IDENTITY, NVARCHAR(MAX), bracketed names, clustered indexes, GO batches, ALTER COLUMN, computed columns, named defaults, and temporal periods
DDL
CREATE TYPE, CREATE TABLE, ALTER TABLE migration replay, CREATE INDEX, and safe DROP statements for schema shape
DML / SELECT
INSERT, UPDATE, DELETE for preview data, plus read-only SELECT syntax for joins, CTEs, aggregates, windows, and reports
Quick Start
Use this as a model response when asking an AI for Erd Go-compatible SQL: custom types first, tables next, relationship constraints after tables when useful, then sample DML. Keep read-only SELECT queries separate.
-- Define custom types
CREATE TYPE status_enum AS ENUM (
'active', 'inactive', 'suspended'
);
-- Create tables with constraints
CREATE TABLE departments (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE
);
CREATE TABLE employees (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email TEXT UNIQUE NOT NULL,
status status_enum DEFAULT 'active',
department_id INTEGER,
salary NUMERIC(10, 2) DEFAULT 0
);
-- Add constraints via ALTER TABLE
ALTER TABLE employees
ADD CONSTRAINT fk_dept
FOREIGN KEY (department_id)
REFERENCES departments(id);
-- Add sample data after schema DDL
INSERT INTO departments (id, name) VALUES
(1, 'Engineering'),
(2, 'Design');
INSERT INTO employees (id, name, email, status, department_id, salary) VALUES
(1, 'Alice', '[email protected]', 'active', 1, 1500.00),
(2, 'Bob', '[email protected]', 'inactive', 2, 1200.00);
UPDATE employees
SET status = 'active'
WHERE id = 2;REFERENCES or ALTER TABLE ADD FOREIGN KEY. Both forms contribute the same relationship metadata.INSERT, UPDATE, and DELETE after the schema DDL. The script is evaluated top-to-bottom, so sample rows should reference tables and types that already exist.SELECT e.name, d.name AS department, e.salary
FROM employees e
JOIN departments d ON d.id = e.department_id
WHERE e.status = 'active'
ORDER BY e.salary DESC;SQL tabs & file import
Start a new SQL file with a sample, a blank Schema tab, or Import SQL Files. The sample separates schema and data into tabs. Add SQL tab creates another editor tab; double-click its title to rename it.
Active tabs compile together in their visible order for the ERD, Data View, Query View, and AI requests. An inactive tab remains a saved draft and is excluded from those workflows. Place types and tables before statements that depend on them.
Import SQL files accepts multiple text SQL files and reviews parsing issues together before import. This helps resolve references between files. Use the tab order to keep dependencies and migration steps intentional.
Use arrow keys and Home/End in the tab strip to switch tabs, Alt+Left/Right to reorder them, and Shift+Alt+F to format SQL in the editor.
Edit and review the diagram
SQL is the source of schema meaning. Use Add Table to construct a table, or select table and column actions on the canvas to edit columns and connections. Canvas schema edits update the owning SQL tabs as well as the diagram.
Removing a column previews affected connections, keys, indexes, and dependent SQL. Review the removal or cascade before confirming. Complex migrations or unsupported dependent statements can require direct SQL editing.
Use the ERD health score to inspect structural findings and opt-in relationship suggestions. Suggested relationships need review; they do not replace declared foreign keys or establish business rules automatically.
Arrange tables with Layout Mode, zoom or Fit to Screen, find tables with Table Navigator, and use Table Colors or the minimap to navigate a large diagram. Hover over or focus a toolbar control for its help text.
AI review inside the SQL editor
- Enter a specific change in Ask AI to edit SQL and choose an available model. Set Gemini API key is available when you want to use your own Gemini key.
- Review the proposed changes and temporary diagram preview. The request uses the active-tab snapshot captured when it was sent; a response is not saved automatically.
- Accept or reject individual changes, or use the actions for the remaining changes. Navigate code changes with F7 and Shift+F7. Protected inactive tabs remain drafts.
- Inspect the accepted SQL, then save the document. Copy AI Prompt in this guide also lets you use the documentation with an assistant outside the app.
Data, queries, exports & saved files
Use the ERD, Data, and Query buttons to switch workbenches. Data View materializes supported DDL and DML in the browser and exposes tables, rows, types, and an execution log. Download a populated table as CSV from its data panel.
Query View runs read-only SELECT queries against the current in-memory tables. It supports the syntax documented below; it does not connect to an external database. Query text is saved with the SQL document context.
Choose Export ERD to select PNG, image-backed SVG, or PDF with HD, 2K, or 4K sizing. Exported images do not replace an editable saved document.
Sign in and use Save SQL and ERD to save the SQL and diagram context, including tabs and layout. Open saved files from the file list, and use Share on an owned saved document to configure public access and distribute its read-only link.
SQL, Prisma, and Builder documents open in their own tools. Prisma and Builder have separate file lists and cloud-only saving. Unsaved-change prompts protect edited documents when leaving a workspace; save your changes before sharing or switching away.
Troubleshoot the SQL workspace
| Symptom | What to check |
|---|---|
| A table is missing | Check parsing diagnostics, active tabs, and statement order. A later DROP or rename can change the final diagram. |
| A relationship is missing | Check both tables and columns exist, foreign-key column counts match, and the referenced key is globally unique. Schema-qualify ambiguous names. |
| Data View shows no rows | Put INSERT after its CREATE TABLE in an active tab. Inspect the execution log for rejected rows or a transaction rollback. |
| Query results no longer match the SQL | Run the query again after changing the schema or data; use the current in-memory tables. |
| Add Table suggests a different dialect | Detected active-workspace syntax takes priority over the manually selected fallback. Review the SQL language indicator and table-specific defaults. |
| A saved file cannot be edited | Check sign-in and ownership. A public link grants read-only access; use Retry or Open files for recoverable loading errors. |
AI Syntax Workflow
When using an AI assistant, ask it to produce SQL in this order. This keeps the result easy to parse, easy to diagram, and easy to preview with sample rows or read-only queries.
1. Define domains first
Put CREATE TYPE enum/composite definitions before tables that reference them. Prefer explicit names over ad hoc inline types when a domain is shared.
2. Create base tables
Use CREATE TABLE for columns, primary keys, uniqueness, defaults, checks, generated columns, and inline foreign keys.
3. Replay migrations
Use ALTER TABLE for added constraints, renames, dropped columns, column type/nullability changes, and circular foreign keys.
4. Add data and queries last
Put INSERT/UPDATE/DELETE after all DDL. Keep SELECT queries separate from schema mutation when you want result sets.
ALTER TYPE. Expression indexes are preserved with a warning; request simple column keys when you need ERD index badges.DDL & DML Syntax
These statement forms are safe for AI-generated schema scripts and sample data. The same syntax drives ERD structure and in-memory preview rows, so keep DDL before DML and avoid external database-specific side effects.
Supported Statements
-- Schema statements
CREATE TYPE type_name AS ENUM (...);
CREATE TYPE type_name AS (...);
CREATE TABLE table_name (...);
CREATE OR REPLACE TABLE table_name (...);
CREATE VIEW view_name AS SELECT ...;
CREATE OR REPLACE VIEW view_name AS SELECT ...;
CREATE OR ALTER VIEW view_name AS SELECT ...;
ALTER TABLE table_name ADD ...;
ALTER TABLE table_name DROP COLUMN column_name;
ALTER TABLE table_name RENAME COLUMN old_name TO new_name;
CREATE INDEX index_name ON table_name (column_name);
DROP INDEX index_name;
TRUNCATE TABLE table_name;
DROP TABLE [IF EXISTS] table_name;
DROP VIEW [IF EXISTS] view_name;
DROP TYPE [IF EXISTS] type_name;
-- Data statements
INSERT INTO table_name [(col1, col2, ...)] VALUES (...), (...);
UPDATE table_name SET col1 = value WHERE condition;
DELETE FROM table_name WHERE condition;=, !=, <>, >, <, >=, <=, LIKE, NOT LIKE, IN, NOT IN, BETWEEN, NOT BETWEEN, IS NULL, IS NOT NULL, plus AND / OR combinations. NULL comparisons follow SQL-standard UNKNOWN logic — col = NULL never matches; use col IS NULL instead. Unknown assignment targets, right-hand column references, and filter columns reject the complete mutation before any row changes.Preview Semantics for AI-Generated DML
Rows are replayed from scratch
DDL and DML statements are applied together in source order; each data statement uses the schema live at that point.
Types stay inspectable
Named enums and composite types created with CREATE TYPE remain part of the parsed schema.
Constraints are enforced
PK, FK, NOT NULL, UNIQUE, DEFAULT, CHECK, generated columns, and enum domains affect preview rows.
Errors should be fixable
Prefer explicit column lists and concrete WHERE clauses so diagnostics can point at the exact table or column.
SELECT result sets in the main schema/DML script, unsupported procedural bodies, or external database calls. CREATE VIEW, CREATE OR REPLACE VIEW, and MSSQL CREATE OR ALTER VIEW are materialized from their SELECT result for preview; keep ad hoc read-only queries separate.Transactions in Data View
Data View supports BEGIN, COMMIT, ROLLBACK, SAVEPOINT, ROLLBACK TO, and RELEASE in the in-memory preview. These statements affect preview rows; they never commit to an external database.
SQLite, PostgreSQL, and SQL Server retain transactional DDL in the preview. MySQL DDL commits the active transaction. SQLite PRAGMA foreign_keys and MySQL SET FOREIGN_KEY_CHECKS affect subsequent DML validation.
The ERD renderer ignores transaction wrappers and represents schema statements in source order. A transaction rollback in Data View can therefore produce a different set of tables from the diagram. Check each workbench for the behavior you are testing.
CREATE TABLE preview_items (
id INTEGER PRIMARY KEY,
label TEXT NOT NULL
);
INSERT INTO preview_items (id, label) VALUES (1, 'Before transaction');
BEGIN;
INSERT INTO preview_items (id, label) VALUES (2, 'Keep this row');
SAVEPOINT before_trial;
INSERT INTO preview_items (id, label) VALUES (3, 'Undo this row');
ROLLBACK TO before_trial;
RELEASE before_trial;
COMMIT;SELECT Query Syntax
Ask AI to keep read-only reporting queries as SELECT statements. These can reference the tables and rows produced by the schema/DML script and are useful for joins, filters, totals, and report-shaped result sets without requiring a live database.
Supported Query Shape
WITH dept_totals AS (
SELECT department_id, COUNT(*) AS employee_count, SUM(salary) AS payroll
FROM employees
GROUP BY department_id
)
SELECT d.name,
dt.employee_count,
dt.payroll
FROM dept_totals dt
JOIN departments d ON d.id = dt.department_id
WHERE dt.employee_count >= 1
ORDER BY dt.payroll DESC
LIMIT 10;SELECT only
The query editor accepts SELECT statements, including SELECT without FROM and WITH CTEs that resolve to SELECT.
Joins & filters
INNER, LEFT, RIGHT, FULL, and CROSS joins, WHERE conditions, EXISTS, IN subqueries, BETWEEN, LIKE, ILIKE, and NULL checks.
Aggregates
GROUP BY, HAVING, COUNT, SUM, AVG, MIN, MAX, ARRAY_AGG, GROUP_CONCAT, STRING_AGG, and DISTINCT variants.
Ordering & paging
ORDER BY names or ordinals, ASC/DESC, NULLS FIRST/LAST, LIMIT/OFFSET, TOP, and FETCH FIRST.
Advanced SELECT Support
CTEs & derived tables
WITH clauses, nested SELECTs in FROM, scalar subqueries, EXISTS, NOT EXISTS, IN, and NOT IN are supported.
Set operators
UNION, UNION ALL, INTERSECT, and EXCEPT can combine compatible result sets.
Window functions
ROW_NUMBER, RANK, DENSE_RANK, NTILE, LAG, LEAD, FIRST_VALUE, and LAST_VALUE with OVER and PARTITION BY. Ordered windows use the peer-aware SQL default frame, and NTILE assigns larger buckets first.
Dialect helpers
Common SQLite, PostgreSQL, MySQL, and MSSQL functions are normalized for preview, including JSON and date/string helpers.
INSERT, UPDATE, DELETE, CREATE, ALTER, and DROP statements in the schema/DML script. Query syntax should stay SELECT-only.Diagnostics & Errors
Syntax diagnostics surface schema-parse, DDL/DML execution, and read-only SELECT issues with clear messages. Parse errors include line/column positions when possible, and execution messages point back to the table, column, or statement shape that needs correction.
Schema Parse Errors
The schema parser (SQLite, PostgreSQL, MySQL, and MSSQL) reports problems with two severities: error stops a single statement from contributing to the schema; warning keeps valid syntax but flags something the user should know about.
// Every entry the editor receives has this shape:
{
message: "Column 'a' missing data type",
severity: "error" | "warning",
position: { line: 1, column: 17, index: 16 },
token?: { type, value, raw } // present for parser-side errors
}Parser errors
Empty PRIMARY KEY / UNIQUE / FOREIGN KEY column lists, missing required data types (including SQLite STRICT columns), unmatched parentheses, unexpected commas, and malformed constraints.
Tokenizer warnings
Unclosed strings, unclosed block comments, mismatched parentheses, and suspicious cross-line CHECK expressions.
Foreign-key warnings
FKs that point to an unknown table, unknown column, ambiguous schema leaf, or non-PK/non-UNIQUE target are dropped from the diagram with a descriptive warning.
Grouped diagnostics
Warnings are grouped by cause, including Dropped relation, Unknown table, Unknown column, Non-unique FK target, Warnings, and Parse errors.
Empty SQL
Empty input is valid and produces an empty schema. Only invalid non-string inputs throw.
DDL/DML Execution Log
Every executed DDL or DML statement appends one structured entry to the in-memory execution log. The message stream preserves chronological order and classifies each statement as error, warning, or success.
// Each log entry is:
{
type: "success" | "warning" | "error",
message: "Inserted 3 row(s) into \"users\"",
timestamp: 1734567890123 // ms since epoch, monotonic
}-- Examples ────────────────────────────────────────────────
-- success
CREATE TABLE users (id INT PRIMARY KEY);
-- → success: Table "users" created with 1 columns
-- warning (no WHERE clause)
UPDATE users SET id = 99;
-- → warning: UPDATE "users": no WHERE clause — all 2 row(s) will be affected
-- → success: Updated 2 row(s) in "users"
-- error (unknown table)
INSERT INTO ghost VALUES (1);
-- → error: INSERT INTO "ghost": table does not existCommon Diagnostics
error : INSERT INTO "x": unknown column(s): foo
warning : INSERT INTO "x" row 3: expected 4 values but got 3
warning : UPDATE "x": setting NOT NULL column "name" to NULL
warning : DELETE FROM "x": no WHERE clause — all 1200 row(s) will be removed
error : DROP: table "ghost" does not exist
warning : ALTER "x": column "old_col" does not exist
warning : Relation dropped: foreign key 'posts.user_id' references column
'users.no_such' which does not exist on table 'users'.
warning : Relation dropped: foreign key 'events.email' references 'users.email',
but the target is only partial-unique, not globally unique.CREATE TYPE
Define custom data types for reuse across your database schema. Named enum and composite types should appear before tables that reference them, including schema-qualified references.
1.1. Composite Type
Define custom composite types with multiple fields of various data types.
CREATE TYPE type_name AS (
field1 data_type1,
field2 data_type2,
...
);Example:
CREATE TYPE address_type AS (
street VARCHAR(100),
city TEXT,
postal_code INTEGER
);
CREATE TYPE coordinates AS (
latitude DECIMAL(10, 8),
longitude DECIMAL(11, 8)
);1.2. Enum Type
Create enumerated types with predefined values for better data integrity and readability.
CREATE TYPE enum_name AS ENUM (
'value1',
'value2',
...
);Example:
CREATE TYPE status_enum AS ENUM (
'pending',
'active',
'completed',
'cancelled'
);
CREATE TYPE priority_level AS ENUM (
'low', 'medium', 'high', 'critical'
);1.3. Schema-Qualified Types
User-defined types are resolved with the same schema awareness as tables. If two schemas define the same leaf name, the ERD keeps them distinct; if a type is unique, the display name stays simple.
CREATE TYPE s1.status AS ENUM ('draft', 'active');
CREATE TYPE s2.status AS ENUM ('queued', 'done');
CREATE TABLE s1.items (
id INT PRIMARY KEY,
state s1.status
);
CREATE TABLE s2.items (
id INT PRIMARY KEY,
state s2.status
);s1.status never resolves to s2.status just because both share the leaf name status. Qualified type names are strict, while unqualified names resolve only when unambiguous.CREATE TABLE
Define database tables with columns, data types, and constraints.
2.1. Basic Table Structure
The general syntax for creating a table:
CREATE TABLE table_name (
column_name data_type [constraints],
...
-- table-level constraints (optional)
);
-- Also supports:
CREATE TABLE IF NOT EXISTS table_name ( ... );
CREATE TABLE schema_name.table_name ( ... );"name"), backticks (\`name\`), or square brackets ([name]) for escaping special characters or reserved keywords.2.2. Column-Level Definitions
Each column can include various constraints and properties:
CREATE TABLE users (
id INTEGER PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email TEXT NOT NULL CHECK (email LIKE '%@%'),
status VARCHAR(20) DEFAULT 'active',
created_at TIMESTAMP DEFAULT now(),
department_id INTEGER REFERENCES departments(id)
);PRIMARY KEY
Uniquely identifies each row
UNIQUE
Ensures column values are distinct
NOT NULL
Prevents null values
DEFAULT
Sets default values (literals or functions)
CHECK
Validates data with boolean expressions
REFERENCES
Creates foreign key relationships
- Literals: numbers (
123), strings ('text') - PostgreSQL functions:
now(),CURRENT_TIMESTAMP,CURRENT_DATE,nextval('seq'::regclass) - MSSQL functions:
GETDATE(),NEWID()
REFERENCES table(column), schema-qualified REFERENCES schema.table(column), omitted target columns such as REFERENCES departments, and FK actions such as ON DELETE CASCADE, ON UPDATE SET NULL, MATCH FULL, and DEFERRABLE INITIALLY DEFERRED.2.3. Generated / Computed Columns
Quick reference. See the dedicated Computed Columns section below for the full per-dialect breakdown including the MSSQL bare-AS form.
CREATE TABLE products (
id SERIAL PRIMARY KEY,
price NUMERIC(10, 2),
quantity INTEGER,
-- PostgreSQL / MySQL: parenthesized expression
total NUMERIC GENERATED ALWAYS AS (price * quantity) STORED,
-- MySQL VIRTUAL column
discount NUMERIC GENERATED ALWAYS AS (price * 0.1) VIRTUAL
);2.4. Inline ENUM Types
You can declare an ENUM directly in a column definition (MySQL style). The parser will register it as a named enum and replace the column type automatically.
CREATE TABLE tasks (
id INTEGER PRIMARY KEY,
status ENUM('todo', 'doing', 'done'),
priority ENUM('low', 'medium', 'high')
);column_enum (for example, status_enum); later tables with a different definition for the same column name get a table-qualified name such as tasks_status_enum so domains never collapse accidentally.CREATE TYPE ... AS ENUM instead.2.5. Table-Level Constraints
Define constraints at the end of the table body. Useful for composite keys and multi-column constraints:
-- Syntax:
[CONSTRAINT name] PRIMARY KEY (col1, col2, ...)
[CONSTRAINT name] FOREIGN KEY (col1, col2, ...)
REFERENCES ref_table(ref_col1, ref_col2, ...)
[MATCH FULL|PARTIAL|SIMPLE]
[ON DELETE CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION]
[ON UPDATE CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION]
[DEFERRABLE|NOT DEFERRABLE] [INITIALLY DEFERRED|IMMEDIATE]
[CONSTRAINT name] UNIQUE (col1, col2, ...)
[CONSTRAINT name] CHECK (expression)Example:
CREATE TABLE order_items (
order_id INTEGER,
product_id INTEGER,
quantity INTEGER CHECK (quantity > 0),
unit_price NUMERIC(10, 2),
CONSTRAINT pk_order_items
PRIMARY KEY (order_id, product_id),
CONSTRAINT fk_order
FOREIGN KEY (order_id) REFERENCES orders(id),
CONSTRAINT fk_product
FOREIGN KEY (product_id) REFERENCES products(id),
CONSTRAINT uq_order_product
UNIQUE (order_id, product_id),
CONSTRAINT chk_price
CHECK (unit_price >= 0)
);CONSTRAINT name) is optional.REFERENCES ref_table omits the target column list, Erd Go resolves it to the referenced table's primary key, including composite primary keys. If you specify target columns explicitly, they must be backed by a PRIMARY KEY, UNIQUE constraint, or global CREATE UNIQUE INDEX; filtered / partial unique indexes do not count as FK targets. Invalid relations are dropped with a warning.2.6. Reserved-Word Column Names
Words like key, index, value, and name are reserved in some dialects but commonly used as column names in others. The parser disambiguates automatically when the next token is a known data type, so bare unquoted use works:
CREATE TABLE feature_flags (
id BIGSERIAL PRIMARY KEY,
org_id BIGINT NOT NULL,
key VARCHAR(128) NOT NULL, -- 'key' as column name → OK
index INT, -- 'index' as column name → OK
value TEXT, -- 'value' as column name → OK
enabled BOOLEAN NOT NULL DEFAULT FALSE,
UNIQUE (org_id, key)
);
-- The MySQL "KEY ... (cols)" index form is still recognized correctly
CREATE TABLE posts (
id INT PRIMARY KEY,
body TEXT,
KEY idx_body (body), -- treated as index, adds IDX badge
FULLTEXT KEY ft_body (body) -- treated as fulltext index badge
);KEY / INDEX / FULLTEXT / SPATIAL followed by a data-type keyword (VARCHAR, INT, …) is a column. Followed by an identifier and a parenthesized column list, it's an index. For absolute safety, quote the column with "key", `key`, or [key].Identity / Auto-Increment Columns
Use the native identity syntax for the selected dialect. All supported forms produce a primary-key column with the AUTO_INCREMENT marker, but SQLite has one strict rule: AUTOINCREMENT requires a single INTEGER PRIMARY KEY column.
SQLite
-- Rowid alias; auto assigns a value when omitted
CREATE TABLE users (
id INTEGER PRIMARY KEY,
email TEXT NOT NULL UNIQUE
);
-- AUTOINCREMENT prevents rowid reuse after DELETE
CREATE TABLE events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL
);
-- Invalid for SQLite AUTOINCREMENT:
-- 1. BIGINT PRIMARY KEY AUTOINCREMENT
-- 2. INT PRIMARY KEY AUTOINCREMENT
-- 3. AUTOINCREMENT on a composite primary keyINTEGER PRIMARY KEY AUTOINCREMENT. Do not use BIGINT, INT, or a composite primary key with AUTOINCREMENT.PostgreSQL
-- Classic SERIAL family
CREATE TABLE users (
id SERIAL PRIMARY KEY,
big_id BIGSERIAL,
small_id SMALLSERIAL
);
-- Modern SQL-standard identity columns
CREATE TABLE events (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name TEXT
);
CREATE TABLE invoices (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
);GENERATED ALWAYS AS IDENTITY is an identity column (auto-increment), distinct from GENERATED ALWAYS AS (expression) which is a computed column. The parser keeps them apart automatically.MySQL
CREATE TABLE users (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE
) ENGINE=InnoDB;MSSQL / SQL Server
-- IDENTITY(seed, increment)
CREATE TABLE [dbo].[Customers] (
[CustomerID] BIGINT IDENTITY(1,1) NOT NULL CONSTRAINT pk_customer PRIMARY KEY,
[Email] NVARCHAR(255) NOT NULL
);
-- Bare IDENTITY (defaults to (1,1))
CREATE TABLE [dbo].[Orders] (
[OrderID] INT IDENTITY NOT NULL PRIMARY KEY
);AUTO_INCREMENT tag — so you can mix dialects in one schema without losing the auto-increment hint.Computed Columns (per-dialect)
A computed column derives its value from an expression over other columns in the same row. Supported in all four dialects with their respective syntaxes.
PostgreSQL & MySQL — GENERATED ALWAYS AS
CREATE TABLE products (
id SERIAL PRIMARY KEY,
price NUMERIC(10,2),
qty INTEGER,
total NUMERIC GENERATED ALWAYS AS (price * qty) STORED,
discount NUMERIC GENERATED ALWAYS AS (price * 0.1) VIRTUAL
);
-- Complex expressions, including CASE
CREATE TABLE orders (
id BIGSERIAL PRIMARY KEY,
qty INT,
flag INT GENERATED ALWAYS AS (CASE WHEN qty > 100 THEN 1 ELSE 0 END) STORED
);STORED stores the computed value and VIRTUAL computes it on read. Erd Go keeps preview values in memory; the ERD preserves each mode as column metadata.MSSQL / SQL Server — column AS expression [PERSISTED]
MSSQL has its own syntax: column_name AS expression, optionally followed by PERSISTED (and optionally NOT NULL). Both parenthesized and bare expression forms are supported.
CREATE TABLE [dbo].[Customers] (
[CustomerID] BIGINT IDENTITY(1,1) NOT NULL CONSTRAINT pk_c PRIMARY KEY,
[FirstName] NVARCHAR(50) NOT NULL,
[LastName] NVARCHAR(50) NOT NULL,
-- Parenthesized form
[FullName] AS ([FirstName] + ' ' + [LastName]) PERSISTED,
-- Bare form (no parens) — also accepted
[Initials] AS LEFT([FirstName], 1) + LEFT([LastName], 1)
);
CREATE TABLE [dbo].[Loans] (
[LoanID] BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY,
[StartDate] DATE NOT NULL,
[TermMonths] INT NOT NULL,
-- Function call in a bare AS expression
[MaturityDate] AS DATEADD(MONTH, [TermMonths], [StartDate]) PERSISTED,
-- CASE expression in a bare AS
[SignedAmount] AS CASE WHEN [TermMonths] > 12 THEN 1 ELSE 0 END
);col AS expression (no surrounding parentheses) — including function calls like DATEADD(...), table-qualified references like c.GrowthDurationDays, and CASE expressions — terminating at the next column-comma, PERSISTED, or end of the table body.COMPUTED as the type and the expression preserved in the column extras / tooltip.ALTER TABLE
Common migration-style ALTER TABLE statements are replayed after table creation, so the final schema reflects added columns, constraints, renames, dropped columns, and column type/nullability changes. PostgreSQL dump syntax using ALTER TABLE ONLY preserves the qualified table identity.
Add & constrain
ADD COLUMN, ADD PRIMARY KEY, ADD UNIQUE, ADD CHECK, and ADD FOREIGN KEY are reflected in the ERD.
Rename & drop
Table, column, constraint, and index renames—including MySQL RENAME TABLE, PostgreSQL ALTER INDEX, and SQL Server sp_rename—stay bound to the correct object incarnation before later DROP operations.
Column changes
PostgreSQL ALTER COLUMN TYPE/SET DATA TYPE/SET or DROP DEFAULT/nullability, MSSQL ALTER COLUMN, and MySQL MODIFY/CHANGE COLUMN are replayed.
Relationship safety
Foreign keys are validated against the final table state after migration replay, including PK/UQ/index changes and exact source/target column counts.
3.1. Add Foreign Key
ALTER TABLE table_name
ADD [CONSTRAINT name] FOREIGN KEY (cols...)
REFERENCES ref_table[(ref_cols...)]
[ON DELETE action] [ON UPDATE action]
[DEFERRABLE|NOT DEFERRABLE] [INITIALLY DEFERRED|IMMEDIATE];Example:
ALTER TABLE orders
ADD CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id)
REFERENCES customers(id)
ON DELETE CASCADE;3.2. Add Primary Key
ALTER TABLE table_name
ADD [CONSTRAINT name] PRIMARY KEY (cols...);Example:
ALTER TABLE products
ADD CONSTRAINT pk_products
PRIMARY KEY (product_id);3.3. Add Unique Constraint
ALTER TABLE table_name
ADD [CONSTRAINT name] UNIQUE (cols...);Example:
ALTER TABLE users
ADD CONSTRAINT uq_users_email
UNIQUE (email);3.4. Add Check Constraint
ALTER TABLE table_name
ADD [CONSTRAINT name] CHECK (expression);Example:
ALTER TABLE employees
ADD CONSTRAINT chk_salary
CHECK (salary >= 0);3.5. Migration Replay Example
CREATE TYPE app.ticket_status AS ENUM ('new', 'open', 'closed');
CREATE TABLE app.accounts (
tenant_id INT NOT NULL,
account_no TEXT NOT NULL,
status app.ticket_status NOT NULL DEFAULT 'new',
PRIMARY KEY (tenant_id, account_no)
);
CREATE TABLE app.contacts (
tenant_id INT NOT NULL,
account_no TEXT NOT NULL,
owner_email TEXT,
CONSTRAINT fk_contacts_account
FOREIGN KEY (tenant_id, account_no)
REFERENCES app.accounts
ON DELETE CASCADE
DEFERRABLE INITIALLY DEFERRED
);
ALTER TABLE app.contacts ADD COLUMN audit_state TEXT;
ALTER TABLE app.contacts ALTER COLUMN owner_email TYPE VARCHAR(320);
ALTER TABLE app.contacts ALTER COLUMN owner_email SET NOT NULL;
ALTER TABLE app.contacts RENAME COLUMN owner_email TO contact_email;
ALTER TABLE app.contacts DROP COLUMN audit_state;
CREATE UNIQUE INDEX ux_contacts_email
ON app.contacts (contact_email);app.accounts, owner_email becomes contact_email, audit_state is removed, and the unique index gives contact_email a UQ badge.3.6. MySQL CHANGE / MODIFY COLUMN
CREATE TABLE `customers` (
`id` BIGINT AUTO_INCREMENT PRIMARY KEY,
`email` VARCHAR(320) NOT NULL,
UNIQUE INDEX `ux_customers_email` (`email`)
) ENGINE=InnoDB;
CREATE TABLE `orders` (
`id` BIGINT AUTO_INCREMENT PRIMARY KEY,
`customer_email` VARCHAR(320),
`status` ENUM('new','paid') NOT NULL DEFAULT 'new',
`old_note` VARCHAR(50),
CONSTRAINT `fk_orders_customer`
FOREIGN KEY (`customer_email`)
REFERENCES `customers`(`email`)
ON DELETE SET NULL
) ENGINE=InnoDB;
ALTER TABLE `orders`
CHANGE COLUMN `old_note` `note` VARCHAR(255) NOT NULL;
ALTER TABLE `orders`
MODIFY COLUMN `status` ENUM('new','paid') NOT NULL DEFAULT 'paid';3.7. MSSQL / SQL Server ALTER COLUMN
CREATE TABLE [dbo].[Customers] (
[CustomerID] INT IDENTITY(1,1) NOT NULL,
[Email] NVARCHAR(320) NOT NULL,
CONSTRAINT [PK_Customers] PRIMARY KEY CLUSTERED ([CustomerID]),
CONSTRAINT [UQ_Customers_Email] UNIQUE NONCLUSTERED ([Email])
);
CREATE TABLE [dbo].[Orders] (
[OrderID] INT IDENTITY(1,1) NOT NULL,
[CustomerEmail] NVARCHAR(320),
[LegacyNote] NVARCHAR(50),
CONSTRAINT [PK_Orders] PRIMARY KEY NONCLUSTERED ([OrderID]),
CONSTRAINT [FK_Orders_Customers]
FOREIGN KEY ([CustomerEmail])
REFERENCES [dbo].[Customers]([Email])
ON DELETE SET NULL
);
ALTER TABLE [dbo].[Orders]
ALTER COLUMN [LegacyNote] NVARCHAR(255) NOT NULL;
ALTER TABLE [dbo].[Orders]
ADD [CreatedAt] DATETIME2 NOT NULL DEFAULT '2026-01-01';CREATE INDEX
Index DDL is parsed across all four dialects. Simple column keys are surfaced as small ERD badges: IDX for non-unique speed-ups, UQ for global unique indexes, and PUQ for filtered / partial unique indexes. Expression entries are preserved in parser output and reported when they cannot honestly map to a column badge.
IDX badge
Non-unique index. Solid pill on the column. Composite indexes mute the pill (gray) on every key column so single-col vs multi-col is obvious at a glance.
UQ badge
From a global CREATE UNIQUE INDEX. Behaves like a UNIQUE constraint for FK validation and column badging.
PUQ badge
From CREATE UNIQUE INDEX ... WHERE. Shows the partial uniqueness predicate but does not validate FK targets as globally unique.
Suppression rule
Stronger constraints win: PK ⟶ UQ ⟶ PUQ ⟶ IDX. A stronger key is never demoted by a later index.
Multi-dialect
PostgreSQL CONCURRENTLY / USING / INCLUDE / WHERE, MySQL FULLTEXT/SPATIAL & inline KEY/INDEX, MSSQL CLUSTERED/NONCLUSTERED with INCLUDE, SQLite standard indexes.
4.1. Syntax
CREATE [UNIQUE] INDEX [IF NOT EXISTS] [name]
ON [schema.]table (col1 [ASC|DESC] [NULLS FIRST|LAST], col2, ...)
[INCLUDE (cols)] -- PG / MSSQL: covering columns (no badge added)
[WHERE predicate] -- PG / MSSQL: partial / filtered index
[USING method] -- PG: btree, hash, gin, gist, brin
[WITH (options)]; -- storage parameters4.2. Examples
Speed up a frequent FK lookup (single-column IDX):
CREATE INDEX idx_orders_customer ON orders (customer_id);Composite index (muted IDX pill on every key column):
CREATE INDEX idx_orders_customer_status
ON orders (customer_id, status);Promote a column to UNIQUE without altering its definition:
CREATE UNIQUE INDEX uq_shipments_tracking
ON shipments (tracking_number);Partial / filtered unique index (PUQ, not a global FK target):
CREATE UNIQUE INDEX ux_users_active_email
ON users (email)
WHERE deleted_at IS NULL;MySQL inline index inside CREATE TABLE:
CREATE TABLE posts (
id INT PRIMARY KEY,
slug VARCHAR(160) NOT NULL,
body TEXT,
UNIQUE INDEX ux_posts_slug (slug),
KEY idx_posts_body (body),
FULLTEXT KEY ft_body (body)
);MSSQL / SQL Server filtered + INCLUDE index:
CREATE NONCLUSTERED INDEX idx_active_orders
ON orders (customer_id)
INCLUDE (total)
WHERE status = 'active';4.3. Behavior & Semantics
- Functional / expression indexes (e.g.
CREATE INDEX … ON t (lower(email))) are preserved and produce an explicit warning but no badge — they don't map cleanly to a single column. - INCLUDE columns (covering columns) are parsed but never badged — only key columns earn the pill.
- PK / UQ columns are stronger guarantees than IDX, so any later
CREATE INDEXon them is silently skipped — you won't see redundant pills. - Partial / filtered unique indexes render as
PUQwith theWHEREpredicate in column details. They are not accepted as globally unique targets for foreign-key validation. - Final badges are order-invariant: a global unique guarantee suppresses a redundant
IDXbadge whether the unique or non-unique index appears first. Composite index metadata is likewise retained regardless of declaration order. - Indexes on unknown tables / columns are silently dropped — useful when porting partial schemas without breaking the diagram.
- Index lifecycle includes top-level
CREATE/DROP INDEX, PostgreSQLDROP INDEX CONCURRENTLY, MySQLALTER TABLE … ADD/DROP/RENAME INDEX, PostgreSQLALTER INDEX … RENAME, and SQL Server index renames throughsp_rename. Named UQ/PUQ/IDX metadata follows the final surviving name.
Multi-Dialect Support
The browser-side parser handles syntax differences across SQLite, PostgreSQL, MySQL, and MSSQL without a database connection. Auto detection uses structural syntax signals and remains neutral for genuinely mixed-dialect input; callers can also provide an explicit dialect. Examples below follow the same standard order: SQLite first, then PostgreSQL, MySQL, and MSSQL.
4.1. SQLite Features
-- SQLite uses type affinities: INTEGER, TEXT, REAL, NUMERIC, BLOB
CREATE TABLE users (
id INTEGER PRIMARY KEY, -- implicit rowid alias
email TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
balance REAL DEFAULT 0.0,
metadata BLOB,
created TEXT DEFAULT (datetime('now'))
);
-- AUTOINCREMENT prevents rowid reuse after DELETE
CREATE TABLE events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
ts TEXT DEFAULT CURRENT_TIMESTAMP
);
-- WITHOUT ROWID — clustered on the declared PRIMARY KEY
CREATE TABLE kv_store (
key TEXT PRIMARY KEY,
value TEXT
) WITHOUT ROWID;
-- STRICT mode — enforces declared column types
CREATE TABLE audit_log (
id INTEGER PRIMARY KEY,
action TEXT NOT NULL,
payload TEXT
) STRICT;
-- STRICT + WITHOUT ROWID combined
CREATE TABLE config (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
) STRICT, WITHOUT ROWID;
-- Foreign keys with ON DELETE / ON UPDATE actions
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL
REFERENCES users(id) ON DELETE CASCADE ON UPDATE SET NULL,
total REAL DEFAULT 0 CHECK (total >= 0)
);
-- IF NOT EXISTS works as expected
CREATE TABLE IF NOT EXISTS sessions (
token TEXT PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
expires TEXT
);INTEGER PRIMARY KEY (rowid alias), AUTOINCREMENT, WITHOUT ROWID, STRICT tables, typeless non-STRICT columns, type affinities (INTEGER, TEXT, REAL, NUMERIC, BLOB), datetime('now') / CURRENT_TIMESTAMP defaults, and standard REFERENCES with ON DELETE / ON UPDATE actions.VARCHAR(255), BOOLEAN, or TIMESTAMP) and may omit a type. STRICT tables follow SQLite's exact allowed set: INT, INTEGER, REAL, TEXT, BLOB, or ANY; every STRICT column must declare one.SQLite migration replay example:
CREATE TABLE authors (
id INTEGER PRIMARY KEY,
email TEXT NOT NULL UNIQUE
) STRICT;
CREATE TABLE posts (
author_id INTEGER NOT NULL
REFERENCES authors(id) ON DELETE CASCADE ON UPDATE RESTRICT,
post_id TEXT NOT NULL,
body TEXT,
PRIMARY KEY (author_id, post_id)
) STRICT, WITHOUT ROWID;
ALTER TABLE posts ADD COLUMN summary TEXT;
ALTER TABLE posts RENAME COLUMN summary TO excerpt;
ALTER TABLE authors RENAME TO writers;4.2. PostgreSQL Features
-- Composite type + ENUM + array column types
CREATE TYPE address AS (street TEXT, city VARCHAR(100), zip VARCHAR(20));
CREATE TYPE org_plan AS ENUM ('free','starter','pro','enterprise');
CREATE TABLE organizations (
id BIGSERIAL PRIMARY KEY,
-- Or: id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY
name VARCHAR(255) NOT NULL,
plan org_plan NOT NULL DEFAULT 'free',
-- PostgreSQL-specific types
billing_addr address, -- composite type
data JSONB DEFAULT '{}', -- JSON binary
tags TEXT[], -- 1-D array
matrix INT[5], -- sized array
uuid UUID DEFAULT gen_random_uuid(),
ip INET,
ts TIMESTAMPTZ DEFAULT NOW(),
-- Dollar-quoted defaults
description TEXT DEFAULT $Hello World$,
-- Type casting in defaults
seq_id INTEGER DEFAULT nextval('org_seq'::regclass)
);
-- Schema-qualified table & type references both work
CREATE TABLE public.users (
id INTEGER PRIMARY KEY,
status public.org_plan -- schema-qualified user-defined type
);
-- Empty body INHERITS (PG-only)
CREATE TABLE child () INHERITS (parent);UNIQUE NULLS NOT DISTINCT, expression-index preservation, and PostgreSQL's exact case-sensitive identity for quoted names.4.3. MySQL Features
-- Backtick-quoted identifiers + ENGINE / CHARSET / COLLATE
CREATE TABLE `users` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(100) NOT NULL,
-- Inline ENUM and SET (auto-registered as named types)
`status` ENUM('active', 'inactive', 'suspended') NOT NULL DEFAULT 'active',
`flags` SET('verified','staff','beta','muted'),
-- UNSIGNED / SIGNED modifiers + TINYINT(1)
`age` TINYINT UNSIGNED,
`is_pro` TINYINT(1) UNSIGNED DEFAULT 0,
-- COMMENT on columns
`email` VARCHAR(255) COMMENT 'User email address',
-- Timestamps with ON UPDATE CURRENT_TIMESTAMP
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- MySQL-specific types
`bio` MEDIUMTEXT,
`avatar` LONGBLOB,
`birth_year` YEAR(4),
`location` POINT,
-- Inline indexes become ERD badges when they map to columns
INDEX idx_name (`name`),
FULLTEXT KEY ft_bio (`bio`),
UNIQUE INDEX ux_name (`name`),
UNIQUE KEY uk_email (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;AUTO_INCREMENT, COMMENT, signed modifiers, integer/text/blob/spatial variants, inline ENUM and SET, inline indexes, CHANGE COLUMN, MODIFY COLUMN, FIRST/AFTER physical placement, ON UPDATE CURRENT_TIMESTAMP, and common table options.4.4. MSSQL / SQL Server Features
-- Bracket-quoted identifiers + IDENTITY + computed columns
CREATE TABLE [dbo].[Users] (
[UserID] BIGINT IDENTITY(1,1) NOT NULL CONSTRAINT pk_users PRIMARY KEY CLUSTERED,
[FirstName] NVARCHAR(50) NOT NULL,
[LastName] NVARCHAR(50) NOT NULL,
-- Computed column (parenthesized form)
[FullName] AS ([FirstName] + ' ' + [LastName]) PERSISTED,
-- Computed column (bare AS form — also supported)
[Initials] AS LEFT([FirstName],1) + LEFT([LastName],1),
-- MSSQL-specific types
[IsActive] BIT DEFAULT 1,
[CreatedAt] DATETIME2 DEFAULT GETDATE(),
[UniqueToken] UNIQUEIDENTIFIER DEFAULT NEWID(),
[SeqToken] UNIQUEIDENTIFIER DEFAULT NEWSEQUENTIALID() ROWGUIDCOL,
[Photo] VARBINARY(MAX),
[Notes] NVARCHAR(MAX),
[Config] XML,
[Location] GEOGRAPHY,
[TreePath] HIERARCHYID
) ON [PRIMARY];
GO
-- ALTER TABLE with WITH CHECK and CONSTRAINT WITH (...)
ALTER TABLE [Users] WITH CHECK
ADD CONSTRAINT [chk_active] CHECK ([IsActive] IN (0,1));
GO
-- Multi-statement batches separated by GO are honored
CREATE TABLE [dbo].[Orders] (
[OrderID] INT IDENTITY NOT NULL PRIMARY KEY -- bare IDENTITY → (1,1)
);
GOIDENTITY(seed, increment), NVARCHAR(MAX), date/time, binary, XML, identifier and spatial types, ROWGUIDCOL, clustered indexes, GO batches, ALTER COLUMN, computed columns, named DEFAULT ... FOR column constraints, generated temporal columns, and PERIOD FOR SYSTEM_TIME.col AS expression PERSISTED (no surrounding parens) is fully supported, including function calls (DATEADD, LEFT, …), table-qualified column references (c.col), and CASE expressions.4.5. Identifier Quoting Styles
All three major quoting conventions are supported:
-- PostgreSQL / Standard SQL: double quotes
CREATE TABLE "My Table" (
"Column Name" INTEGER
);
-- MySQL: backtick quotes
CREATE TABLE `my_table` (
`column_name` INT
);
-- MSSQL / SQL Server: square brackets
CREATE TABLE [my_table] (
[column_name] INT
);Notes & Tips
Important syntax support details: what contributes to schema structure, what DDL/DML can be replayed with sample rows, what SELECT queries can read, and where the limits are.
✓ Schema / ERD Syntax Support
- Multi-dialect: SQLite, PostgreSQL, MySQL, and MSSQL syntax can share a script; auto detection remains neutral when strong signals are genuinely mixed
- Case-insensitive keywords: CREATE, TABLE, ALTER, etc. can be written in any case
- Quoted identifiers: Double quotes (
"name"), backticks (`name`), and brackets ([name]) - Reserved-word column names: Bare
key,index,value,name,order, etc. are accepted when followed by a data type - Schema-qualified names:
schema.tableandschema.type(e.g.,public.users,dbo.Orders,app.status_enum) — schema prefixes on user-defined types are preserved for strict lookup and simplified for display only when unambiguous - Comments: Single-line (
-- comment) and block comments (/* comment */) are automatically skipped - BOM & CRLF: UTF-8 byte-order-mark and Windows line endings are handled transparently
- Custom types:
CREATE TYPE … AS ENUMandCREATE TYPE … AS (composite), including schema-qualified names - Inline ENUM & SET (MySQL):
ENUM('a','b')andSET('a','b','c')inside column types — auto-registered as named types and namespaced by table/column when duplicate column names use different domains - Constraints: Column-level and table-level
PRIMARY KEY,FOREIGN KEY,UNIQUE,CHECK(including composite multi-column variants). FK targets are validated against primary keys, unique constraints, or global unique indexes - Identity / auto-increment:
SERIAL/BIGSERIAL(PG),GENERATED ALWAYS/BY DEFAULT AS IDENTITY(PG),AUTO_INCREMENT(MySQL),IDENTITY(1,1)and bareIDENTITY(MSSQL) — all unify in the diagram - Computed columns:
GENERATED ALWAYS AS (expr) STORED/VIRTUAL(PG/MySQL) and MSSQLcol AS (expr) [PERSISTED]+ barecol AS expr [PERSISTED] - PostgreSQL arrays:
type[], sizedtype[5], nestedtype[][][], and arrays of user-defined types (e.g.,webhook_event[]) - Default values: Literals, dollar-quoted strings, function calls (
now(),CURRENT_TIMESTAMP,GETDATE(),NEWID(),NEWSEQUENTIALID(),gen_random_uuid(),nextval()), andON UPDATE CURRENT_TIMESTAMP(MySQL) - FK actions:
ON DELETE/ON UPDATEwithCASCADE,RESTRICT,SET NULL,SET DEFAULT,NO ACTION, plusMATCH,DEFERRABLE, andINITIALLYmetadata. Actions are preserved as relationship metadata. - Migration replay:
ALTER TABLE ADD COLUMN, add/drop/rename keys and constraints, table/column/index renames,DROP COLUMN,ALTER COLUMN TYPE/SET DATA TYPE,SET/DROP DEFAULT,SET/DROP NOT NULL, MSSQLsp_rename, and MySQLMODIFY/CHANGE COLUMN - Indexes: Top-level
CREATE/DROP INDEX, PostgreSQLALTER INDEX … RENAME, MySQL add/drop/rename and inlineKEY/INDEX/UNIQUE INDEX/FULLTEXT/SPATIALdefinitions. Column-backed indexes render asIDX,UQ, orPUQbadges - IF NOT EXISTS, IF EXISTS: handled gracefully on both
CREATEandDROP - Schema-only transaction wrappers:
BEGIN,COMMIT,SET search_path,USE [db](MSSQL), andSET FOREIGN_KEY_CHECKS(MySQL) are gracefully skipped - Gracefully skipped:
CREATE VIEW,CREATE MATERIALIZED VIEW,CREATE FUNCTION/TRIGGER/SCHEMA/SEQUENCE, proceduralEXECstatements other thansp_rename,DECLARE,PRINT,PARTITION BY,INHERITS(including empty body),WITH OIDS,STORAGE PLAIN,EXCLUDE USING gist, and statement bodies that do not affect ERD structure
✓ DDL/DML Preview Syntax
- Top-to-bottom execution: The script is replayed in statement order, so sample data should come after the schema it references
- Supported statements:
CREATE TYPE,CREATE TABLE,CREATE VIEW,CREATE INDEX,ALTER TABLEcolumn/constraint changes,DROP INDEX,TRUNCATE TABLE,INSERT,UPDATE,DELETE,DROP TABLE,DROP VIEW, andDROP TYPE - WHERE filters:
=,!=,<>,>,<,>=,<=,LIKE,IN,NOT IN,NOT LIKE,BETWEEN,NOT BETWEEN,IS NULL, andIS NOT NULL - Row materialization: Tables expose row counts, structure metadata, and current row data after DML statements execute
- Named types: ENUM and composite types created with
CREATE TYPEare surfaced separately from tables - Execution log: Success, warning, and error states are captured for unknown columns, missing tables, duplicate keys, NULL violations, and broad updates/deletes; invalid mutation bindings fail before rows change
- Default evaluation: Literal defaults plus functions like
now(),CURRENT_TIMESTAMP,CURRENT_DATE,GETDATE(), andNEWID()
✓ SELECT Query Syntax
- Read-only SELECT:
SELECTstatements can read the in-memory tables produced from the main SQL file - Result metadata: Columns, row counts, elapsed time, NULL/boolean/number formatting, and query errors are preserved with the SELECT result
- Relational queries: Joins, WHERE filters, GROUP BY, HAVING, ORDER BY, LIMIT/OFFSET, TOP, FETCH FIRST, CTEs, derived tables, subqueries, and set operators
- Analytical helpers: Aggregates, common scalar functions, JSON helpers, date/string functions, and window functions such as
ROW_NUMBER,RANK,NTILE,FIRST_VALUE,LAST_VALUE,LAG, andLEAD; ordered default frames include the current peer group
✗ Still Not Supported / Skipped
- ALTER TYPE: Cannot modify existing ENUM or composite types
- LIKE clause:
CREATE TABLE foo (LIKE bar INCLUDING …)is gracefully skipped — copied columns are not materialized - INHERITS column inheritance: The
INHERITS (parent)clause itself parses without errors, but inherited columns are not pulled into the child table for ERD purposes - Index execution: Index DDL can create schema badges, but preview execution does not model storage engines, query planners, or index performance
- SELECT limits: Read-only query syntax does not support recursive CTEs, external database execution, stored procedures, or mutation statements
- Other objects: Triggers, stored procedures, functions, and sequences
- External execution: DDL/DML preview does not connect to a real database or persist rows outside the current SQL file
- End each statement with a semicolon (
;) - Define
CREATE TYPEbefore tables that reference them - Define
CREATE TABLEbefore relatedINSERT,UPDATE, orDELETEstatements - Use
ALTER TABLEfor foreign keys when tables reference each other (circular dependencies) - Use DDL for structure first, then DML to validate sample rows and constraint behavior
- Keep demo data in the SQL file itself because preview rows are derived from the script