Original SQL
Changed SQL

SQL Diff: Compare Two SQL Files Online

Paste, format, and compare two SQL queries or schema files side-by-side. Works for PostgreSQL, MySQL, SQL Server, SQLite, and Oracle.

What is the SQL diff tool?

A free, in-browser tool for comparing two SQL files. Paste an old CREATE TABLE on the left, the new one on the right, and the changes light up character by character. Same workflow for two versions of an analytics query, an ORM-emitted statement, or a pg_dump output. Nothing leaves your machine.

The diff is text-level. It does not parse the SQL into an AST and it does not understand semantic equivalence: SELECT a, b and SELECT b, a read as different to the diff even though both return the same rows in a different order. For 95% of code-review tasks (schema migrations, query refactors, ORM output comparison) text diff is what you actually want, because the order of columns and clauses is part of how the team reads the change.

If you have ever tried to find the one new WHERE clause in a 400-line analytics query that started returning a different row count, this is the tool that gets you there in seconds. For non-SQL prose, our text diff tool is the right pick. For ORM result payloads in JSON, JSON diff handles object reordering more cleanly. For legacy DDL exports written as XML metadata, XML diff is the better fit.

How the diff actually works

Under the hood the diff runs at the character level, then a semantic pass shifts the highlights so they land on identifiers, keywords, and clause boundaries rather than random punctuation. Insertions show in green on the right, deletions in red on the left. Each pane has a Format button that reflows the SQL with consistent indentation and one statement per line, which kills most formatting noise before you compare.

SQL is a family of dialects, not a single language. The ISO/IEC 9075 standard defines a core grammar, but every database extends it: PostgreSQL has dollar-quoted strings and RETURNING, MySQL has backtick identifiers and ON DUPLICATE KEY UPDATE, SQL Server uses TOP and bracketed identifiers, SQLite tolerates almost anything, and Oracle has ROWNUM and CONNECT BY. Diffing across dialects is fine; the tool will not warn you that a snippet is invalid in one of them.

The other thing to know: comparing query text is not the same as comparing query plans. Two statements with identical text can produce wildly different plans on different statistics, and two cosmetically different statements can produce the identical plan. For plan comparison, the right tools are EXPLAIN ANALYZE in psql or the equivalent in your database client (DataGrip, DBeaver, SSMS). For text refactor review, the diff is what you want. Background reading on the language itself is on Wikipedia.

How to compare SQL in three steps

Two text panes, one diff. There is no signup, no upload, no server round-trip.

  1. 1

    Paste or upload your SQL

    Paste the old SQL on the left, the new SQL on the right. Or click Upload on either side to load a .sql, .ddl, or .psql file directly. The Sample button fills both panes with a small orders schema example if you want to see the tool in action first.

  2. 2

    Format both sides for a fair comparison

    Click Format on each pane to reflow the SQL with consistent indentation, one statement per line, and uppercase keywords. This kills the noise from a Datagrip-formatted query on one side and a hand-typed query on the other, so the diff highlights real schema and clause changes instead of whitespace and case differences.

  3. 3

    Read the diff

    Deletions appear with a red highlight on the left, insertions with a green highlight on the right. Scroll either side and the other follows. The change counts in each header tell you how many distinct edits the diff found across columns, joins, predicates, and DDL elements.

When SQL diff is the right tool

Reviewing a schema migration before applying

A migration PR adds three columns and tweaks two indexes. Paste the previous CREATE TABLE against the new one and the additions stand out: a currency CHAR(3) NOT NULL column was added without a DEFAULT, which will fail on any existing row. Catching that before the migration runs against prod, instead of after the deploy alarm fires at 2am, is the entire point of this workflow. Same value for ALTER TABLE statements where a column type narrows from VARCHAR(255) to VARCHAR(50).

Diffing two versions of an analytics query

A reporting query that returned 12,400 rows yesterday returns 8,900 today. Diff yesterday's saved query against today's and the offending change surfaces: a LEFT JOIN quietly became an INNER JOIN, or a WHERE status <> 'cancelled' got added by someone trying to clean up the dashboard. The text diff lands you on the line in seconds, where reading both queries side-by-side in Slack would take ten minutes.

Comparing ORM-generated SQL between app versions

