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)

Next-gen SQL validator on SQLAI.ai
How a better SQL validator could look.

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:

ShortcomingResult
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
employees
WHERE
salary > 50000
ORDER BY full_name
UNION
SELECT
contact_name,
'Customer' AS contact_type,
last_order_date
FROM
'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):

SQL query with syntax errors
SELECT
employee_id,
fist_name || ' ' || last_name AS full_name,
'Employee' AS contact_type,
hire_date
COUNT(reports_to)
FROM
employees
WHERE
salary > 50000
ORDER BY full_name
UNION
SELECT
contact_name,
'Customer' AS contact_type,
last_order_date
FROM
'customers'
WHERE
total_revenue > 10000 AND,
status = "Active"
LIMIT '25';
#Explanation
1Missing comma after hire_date: I added a comma after hire_date so the next select item is properly separated.
2Mixing 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.
3ORDER BY before UNION: ORDER BY cannot appear before a UNION. I moved the ORDER BY to after the final SELECT (after the UNION).
4Column 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).
5Quoted table name: ‘customers’ used single quotes (string) instead of an identifier. I removed the quotes and used the customers identifier.
6Stray comma in WHERE: Removed the stray comma after AND in the second WHERE clause.
7Wrong string quoting: “Active” used double quotes (identifier in SQL) instead of a string literal. I replaced it with ‘Active’.
8Quoted LIMIT value: LIMIT ‘25’ used a quoted string; I changed it to the numeric literal 25.

Valid SQL query

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_count
FROM
employees e
WHERE
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_count
FROM
customers
WHERE
total_revenue > 10000 AND
status = 'Active'
ORDER BY
full_name
LIMIT 25;

Click “view diff” to compare the invalid and corrected queries:

View diff of SQL syntax 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.

SQL validator runsql.com

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_count
FROM
employees e1
WHERE
salary > 50000
ORDER BY
full_name
UNION
SELECT
NULL,
contact_name,
'Customer' AS contact_type,
last_order_date
FROM
customers
WHERE
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_count
FROM
employees
WHERE
salary > 50000
ORDER BY full_name
UNION
SELECT
contact_name,
'Customer' AS contact_type,
last_order_date
FROM
customers
WHERE
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