A better SQL validator and comparison with existing SQL validators
Oct 7, 2025, 7:24:53 AM
(updated Oct 8, 2025, 7:14:23 AM)
Most SQL validators do one thing: hand your query to a parser, stop at the first syntax error, and throw a generic message back at you, usually detached from the code and without a minimal fix. For real-world work, that’s not enough. When you’re debugging a gnarly migration, reviewing a teammate’s query, or trying to teach a junior dev why the parser choked, you need more than “syntax error at or near …”. You need precise locations, explanations you can act on, and a way to see and trust the fix without playing whack‑a‑mole through your query.
Typical shortcomings:
| Shortcoming | Result |
|---|---|
| Only the first error is reported; subsequent errors are hidden. | You fix one thing just to hit the next; wasted cycles and whack‑a‑mole debugging. |
| Errors are shown outside the editor, not inline at the exact location. | Context switching and mis-clicks slow you down and hide the real source of the issue. |
| Messages are vague (“syntax error at or near…”) and lack actionable guidance. | You’re left guessing at intent; steeper learning curve for less experienced teammates. |
| No suggested fix, or a “fix” that still fails to parse. | Dead ends and eroded trust in the tool. |
| No side-by-side diff to see what changed and why. | Harder reviews and risky copy‑paste fixes you can’t audit. |
Those pain points sketch a straightforward blueprint for a better validator: one that keeps going after the first failure to surface multiple issues in a single pass, explains each error in plain language with exact locations, proposes a corrected query with a clear rationale, and lets you compare the original and the fix side-by-side without leaving the editor. I built one here, with AI assistance to make the explanations and fixes actually useful.
Inline comments help, but inline annotations with highlights and per-line explanations are better.
SQL validation example
Below is an intentionally broken PostgreSQL query (validated against PostgreSQL 17). It includes multiple syntax and dialect errors.
SELECT employee_id, fist_name || ' ' || last_name AS full_name, 'Employee' AS contact_type, hire_date COUNT(reports_to)FROM employeesWHERE salary > 50000ORDER BY full_nameUNIONSELECT contact_name, 'Customer' AS contact_type, last_order_dateFROM 'customers'WHERE total_revenue > 10000 AND, status = "Active"LIMIT '25';The validator supports most popular SQL dialects, including MySQL, SQLite, Microsoft SQL Server (T‑SQL), Oracle Database (PL/SQL), Google BigQuery, and more.
Validating the SQL query
Running it through the SQL validator finds the errors, annotates them inline, explains each issue, and produces a corrected query. Here is the SQL validator’s response (highlighted with gray background):
SELECT employee_id, fist_name || ' ' || last_name AS full_name, 'Employee' AS contact_type,
hire_date
COUNT(reports_to)FROM employeesWHERE salary > 50000
ORDER BY full_name
UNIONSELECT contact_name, 'Customer' AS contact_type, last_order_dateFROM
'customers'WHERE
total_revenue > 10000 AND,
status = "Active"
LIMIT '25';| # | Explanation |
|---|---|
| 1 | Missing comma after hire_date: I added a comma after hire_date so the next select item is properly separated. |
| 2 | Mixing aggregate with non-aggregate columns: COUNT(reports_to) appeared alongside non-aggregated columns without GROUP BY. I replaced the aggregate with a correlated subquery that returns the count per employee to avoid GROUP BY. |
| 3 | ORDER BY before UNION: ORDER BY cannot appear before a UNION. I moved the ORDER BY to after the final SELECT (after the UNION). |
| 4 | Column count mismatch in UNION: The two SELECTs returned different numbers of columns. I modified the second SELECT to return the same five columns (using NULL and a literal 0 where appropriate). |
| 5 | Quoted table name: ‘customers’ used single quotes (string) instead of an identifier. I removed the quotes and used the customers identifier. |
| 6 | Stray comma in WHERE: Removed the stray comma after AND in the second WHERE clause. |
| 7 | Wrong string quoting: “Active” used double quotes (identifier in SQL) instead of a string literal. I replaced it with ‘Active’. |
| 8 | Quoted LIMIT value: LIMIT ‘25’ used a quoted string; I changed it to the numeric literal 25. |
Valid SQL query
SELECT employee_id, fist_name || ' ' || last_name AS full_name, 'Employee' AS contact_type, hire_date, (SELECT COUNT(*) FROM employees e2 WHERE e2.reports_to = e.employee_id) AS reports_countFROM employees eWHERE salary > 50000
UNION
SELECT NULL AS employee_id, contact_name AS full_name, 'Customer' AS contact_type, last_order_date AS hire_date, 0 AS reports_countFROM customersWHERE total_revenue > 10000 AND status = 'Active'ORDER BY full_nameLIMIT 25;Click “view diff” to compare the invalid and corrected queries:
The diff uses the Monaco Editor, the code editor that powers VS Code.
Existing SQL validators
To gauge the current state of web-based SQL validators, I tried three of the top Google results for “SQL validator” (searched from New York City location). I used the same invalid query as above.
1. RunSQL
Findings:
- Reports only the first error.
- Shows the error message outside separate from the SQL code.
Try it: runsql.com/sql-validator
2. Aiven
Findings:
- Reports only the first error, with a small inline indicator.
- Offers a “fixed” query, but it’s still invalid (notes the fixes with comments above the corrected SQL query).
Returned “fixed” SQL (truncated for clarity):
-- Fixed: Several syntax errors corrected.-- ...SELECT employee_id, first_name || ' ' || last_name AS full_name, 'Employee' AS contact_type, hire_date AS hire_date, (SELECT COUNT(*) FROM employees e2 WHERE e2.reports_to = e1.employee_id) AS report_countFROM employees e1WHERE salary > 50000ORDER BY full_nameUNIONSELECT NULL, contact_name, 'Customer' AS contact_type, last_order_dateFROM customersWHERE total_revenue > 10000 AND status = 'Active'LIMIT 25;Remaining issues:
- ORDER BY appears before UNION (must come after the final SELECT or inside a subquery).
- Column counts differ across the UNION branches.
It also “fixed” fist_name to first_name, which isn’t a syntax error (it’s a data/column name issue).
Try it: aiven.io/tools/sql-syntax-checker
3. SQLValidator
Findings:
- Very simple UI. You can pick a database engine (not version).
- The “fixed” query is still invalid.
SELECT employee_id, first_name || ' ' || last_name AS full_name, 'Employee' AS contact_type, hire_date, COUNT(reports_to) OVER () AS report_countFROM employeesWHERE salary > 50000
ORDER BY full_name
UNIONSELECT contact_name, 'Customer' AS contact_type, last_order_dateFROM customersWHERE total_revenue > 10000 AND status = 'Active'LIMIT 25;Remaining issues:
- ORDER BY still appears before UNION.
- Column counts don’t match across the UNION branches.
Note: COUNT(…) OVER () is a window function (valid alongside non-aggregated columns), but it changes semantics and still doesn’t address the UNION mismatch.
Try it: sqlvalidator.com

