The error message you get at the worst possible time
Runtime is a terrible place to learn about a syntax error. The migration window opens at 2 a.m., the script has been reviewed twice, and the database's answer to your 400-line ALTER sequence is a laconic `ERROR: syntax error at or near "FROM"` with a position number and nothing else. You count characters in the dark, discover the trailing comma, and resolve to never again run SQL that has not been checked by something whose only job is checking.
This page is that something. Paste a query or an entire migration script, pick a dialect — or let the tool guess it — and get a verdict in milliseconds: valid, or invalid with the exact line, column, and offending token. It parses; it never executes. There is no database behind it, which is precisely why it can sit between you and the 2 a.m. window without asking for a connection string first.
The syntax-check niche has been quietly underserved forever: formatter sites will happily reformat your SQL and silently fail on the broken parts, and 'paste it into a scratch database' is advice that assumes you have a scratch database, credentials, and patience. A validator page that reports exact error positions for three major dialects, offline, fills the actual gap.
What validation means on this page
Two kinds of correctness matter for SQL, and conflating them causes most of the confusion in this corner of the internet. Syntactic correctness asks: does this text parse under the dialect's grammar — balanced parentheses, clauses in the right places, no keywords where expressions belong? Semantic correctness asks: do these tables and columns exist, are the types compatible, will this query actually run against my schema? The first question is answerable from text alone. The second requires a live database or at least a schema dump.
This validator answers the first question, completely and offline. That division of labor is not a limitation so much as a boundary with useful properties: no connection means no credentials to manage, no risk of accidentally running the DELETE you meant to lint, and no possibility that checking a query disturbs anything. Semantic checks belong to your database's own tooling — it is the only party that knows the truth.
In practice, syntax is also where most late-caught errors live. Trailing commas before a closing parenthesis, unbalanced quotes, an alias that happens to be a reserved word, backticks in a Postgres script — every one of these is a full runtime failure that a parse would have caught in milliseconds. The validator catches the class of error that is embarrassing precisely because it was always checkable.
Checking a query, start to finish
Paste your SQL
A single statement or a semicolon-separated script. Comments in all the usual styles survive and are attributed to the right statement.
Confirm the dialect
Auto-detect scores MySQL, PostgreSQL, and SQLite fingerprints in your text and proposes the strongest match; override if you know better. Dialect matters — the same text can be valid MySQL and invalid Postgres.
Read the verdict
Valid means every statement parsed under the chosen grammar. Invalid lists each error with statement number, line, column, the offending token, and a one-line explanation of what the grammar expected at that point.
Fix and re-check
The validator re-parses on every keystroke, so the error list updates as you edit. When it goes quiet, your syntax is clean in that dialect — semantic questions are then a matter for the database.
Four steps, no account, no connection, and no file to install. The tool is deliberately boring in the way good infrastructure is boring.
How the validator reads your SQL
Under the hood is the same two-stage machinery every compiler uses, sized for SQL. The lexer first turns your text into tokens: keywords, identifiers, string and numeric literals, operators, comments. It tracks line and column for every token it emits, which is where exact error positions come from — the parser consumes tokens, and when the grammar rejects one, the position attached to that token is the position reported to you.
The parser then walks the token stream against the dialect's grammar. SQL grammars are mostly predictable — a SELECT is a column list, then FROM, then an optional WHERE expression tree, and so on — so a parser failure localizes cleanly: the first token that cannot extend a valid production is the error. What the tool adds on top of the raw parse is error translation, converting `expected expression, found keyword 'FROM'` into the sentence a human wants: *there is a trailing comma before FROM, or a missing column name.*
tokenize(text) -> [ {kind, value, line, col}, ... ]
parse(tokens, dialect) -> AST | Error{token, expected}
on Error:
statement index = count of ';' before error token
line / column = token position
hint = rule table lookup(token.kind, expected)Dialect auto-detection runs before parsing as a scoring pass over the token stream. Backticks vote MySQL; double-quoted aliases vote Postgres; `AUTOINCREMENT` as one word votes SQLite; `LIMIT n` is neutral territory while `TOP n` votes T-SQL; `::` casts are a Postgres landslide. No single signal is conclusive, so the detector sums the evidence and shows you the tally — you are overruling an informed guess, not a coin flip.
Multi-statement scripts get the same treatment with a bit more bookkeeping: the input is split on semicolons at the token level — never inside string literals or comments — and each statement is parsed and scored independently. The error list therefore reads like a build log: statement 7 fails at line 89, statement 12 fails at line 140, everything else green. That granularity matters for migrations, where you want to know whether the script is salvageable with two edits or fundamentally wrong, before the change window opens and answers the question for you.
One subtlety in the design worth knowing: the validator reports the first grammar-breaking token, not every downstream consequence. A missing closing parenthesis at line 3 can surface as an unexpected end-of-input at line 40, because the parser was legitimately inside a subexpression the whole way down. The tool handles the common case — tracking back to the unmatched opener — because it maintains a parenthesis stack during the walk, which is also how you get 'opened at line 3, column 18' as part of the message instead of a shrug.
Four queries it catches, with exact positions
Every error class below appears constantly in the wild — each one is a runtime failure that costs minutes to hours, and each one is caught here in the time it takes to paste:
SELECT name, FROM users;Input
SELECT name, FROM users;
Output
Line 1, column 14: expected an expression, found keyword FROM. The comma at column 13 promises another column; either add it or delete the comma.
SELECT id FROM users WHERE (role = 'admin' AND active = 1;Input
SELECT id FROM users WHERE (role = 'admin' AND active = 1;
Output
Line 1: parenthesis opened at column 31 never closes. The semicolon arrives while the expression is still open — add ')' before the semicolon, or remove the '(' if grouping was not intended.SELECT `id`, `name` FROM users;Input
Dialect: PostgreSQL — SELECT `id`, `name` FROM users;
Output
Line 1, column 8: unexpected token '`'. PostgreSQL quotes identifiers with double quotes ("id"), not backticks; backticks are MySQL's delimiter.SELECT total order FROM invoices;Input
SELECT total order FROM invoices;
Output
Line 1, column 14: 'order' is a reserved word in this dialect and cannot be an unquoted alias. Write AS total_order, or quote the identifier.
The fourth case deserves special affection, because it is the one engines report worst: several databases will accept `order` as an alias in some contexts and reject it in others, making the bug intermittent by construction. A grammar check applied uniformly catches it every time, which is what grammar checks are for.
Count the character columns yourself on the first example — `SELECT name, ` is thirteen characters, so FROM begins at column 14, exactly where the error points. That precision is not a nicety; it is the difference between an error report you act on and an error report you re-derive.
The dialect matrix
Most 'invalid SQL' turns out to be valid SQL in the wrong accent. The table below is the compatibility cheat sheet the validator effectively enforces — the places where the major dialects disagree on spelling:
| Feature | MySQL | PostgreSQL | SQLite | T-SQL |
|---|---|---|---|---|
| Identifier quoting | `backticks` | "double quotes" | "double quotes" | [brackets] |
| Row limit | LIMIT 5 | LIMIT 5 | LIMIT 5 | TOP 5 / FETCH FIRST 5 |
| Auto-increment DDL | AUTO_INCREMENT | GENERATED ... AS IDENTITY | INTEGER PRIMARY KEY | IDENTITY(1,1) |
| String concatenation | CONCAT(a, b) | a || b | a || b | a + b |
| NULL fallback | IFNULL(a, b) | COALESCE(a, b) | IFNULL / COALESCE | ISNULL(a, b) |
| Boolean literals | TINYINT 0/1 | true / false | INTEGER 0/1 | BIT 0/1 |
| Upsert | ON DUPLICATE KEY UPDATE | ON CONFLICT DO UPDATE | ON CONFLICT DO UPDATE | MERGE |
Paste a query containing backticks with the dialect set to PostgreSQL and the validator flags the backtick; switch the dialect to MySQL and the same text passes. That asymmetry is the point of dialect-aware validation rather than lowest-common-denominator checking — the tool models how each database actually reads your text, not an average of them that matches none.
The matrix is also a migration checklist. Moving a report from MySQL to Postgres means auditing every row of that table in your query text; the validator automates exactly this audit, one parse at a time, and its error list is effectively the diff between your SQL and the destination dialect's expectations.
The upsert row earns its place at the bottom of that table as the most-migrated feature in practice, because the three spellings are not stylistic variants — they encode genuinely different conflict-resolution models. MySQL's ON DUPLICATE KEY UPDATE fires on any unique-key collision; Postgres and SQLite's ON CONFLICT let you name the exact constraint in conflict; T-SQL's MERGE has semantics of its own and a documented list of caveats longer than the syntax. When a converted script validates but behaves differently in production, this row is usually the reason — the grammar agreed, the meaning did not.
What syntax-valid does not mean
A passing grade from this tool has a specific scope, and overselling it would get someone hurt:
- No schema awareness. `SELECT color FROM users` validates even if users has no color column. Column and table existence are semantic facts; only your database knows them.
- No type checking. Comparing a string to an integer in a WHERE clause parses fine everywhere and means something different in each engine.
- No permission modeling. A query can be valid for the dialect and still fail at runtime for grants, locks, timeouts, or resource limits.
- False negatives on exotic syntax. Vendor extensions outside the parser's grammar — certain JSONB operators, procedural blocks, some ANALYZE and VACUUM spellings — can be rejected despite being legal. When the validator disagrees with a database that accepted the query, the database wins.
- No logic review. A perfectly parsed DELETE without a WHERE clause is a perfectly parsed catastrophe. The validator checks grammar, not judgment.
Stating the boundary this plainly is also what keeps the tool honest — a validator that implies more knowledge than it has is worse than no validator, because its verdicts start substituting for thinking instead of accelerating it.
Who validates before running
The heaviest users are people touching production migrations. A script with thirty ALTER statements gets pasted here before the change window opens, because the cost of a mid-migration syntax error is not the error — it is the partially-applied script in front of it. Statement-level error positions let the fix happen before anything runs at all.
The second cohort is the SQL-in-code crowd: query builders, ORM-generated SQL captured from logs, embedded analytics queries in application code. Pasting the generated text into a validator is the fastest way to see what the framework actually emitted, and dialect pinning catches the classic failure where an ORM targets Postgres in development and the production warehouse speaks a different flavor.
Students and interview-preparers round it out — a syntax checker is instant feedback while drilling, with no database to install and no environment to break. And there is a persistent, honorable population pasting SQL copied from documentation or a forum post, checking which dialect it actually belongs to before adapting it. The auto-detector answers that question in one glance, which is a nicer experience than the traditional method: running it and reading the error.
Around this tool sits the rest of the loop: the SQL Query Explainer narrates what a validated query does clause by clause, SQL on CSV runs real SQL against flat files with no server, and the JSON Formatter & Validator and YAML Validator apply the same check-before-you-run discipline to the config formats that travel with database code.
Validator versus a scratch database versus formatter sites
| Approach | Catches syntax errors | Error position | Setup | Risk of running your SQL |
|---|---|---|---|---|
| This validator | Yes, at parse time | Exact line, column, token | None — paste and go | Zero — nothing executes |
| Scratch database | Yes, at run time | Engine-dependent, often vague | Install, credentials, seed data | Nonzero — you are connected to something that can execute |
| Formatter sites | Incidentally — formatting fails silently or half-applies | Rarely reported | None | Uploads your SQL to their server |
| Asking an AI checker | Sometimes, with invented confidence | Approximate at best | None | Query text leaves your machine |
The scratch database is the traditional answer and it works, but it is heavyweight for the question being asked: standing up a container to catch a trailing comma is using a live database as a spell checker. Formatter sites are the accidental answer — people paste broken SQL into pretty-printers, the formatter produces garbage or nothing, and the user learns less than they came with. The dedicated-validator row exists because the question deserves a tool shaped like the question.
The AI row is included with respect and one caveat: probabilistic checkers can be genuinely helpful for explanation, but their verdicts on syntax arrive with unearned confidence, including inventing rules that do not exist in any dialect. When the answer needs to be exact — and syntax is nothing if not exact — a parser with a grammar is the right instrument.
Offline by architecture
The validator ships its grammar to your browser and does all its work there. Lexing, parsing, error translation, dialect scoring — every stage runs in the tab, and the page makes no request after load. Your SQL, which in production form contains table names, literal filters, and occasionally a customer ID somebody pasted into a WHERE clause in 2019, stays exactly where it already was.
The offline property is a direct consequence of doing parsing instead of execution. A tool that needs to run queries needs a database somewhere; a tool that needs only a grammar needs nothing but the page itself. That is why this can work on a plane, inside a customer's network with no outbound access, and on machines where installing a scratch database is a four-week procurement conversation.
There is also a quiet speed argument: no network round-trip means validation latency is parser latency, single-digit milliseconds even for long scripts. The error list updates as you type, which changes the editing rhythm from paste-pray-fix to something closer to a spell checker's red squiggle — immediate, ignorable when clean, pointed when not.
SQL validator questions
Which SQL dialects can it validate?
MySQL/MariaDB, PostgreSQL, SQLite, and common T-SQL. You can pin the dialect manually or let the auto-detector guess from syntax fingerprints — backticks suggest MySQL, double-quoted strings in Postgres, IFNULL versus COALESCE, LIMIT versus TOP. The detector shows its reasoning so you can overrule it with one click.
Does it check that my tables and columns exist?
No — that is semantic validation, and it requires a live schema to validate against. This tool checks syntax: does the query parse cleanly in the chosen dialect? A query referencing a table that does not exist anywhere will validate perfectly, because structurally it is a well-formed query. The trade is deliberate: syntax checking needs no connection, no credentials, and no risk.
Why did my definitely-valid query fail?
Almost always a vendor extension the parser's grammar does not cover — PostgreSQL's JSONB operators like @> and #>>, exotic ANALYZE syntax, or a stored-procedure body in a nonstandard dialect. The validator's failure mode on such queries is a false negative: it says invalid when the database would accept it. When that happens, pin the dialect explicitly and retry; if it still objects, treat the verdict as 'unverified' rather than 'broken.'
Can it validate more than SELECT statements?
Yes — INSERT, UPDATE, DELETE, CREATE TABLE, ALTER, and common DDL parse under the same grammar. Multiple statements separated by semicolons are validated individually, and the error list reports the statement number alongside line and column, so a failure in statement 14 of a migration script points at exactly the right semicolon-delimited block.
How exact is the error position?
To the character. The parser tracks line and column for every token it consumes, so the error you get names the token that broke the grammar — including the position of an unclosed parenthesis opened fifty lines earlier. Copying the reported position into your editor's go-to-line box lands you on the offending character.
Does the validator execute anything?
No. Parsing is a read-only analysis of text; the query is never sent to a database, never run, and cannot touch data. A syntactically perfect DELETE validates identically to a syntactically perfect SELECT — the tool has no idea and no need to know what the statement would do if executed.
Is my SQL uploaded anywhere?
No. The parser runs in the browser tab, and the query text never makes a network request. Queries often contain schema details, literal values, or internal naming — this page was built so none of that has to leave your machine to get checked.
Standards and manuals behind the checks
Sources and further reading
- PostgreSQL — SQL syntax, lexical structure, and quoting rules(postgresql.org)
- SQLite — SQL language grammar index(sqlite.org)
- MySQL 8.0 — Keywords and reserved words(dev.mysql.com)
- node-sql-parser — the multi-dialect parser behind the validator(github.com)
We cite primary sources (standards bodies, government agencies, official documentation) wherever they exist so you can verify every number on this page yourself.