What does an SQL validator check?
An SQL validator checks query syntax and structural correctness, including clause order, alias scope, JOIN structure, and aggregate rules. It helps catch errors before execution.
This page focuses on SQL validation workflows for PostgreSQL queries: syntax checks, clause ordering, alias scope, JOIN correctness, aggregate rules, and window-function usage.
Use these examples to identify and fix invalid SQL quickly before handing queries off to your formatter, optimizer, or execution pipeline.
Review common SQL validation failures and corrected PostgreSQL queries. Each example shows the invalid input, precise issue list, and a fixed query.
Invalid SQL input
SELECT
user_id,
email
users
WHERE
is_active = true; Validator output
SELECT
user_id,
email
-- 1. Missing FROM before the source table:
users
WHERE
is_active = true;
| # | Explanation |
|---|---|
| 1 | The SELECT statement is missing FROM users. PostgreSQL requires FROM to define the row source before WHERE filters are applied. |
SELECT
user_id,
email
FROM users
WHERE
is_active = true;Invalid SQL input
SELECT
product_name,
price,
category,
FROM
products; Validator output
SELECT
product_name,
price,
-- 1. Trailing comma before FROM:
category,
FROM
products;
| # | Explanation |
|---|---|
| 1 | PostgreSQL does not allow a trailing comma after the last projected column. Remove the comma after category before FROM. |
SELECT
product_name,
price,
category
FROM
products;Invalid SQL input
SELECT
order_id,
(quantity * unit_price) AS total_value
FROM
order_details
WHERE
total_value > 1000; Validator output
SELECT
order_id,
(quantity * unit_price) AS total_value
FROM
order_details
WHERE
-- 1. SELECT alias is not visible in WHERE:
total_value > 1000;
| # | Explanation |
|---|---|
| 1 | WHERE runs before SELECT aliases are created, so total_value is undefined in this clause. Repeat the expression or use a subquery/CTE. |
SELECT
order_id,
(quantity * unit_price) AS total_value
FROM
order_details
WHERE
(quantity * unit_price) > 1000;Invalid SQL input
SELECT
c.customer_name,
o.order_date
FROM
customers c
JOIN
ON c.id = o.customer_id; Validator output
SELECT
c.customer_name,
-- 1. Alias "o" is referenced but never defined:
o.order_date
FROM
customers c
-- 2. JOIN target table is missing:
JOIN
-- 3. ON cannot appear without a valid JOIN target:
ON c.id = o.customer_id;
| # | Explanation |
|---|---|
| 1 | o.order_date references alias o, but no table was declared with alias o. |
| 2 | Every JOIN must specify a table or subquery to join. |
| 3 | The ON predicate is only valid after a complete JOIN target declaration, such as JOIN orders o. |
SELECT
c.customer_name,
o.order_date
FROM
customers c
JOIN
orders o
ON c.id = o.customer_id;Invalid SQL input
SELECT
department,
AVG(salary)
FROM
employees
HAVING
department = 'Sales'; Validator output
SELECT
-- 1. Non-aggregated department is selected with AVG without grouping:
department,
AVG(salary)
FROM
employees
HAVING
-- 2. HAVING is used to filter detail rows instead of grouped results:
department = 'Sales';
| # | Explanation |
|---|---|
| 1 | When aggregate functions are used, non-aggregated columns in SELECT must appear in GROUP BY. |
| 2 | Use WHERE for row-level filtering (pre-aggregation) and HAVING for aggregate/group-level filtering (post-aggregation). |
SELECT
department,
AVG(salary) AS avg_salary
FROM
employees
WHERE
department = 'Sales'
GROUP BY
department;Invalid SQL input
SELECT
employee_name,
salary,
department,
SUM(salary) OVER (PARTITION BY department WHERE is_manager = false ORDER BY hire_date) AS running_total
FROM
employees; Validator output
SELECT
employee_name,
salary,
department,
-- 1. OVER(...) does not allow WHERE:
SUM(salary) OVER (PARTITION BY department WHERE is_manager = false ORDER BY hire_date) AS running_total
FROM
employees;
| # | Explanation |
|---|---|
| 1 | Window definitions only allow PARTITION BY, ORDER BY, and frame clauses. Put conditional logic in FILTER (WHERE ...) or a CASE expression. |
SELECT
employee_name,
salary,
department,
SUM(salary) FILTER (WHERE NOT is_manager) OVER (
PARTITION BY department
ORDER BY hire_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_total
FROM
employees;Invalid SQL input
INSERT INTO users (
username,
email,
is_active
)
VALUES (
'alice',
'alice@example.com'
); Validator output
INSERT INTO users (
username,
email,
is_active
)
VALUES (
-- 1. Only two values for three target columns:
'alice',
'alice@example.com'
);
| # | Explanation |
|---|---|
| 1 | The INSERT target list has three columns, but VALUES provides only two entries. PostgreSQL raises a column/value count mismatch error. |
INSERT INTO users (
username,
email,
is_active
)
VALUES (
'alice',
'alice@example.com',
true
);Invalid SQL input
WITH monthly_revenue AS (
SELECT
date_trunc('month', order_date)::date AS sales_month,
SUM(total_amount) AS revenue
FROM orders
GROUP BY 1
)
SELECT
sales_month,
revenue,
region_name
FROM
monthly_revenue
WHERE
region_name = 'North America'; Validator output
WITH monthly_revenue AS (
SELECT
date_trunc('month', order_date)::date AS sales_month,
SUM(total_amount) AS revenue
FROM orders
GROUP BY 1
)
SELECT
sales_month,
revenue,
-- 1. region_name is not produced by monthly_revenue:
region_name
FROM
monthly_revenue
WHERE
-- 2. region_name filter references an undefined column:
region_name = 'North America';
| # | Explanation |
|---|---|
| 1 | The CTE only returns sales_month and revenue; region_name is not part of its schema. |
| 2 | Filters can only reference columns available from the current FROM scope. Add a join to a source that contains region_name. |
WITH monthly_revenue AS (
SELECT
date_trunc('month', o.order_date)::date AS sales_month,
o.region_name,
SUM(o.total_amount) AS revenue
FROM orders o
GROUP BY
date_trunc('month', o.order_date)::date,
o.region_name
)
SELECT
sales_month,
revenue,
region_name
FROM
monthly_revenue
WHERE
region_name = 'North America';Use this checklist before running SQL in staging or production to reduce preventable syntax and structure errors.
After validation, continue with the SQL formatter, SQL optimizer, and SQL explainer to improve readability, performance, and documentation quality.
An SQL validator checks query syntax and structural correctness, including clause order, alias scope, JOIN structure, and aggregate rules. It helps catch errors before execution.
Validation catches many issues early, but not all runtime problems. You should still test against real schemas, permissions, and data distributions to validate behavior end-to-end.
Yes. Validate first so downstream formatting and optimization steps work on correct SQL. Then format for readability and optimize based on execution plans.
Get started for free
Generate, validate, optimize, and explain queries in seconds. Bring your own schema, keep full control, and start free with no credit card required.