A teammate hands you a 60 line report query with five joins and a CTE on top. Paste the query below to see which tables feed which, what each join hangs on, and the order your database works through the clauses, before you touch a single line.
Parsing runs in this browser tab. Your SQL is never sent anywhere or stored.
SQL puts the output first and the data source second. In a long report query the SELECT list fills the top half of the screen, the tables hide in the middle, and the join conditions sit next to tables they do not belong to. You end up scrolling up and down to answer one question: where does this number come from?
The viewer answers by drawing the query instead of reformatting the text. Each table becomes a card listing only the columns this query touches, tagged by what the query does with them. Each join becomes a line from the parent table to the joined table, anchored on the actual key columns and colored by join type. The driving table sits on the left, and every hop to the right is one more join away from the start.
Three situations where this view saves time:
pg_dump --schema-only or mysqldump --no-data and the foreign keys come back as a schema map.The database never reads your query top to bottom. Logically, the engine builds the row set first, then filters, then groups, and only then computes the SELECT list. The Run order tab lays out your own query in this sequence, with the written position next to each step.
| Run step | Clause | What exists at this point |
|---|---|---|
| 1 | FROM and JOIN | Raw rows from every table, paired by the join conditions |
| 2 | WHERE | Individual rows. No groups, no output aliases |
| 3 | GROUP BY | One row per group. Ungrouped columns now need an aggregate |
| 4 | HAVING | Groups, so SUM() and COUNT() work here |
| 5 | SELECT | Output columns and their aliases, plus window functions |
| 6 | DISTINCT | Duplicate output rows removed |
| 7 | ORDER BY | Aliases from step 5 are usable |
| 8 | LIMIT / OFFSET | The final slice handed back |
This table explains the most common SQL error people search for. Write WHERE revenue > 500 where revenue is a SELECT alias, and PostgreSQL replies with column "revenue" does not exist, while MySQL reports Unknown column 'revenue' in 'where clause'. The alias is born at step 5, three steps after WHERE runs. The review panel flags this before you hit run.
One caution. This is the logical order, the one which decides what each clause sees. The query planner is free to reorder joins and push filters down for speed. For the physical plan, run EXPLAIN in PostgreSQL or MySQL, or turn on the actual execution plan in SQL Server Management Studio.
The checks look for patterns which run without errors and still return the wrong rows, or the right rows slowly. Each finding names the clause involved.
This one survives code review because the query looks right. A LEFT JOIN keeps stores with no reviews by filling the review columns with NULL. A WHERE filter on those columns then throws the NULL rows away, and the stores you meant to keep vanish.
SELECT s.name, r.rating
FROM stores s
LEFT JOIN reviews r ON r.store_id = s.id
WHERE r.rating >= 4;
SELECT s.name, r.rating
FROM stores s
LEFT JOIN reviews r ON r.store_id = s.id
AND r.rating >= 4;
Load the LEFT JOIN trap sample above to see the warning on a live query.
WHERE, or a JOIN missing its ON. Two tables of 1,000 rows return 1,000,000. The Comma join sample shows one.NOT IN over a subquery. One NULL in the subquery result makes the whole predicate unknown, and the query returns zero rows with no error. NOT EXISTS does not have this problem.WHERE YEAR(placed_at) = 2026 forces a calculation on every row and skips a plain index on placed_at. A date range does the same job with the index.LIKE '%mart' has no prefix to seek on, so the database scans the full table.Give the viewer a script of CREATE TABLE statements and the diagram switches to schema mode. Every column shows its declared type, primary keys get a PK badge, and each foreign key draws a line from the referenced table to the referencing one. With more than one table in the script, a Whole schema entry appears first in the statement menu so you see every relationship on one board.
Foreign keys added later through ALTER TABLE ... ADD FOREIGN KEY are picked up too, which matters for PostgreSQL dumps, since pg_dump writes constraints as separate ALTER statements after all the tables. A table referenced by a key but never created in the script still appears, with a dashed outline and a note, so a missing dependency is obvious before you run the script on an empty database.
Load the Schema DDL sample to see four tables from an order system laid out by dependency: customers and products on the left, order lines on the far right.
The viewer reads the text of your SQL. There is no database connection, which keeps your data private and also sets hard limits.
orders holds 200 rows or 200 million, so a flagged leading wildcard on a tiny lookup table is harmless.FROM shows as one card with its output columns. Subqueries inside WHERE are counted, not drawn. CTEs get their own entry in the statement menu instead.BEGIN ... END blocks, cursors and dynamic SQL built from strings fall outside the diagram. MERGE statements are listed without a drawing.INSERT rows is not, so export with --no-data or --schema-only first.For layout only, without a diagram, the SQL Formatter rewrites the same query with consistent indentation.
Privacy, dialects, and why a diagram sometimes looks thinner than the query.
No. The SQL is parsed inside your browser tab and nothing reaches a server, so production queries with customer names or internal table names stay on your machine. In exchange, the viewer never sees real data, row counts or indexes.
MySQL, MariaDB, PostgreSQL, SQL Server, SQLite and the common Oracle syntax. Backtick, double quote and square bracket identifiers are all understood, along with PostgreSQL dollar quoted strings, MySQL hash comments, SQL Server TOP and CROSS APPLY, and LATERAL joins.
Columns written without a table prefix in a query with two or more tables have no known owner. Only the database schema says whether status lives in orders or in customers. Those names appear in the review panel instead. Prefix them with the table alias and they move onto the right card.
No. Run order is the logical sequence the SQL standard defines, which decides what each clause is allowed to see. The execution plan is the physical route the optimizer picked, often joining tables in a different order. For the plan, run EXPLAIN or EXPLAIN ANALYZE in PostgreSQL and MySQL, or enable the actual execution plan in SQL Server.
Paste the whole script. With more than one statement present, a Show menu appears next to the counters. The menu lists every statement, each part of a UNION, and each CTE separately. The Highlighted SQL tab shades the statement currently on display.
A WHERE condition on the right-hand table of a LEFT JOIN removes the rows where no match was found, because their columns are NULL and NULL fails almost every comparison. The result matches an INNER JOIN. Move the condition into the ON clause to keep unmatched rows. Conditions using IS NULL or COALESCE are left alone, since they are usually deliberate.
Not as a file yet. A screenshot of the diagram tab works for tickets and pull request comments. The diagram redraws from the SQL every time, so keeping the query text alongside the screenshot is the reliable way to reproduce the drawing.