After a Hibernate or SQLAlchemy upgrade, queries that used to be three joins are now four with extra subqueries, or parameter binding switched from ? to $1 placeholders. Capture the SQL from your query log on each app version (psql log_statement = all, MySQL slow log, or your APM tool) and diff them. The alias names will be noisy (t1, t2, generatedAlias0), but the actual structural change is usually the only thing the diff highlights as a real edit.

Validating a DDL refactor preserves intent

You renamed user_orders to orders and split a JSON blob column into normalised tables. Paste the pg_dump --schema-only output before and after the refactor; the diff makes it obvious whether you preserved every constraint, index, and default. The trap to watch for is a NOT NULL that quietly went away during the rename, or a UNIQUE constraint that got dropped because nobody re-added it on the new table.

Checking staging schema matches prod

Run pg_dump --schema-only --no-owner against staging and prod, then diff the two. The differences should be exactly the migrations queued for the next deploy. Anything else, an extra index, a different column type, a stray test table, is a sign of schema drift that will bite during the actual deploy. Same approach with mysqldump --no-data for MySQL and SCRIPT TO for SQL Server, or DataGrip's schema export across any database.

Reviewing dialect porting (Postgres to MySQL)

Porting a query from PostgreSQL to MySQL or back again means tracking which functions, types, and clauses need to change: SERIAL versus AUTO_INCREMENT, NOW() versus CURRENT_TIMESTAMP, RETURNING versus LAST_INSERT_ID(). Diff the original against the ported version and the dialect substitutions all surface. The diff will not validate the SQL on the target dialect, so still run it through psql or mysql to confirm, but the line-by-line view tells you which decisions to double-check.

SQL quick reference

A short cheat sheet for the dialect and parsing edge cases this diff surfaces most often. Grounded in the ISO SQL standard and the major vendor docs.

TopicWhat this tool does
Dialect driftPostgreSQL, MySQL, SQL Server, SQLite, and Oracle each extend the ISO SQL standard with their own syntax. SERIAL, AUTO_INCREMENT, IDENTITY, and GENERATED ALWAYS AS IDENTITY are four ways to write the same idea.
Identifier case foldingPostgreSQL folds unquoted identifiers to lowercase, so Users and users are the same table. MySQL depends on lower_case_table_names and the underlying filesystem. SQL Server is case-insensitive by default but respects the collation. Quote identifiers with "Users" (standard) or backticks (MySQL) or brackets (SQL Server) to preserve case.
Keyword casingSQL keywords are case-insensitive everywhere, so SELECT and select are identical to the parser. Convention is uppercase for keywords and lowercase for identifiers; most style guides and the ISO spec follow it. Mixed case is valid and will execute, but tools like sqlfluff and sqlfmt normalise it.
Comment stylesTwo forms: -- line comment (terminates at end of line) and /* block comment */ (no nesting in standard SQL, though some dialects allow it). MySQL also supports # for line comments as an extension. The diff treats comments as text and will show comment edits like any other change.
Statement separatorsSemicolon (;) terminates a statement in standard SQL. Most clients require it between statements in a script. Some clients (SSMS) use GO as a batch separator instead, which is not part of the SQL grammar; it is a client directive.
Dollar-quoted strings (PostgreSQL)PostgreSQL supports $tag$ ... $tag$ for string literals so you do not need to escape single quotes inside, especially useful for function bodies. $$ SELECT 'hello' $$ is valid and common in CREATE FUNCTION blocks. Other dialects do not parse this syntax.
Parameter binding placeholdersNo standard. PostgreSQL and ORMs targeting it use $1, $2. JDBC, MySQL, and ODBC use ?. Named placeholders :name are common in Oracle and many ORM string templates. ORM-generated SQL across versions often differs only in placeholder style; format both sides before diffing.
EncodingUTF-8 is the universal default for SQL files in 2026. Older Windows-origin scripts may still be saved as UTF-16 LE with a BOM, which shows as a phantom character at line 1 in a text diff. If two files look identical but the diff flags a one-character change at the start, suspect the BOM and re-save with explicit UTF-8.

SQL diff: frequently asked questions

How is this different from running EXPLAIN ANALYZE on both queries?

