Common syntax errors with explanations and corrections.
Structural Errors
Missing FROM Clause
Error:
SELECT id, name WHERE status = 'active';Fixed:
SELECT id, name FROM users WHERE status = 'active';Explanation: SELECT requires a FROM clause to specify the source table.
Wrong Clause Order
Error:
SELECT * FROM users ORDER BY name WHERE status = 'active';Fixed:
SELECT * FROM users WHERE status = 'active' ORDER BY name;Explanation: WHERE must come before ORDER BY. Correct order: FROM -> WHERE -> GROUP BY -> HAVING -> ORDER BY -> LIMIT.
Missing Semicolon (Multi-Statement)
Error:
SELECT * FROM users
SELECT * FROM ordersFixed:
SELECT * FROM users;
SELECT * FROM orders;Explanation: When running multiple statements, each must be terminated with a semicolon.
Quote and Literal Errors
Wrong String Quotes
Error (Standard SQL):
SELECT * FROM users WHERE name = "John";Fixed:
SELECT * FROM users WHERE name = 'John';Explanation: Standard SQL uses single quotes for string literals. Double quotes are for identifiers (or backticks in MySQL).
Unescaped Quote in String
Error:
SELECT * FROM posts WHERE title = 'It's a test';Fixed:
SELECT * FROM posts WHERE title = 'It''s a test';Or with escape (PostgreSQL, MySQL):
SELECT * FROM posts WHERE title = 'It\'s a test';Explanation: Single quotes inside strings must be escaped by doubling them.
Missing Quotes on String
Error:
SELECT * FROM users WHERE status = active;Fixed:
SELECT * FROM users WHERE status = 'active';Explanation: String values must be enclosed in quotes; otherwise they're interpreted as column names.
Column and Reference Errors
Misspelled Column Name
Error:
SELECT id, nmae, email FROM users;Fixed:
SELECT id, name, email FROM users;Explanation: Column names must match exactly (case-sensitivity depends on dialect and configuration).
Missing Comma in Column List
Error:
SELECT
id
name
email
FROM users;Fixed:
SELECT
id,
name,
email
FROM users;Explanation: Columns in SELECT must be separated by commas.
Extra Comma at End
Error:
SELECT id, name, email, FROM users;Fixed:
SELECT id, name, email FROM users;Explanation: Trailing comma before FROM is not allowed.
Ambiguous Column in JOIN
Error:
SELECT id, name, order_date
FROM users u
JOIN orders o ON u.id = o.user_id;Fixed:
SELECT u.id, u.name, o.order_date
FROM users u
JOIN orders o ON u.id = o.user_id;Explanation: When joining tables, columns that exist in multiple tables must be qualified with table alias.
JOIN Errors
Missing ON Condition
Error:
SELECT * FROM users u JOIN orders o;Fixed:
SELECT * FROM users u JOIN orders o ON u.id = o.user_id;Explanation: INNER/LEFT/RIGHT JOIN requires an ON clause specifying the join condition.
CROSS JOIN with ON
Error:
SELECT * FROM users CROSS JOIN roles ON users.role_id = roles.id;Fixed (if you want filtered join):
SELECT * FROM users JOIN roles ON users.role_id = roles.id;Fixed (if you want cartesian product):
SELECT * FROM users CROSS JOIN roles;Explanation: CROSS JOIN produces cartesian product and doesn't use ON clause.
Using WHERE Instead of ON
Technically valid but poor practice:
SELECT * FROM users u, orders o WHERE u.id = o.user_id;Better:
SELECT * FROM users u JOIN orders o ON u.id = o.user_id;Explanation: Explicit JOIN syntax is clearer and prevents accidental cartesian products.
GROUP BY Errors
Non-Aggregated Column Not in GROUP BY
Error:
SELECT department, employee_name, COUNT(*)
FROM employees
GROUP BY department;Fixed (aggregate the column):
SELECT department, STRING_AGG(employee_name, ', '), COUNT(*)
FROM employees
GROUP BY department;Or (add to GROUP BY):
SELECT department, employee_name, COUNT(*)
FROM employees
GROUP BY department, employee_name;Explanation: All columns in SELECT must either be in GROUP BY or be aggregated.
HAVING Without GROUP BY
Warning (valid but suspicious):
SELECT * FROM orders HAVING total > 100;Probably meant:
SELECT * FROM orders WHERE total > 100;Or:
SELECT customer_id, SUM(total)
FROM orders
GROUP BY customer_id
HAVING SUM(total) > 100;Explanation: HAVING is for filtering groups; use WHERE for row-level filtering.
Using Column Alias in HAVING
Error (most dialects):
SELECT department, COUNT(*) AS emp_count
FROM employees
GROUP BY department
HAVING emp_count > 5;Fixed:
SELECT department, COUNT(*) AS emp_count
FROM employees
GROUP BY department
HAVING COUNT(*) > 5;Explanation: Column aliases defined in SELECT are not available in HAVING (except MySQL).
Subquery Errors
Subquery Returns Multiple Rows with = Operator
Error:
SELECT * FROM products
WHERE category_id = (SELECT id FROM categories WHERE active = true);Fixed (if expecting multiple):
SELECT * FROM products
WHERE category_id IN (SELECT id FROM categories WHERE active = true);Fixed (if expecting one):
SELECT * FROM products
WHERE category_id = (SELECT id FROM categories WHERE active = true LIMIT 1);Explanation: When using = with subquery, the subquery must return exactly one value.
Subquery Column Not Accessible
Error:
SELECT * FROM users
WHERE id IN (SELECT user_id, amount FROM orders WHERE amount > 100);Fixed:
SELECT * FROM users
WHERE id IN (SELECT user_id FROM orders WHERE amount > 100);Explanation: Subquery used with IN must return exactly one column.
Correlated Subquery Reference Error
Error:
SELECT * FROM orders o
WHERE total > (SELECT AVG(total) FROM orders WHERE customer_id = c.id);Fixed:
SELECT * FROM orders o
WHERE total > (SELECT AVG(total) FROM orders WHERE customer_id = o.customer_id);Explanation: Correlated subqueries must reference columns from the outer query using correct alias.
NULL Handling Errors
Using = NULL Instead of IS NULL
Error:
SELECT * FROM users WHERE deleted_at = NULL;Fixed:
SELECT * FROM users WHERE deleted_at IS NULL;Explanation: NULL comparisons must use IS NULL or IS NOT NULL; = NULL always returns unknown.
NOT IN with NULL Values
Problematic:
SELECT * FROM products
WHERE id NOT IN (SELECT product_id FROM discontinued);If discontinued.product_id contains NULL, this returns no rows.
Fixed:
SELECT * FROM products
WHERE id NOT IN (SELECT product_id FROM discontinued WHERE product_id IS NOT NULL);Or better:
SELECT * FROM products p
WHERE NOT EXISTS (SELECT 1 FROM discontinued d WHERE d.product_id = p.id);Type Mismatch Errors
Comparing String to Number
Error:
SELECT * FROM users WHERE id = 'abc';Fixed (if id is numeric):
SELECT * FROM users WHERE id = 123;Fixed (if looking for string):
SELECT * FROM users WHERE username = 'abc';Date Format Issues
Error (ambiguous):
SELECT * FROM orders WHERE order_date = '01/02/2024';Fixed (use ISO format):
SELECT * FROM orders WHERE order_date = '2024-01-02';Explanation: ISO 8601 format (YYYY-MM-DD) is unambiguous and works across all dialects.
Boolean Comparison
Error (Oracle has no boolean):
SELECT * FROM users WHERE is_active = TRUE;Fixed (Oracle):
SELECT * FROM users WHERE is_active = 1;Fixed (PostgreSQL, most others):
SELECT * FROM users WHERE is_active = true;Or simply:
SELECT * FROM users WHERE is_active;Pagination Errors
OFFSET Without LIMIT
Error (PostgreSQL):
SELECT * FROM products OFFSET 10;Fixed:
SELECT * FROM products LIMIT 100 OFFSET 10;MySQL LIMIT Syntax Mix-up
Error:
SELECT * FROM products LIMIT 10, 20 OFFSET 0;Fixed (choose one syntax):
SELECT * FROM products LIMIT 20 OFFSET 10;Or:
SELECT * FROM products LIMIT 10, 20;Explanation: MySQL's LIMIT offset, count and LIMIT count OFFSET offset shouldn't be mixed.
CTE Errors
Missing CTE Name
Error:
WITH AS (
SELECT * FROM users WHERE status = 'active'
)
SELECT * FROM active_users;Fixed:
WITH active_users AS (
SELECT * FROM users WHERE status = 'active'
)
SELECT * FROM active_users;CTE Not Used
Warning:
WITH active_users AS (
SELECT * FROM users WHERE status = 'active'
)
SELECT * FROM users;Fixed:
WITH active_users AS (
SELECT * FROM users WHERE status = 'active'
)
SELECT * FROM active_users;Recursive CTE Missing UNION
Error:
WITH RECURSIVE org_tree AS (
SELECT id, name, manager_id FROM employees WHERE manager_id IS NULL
)
SELECT * FROM org_tree;Fixed:
WITH RECURSIVE org_tree AS (
SELECT id, name, manager_id, 1 AS level
FROM employees
WHERE manager_id IS NULL
UNION ALL
SELECT e.id, e.name, e.manager_id, ot.level + 1
FROM employees e
JOIN org_tree ot ON e.manager_id = ot.id
)
SELECT * FROM org_tree;INSERT/UPDATE/DELETE Errors
INSERT Column Count Mismatch
Error:
INSERT INTO users (id, name, email)
VALUES (1, 'John');Fixed:
INSERT INTO users (id, name, email)
VALUES (1, 'John', 'john@example.com');UPDATE Without WHERE
Warning (affects all rows):
UPDATE users SET status = 'inactive';Probably meant:
UPDATE users SET status = 'inactive' WHERE last_login < '2023-01-01';DELETE Without WHERE
Warning (deletes all rows):
DELETE FROM users;Probably meant:
DELETE FROM users WHERE status = 'deleted';Or:
TRUNCATE TABLE users;SET Using Wrong Operator
Error:
UPDATE users SET status == 'active' WHERE id = 1;Fixed:
UPDATE users SET status = 'active' WHERE id = 1;Explanation: Assignment in SET uses single equals =, not comparison ==.
Window Function Errors
Window Function in WHERE
Error:
SELECT * FROM employees
WHERE ROW_NUMBER() OVER (ORDER BY salary DESC) <= 10;Fixed:
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn
FROM employees
) ranked
WHERE rn <= 10;Explanation: Window functions cannot be used in WHERE; use a subquery or CTE.
Missing OVER Clause
Error:
SELECT id, name, ROW_NUMBER() FROM employees;Fixed:
SELECT id, name, ROW_NUMBER() OVER (ORDER BY id) FROM employees;Explanation: Window functions require an OVER clause.
Invalid Frame Specification
Error:
SELECT id, SUM(amount) OVER (ORDER BY date ROWS UNBOUNDED)
FROM transactions;Fixed:
SELECT id, SUM(amount) OVER (ORDER BY date ROWS UNBOUNDED PRECEDING)
FROM transactions;Explanation: Frame specification needs PRECEDING or FOLLOWING keyword.
sql-analytics
Analytical and aggregation SQL — GROUP BY, ROLLUP, CUBE, GROUPING SETS, pivots, cohorts, funnels, retention, percentiles and statistical functions.
sql-data-quality
Profile and validate data with SQL — NULL analysis, duplicate detection, referential integrity, range and format checks, outliers, constraints and cleansing queries.
sql-debugger
Debug failing SQL or queries returning wrong results — interpret error messages, find causes of duplicates, missing rows, NULL surprises, fan-out joins and type mismatches, and produce a fixed query.
sql-dialect-translator
Translate SQL between dialects — PostgreSQL, MySQL, SQL Server (T-SQL), Oracle, SQLite — covering data types, functions, pagination, date handling, upserts and identity columns.
sql-optimizer
Rewrite slow SQL queries for performance — read execution plans (EXPLAIN / EXPLAIN ANALYZE), remove anti-patterns, make predicates sargable, choose JOIN/EXISTS strategies and suggest indexes.
sql-performance-tuning
Database-level SQL performance tuning — index design and maintenance, query profiling, statistics, partitioning, schema design, locking and configuration bottlenecks.