Different layer of the stack. EXPLAIN ANALYZE shows you the query plan, what the optimiser decided to do given the statistics it has. This tool diffs the query text, which is what changed in your repo. You usually want both: this diff to spot the refactor (a join type changed, a predicate moved), then EXPLAIN ANALYZE in psql or your client to confirm the optimiser actually picked a sensible plan for the new shape. The two are complementary, not substitutes.

Is the diff dialect-aware (PostgreSQL vs MySQL vs SQL Server)?

No. The diff treats the input as text, so it does not parse PostgreSQL, MySQL, SQL Server, SQLite, or Oracle differently and it does not flag dialect-incompatible syntax. That is intentional: it means you can diff across dialects, which is exactly what you want when porting a query. If you need dialect-aware linting, run the SQL through sqlfluff or your database's parser separately. For the review use case ("what changed in this query"), text-level is the right granularity.

Does the tool format SQL for me?

Yes, the Format button on each pane reflows the SQL with consistent indentation, uppercase keywords, and one statement per line. It is a basic formatter, not a sqlfmt or sqlfluff replacement: it does not rewrite implicit joins to explicit, and it does not enforce a specific dialect's style guide. The point is to kill formatting noise before the diff, so a Datagrip-formatted query on one side and a hand-typed query on the other compare cleanly.

Does it normalise keyword casing (SELECT vs select)?

The Format button uppercases keywords, which is the convention in most style guides and in the ISO SQL standard. If you do not click Format, mixed case shows up as a diff because the underlying engine is character-based. SQL keywords are case-insensitive to every database, so select and SELECT execute identically. Identifier case is a different question and varies by database; see the quick reference below for how Postgres, MySQL, and SQL Server differ.

How do I deal with ORM-emitted aliases like t1, t2, generatedAlias0?

ORM output is noisy by design: Hibernate generates generatedAlias0, generatedAlias1, and SQLAlchemy uses anon_1, anon_2. If two ORM versions assign the same aliases in a different order, the text diff will highlight every alias swap as a change even when the query is structurally identical. The practical fix is to format both sides, ignore the alias-only differences (they are usually obvious as paired red and green spans on the same line), and focus on the actual structural edits: a new join, a different WHERE clause, a removed column.

Are there size limits on the SQL I paste?

Soft limits, not hard ones. The diff runs in your browser, so the upper bound is your browser's memory and how long you are willing to wait. A 5,000-line pg_dump output diffs in well under a second on a modern laptop. A 200,000-line dump might take several seconds and could hit memory limits on lower-end devices. For database dumps that big, the right workflow is to diff schema-only first (pg_dump --schema-only), then drill into specific tables if you need data-level comparison.

Why does the same query show as different across PostgreSQL and MySQL?

Because the dialects genuinely differ and the diff is text-based, not parser-aware. Identifier quoting alone is three styles: PostgreSQL uses "users", MySQL uses backticks, SQL Server uses [users]. Pagination splits even harder: PostgreSQL and MySQL use LIMIT/OFFSET, SQL Server uses TOP n or FETCH FIRST n ROWS ONLY, and Oracle historically used ROWNUM. PostgreSQL RETURNING maps to SQL Server OUTPUT, and MERGE syntax varies between vendors. The diff faithfully highlights every one of those, which is what you want when porting. If you want semantic equivalence, normalise both sides through sqlfluff with sqlfluff fix and the right dialect set.

Can I diff two pg_dump or mysqldump outputs?

Yes, the text diff handles dumps fine, but raw pg_dump or mysqldump output includes volatile fields that mask the structural changes you actually care about: timestamp comments at the top, sequence current-value lines, and ALTER TABLE ... OWNER TO ... statements that shift between captures. Use pg_dump --schema-only --no-owner --no-acl to strip ownership and grants. For MySQL, mysqldump --no-data --skip-comments is the equivalent. For migration review specifically, the right diff is between two consecutive migration scripts in your repo, not between two full schema dumps.

Privacy and how this works

Your SQL never leaves your browser. The formatter and diff both run on your machine, locally. No analytics on your input, no logs, no "helpful" cloud round-trip, which matters when the SQL you are pasting contains real table names, real column names, or real production data in a sample row. The language is documented in ISO/IEC 9075, and the dialect references are PostgreSQL, MySQL, SQL Server, and SQLite.