Writing raw SQL queries in application code or migration scripts often starts clean, but as schemas grow, queries accumulate multiple JOIN clauses, Common Table Expressions (CTEs), window functions, and nested subqueries. A 5-line SELECT quickly turns into a 60-line block of unformatted text.
When developers try to clean up these queries using quick regex replacements or basic online formatters, subtle bugs often creep into the database scripts. SQL formatting is deceptively complex because SQL is not a context-free language; keywords can appear inside string literals, identifier names, or JSON path expressions.
Here is a look at why naive SQL formatting breaks and how to handle SQL code structure safely.
1. The String Literal Keyword Trap
The most common failure in regex-driven formatters is blindly capitalizing keywords without tracking lexical context.
Consider this query updating audit logs:
UPDATE audit_logs
SET notes = 'user selected select all option from menu'
WHERE status = 'pending' AND updated_at < NOW() - INTERVAL '7 days';
A regex-based formatter searching for \b(select|from|where|set|update)\b with case-insensitivity might transform the string literal 'user selected select all option from menu' into 'user selected SELECT ALL option FROM menu'.
While this specific example doesn't throw a syntax error, it mutates the application's stored string data in production. If the string literal contained SQL control characters or escaped quotes, regex replacements can easily produce invalid syntax.
2. Window Functions and Complex Over Clauses
Modern analytical queries rely heavily on window functions like ROW_NUMBER(), SUM() OVER (), and DENSE_RANK(). When unformatted, these expressions quickly obscure business logic:
SELECT employee_id, department_id, salary, AVG(salary) OVER(PARTITION BY department_id ORDER BY hire_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS rolling_avg FROM salaries WHERE status = 'active';
Proper formatting requires recognizing the OVER clause boundaries and aligning PARTITION BY, ORDER BY, and frame specifications:
SELECT
employee_id,
department_id,
salary,
AVG(salary) OVER (
PARTITION BY department_id
ORDER BY hire_date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS rolling_avg
FROM salaries
WHERE status = 'active';
Proper AST (Abstract Syntax Tree) parsing isolates function signatures from window specifications, preserving correct clause nesting regardless of length.
3. Dialect Quirks: PostgreSQL :: vs T-SQL CONVERT
SQL is not a single language—it is a family of dialects. A formatter built strictly for Standard ANSI SQL will misinterpret or mangle dialect-specific operators:
-
PostgreSQL type casting:
created_at::dateuses double colons. Naive tools often insert spaces around colons (created_at :: date), which breaks syntax in strict Postgres parsers. -
T-SQL top clauses:
SELECT TOP (10) WITH TIES ...requires keepingWITH TIESattached toTOPrather than treatingWITHas a CTE start. - MySQL backticks vs ANSI double quotes: Preserving identifier escaping rules across dialects prevents unexpected parse failures.
When building client-side formatting pipelines, running tokens through an AST parser ensures that syntax rules for specific dialects are honored. If you need a fast, zero-install tool that formats SQL directly in your browser without sending queries to a server, Nutilz SQL Formatter handles dialect-specific formatting and multi-clause indentation entirely client-side.
4. Common Table Expressions (CTEs) and Multi-Table Join Alignment
CTEs (WITH ... AS (...)) improve query readability only if their internal queries are scoped visually. Standardizing on 2-space or 4-space indentation for CTE blocks prevents deeply nested subqueries from drifting off the right edge of the screen:
WITH regional_sales AS (
SELECT region, SUM(amount) AS total_sales
FROM orders
GROUP BY region
), top_regions AS (
SELECT region
FROM regional_sales
WHERE total_sales > 100000
)
SELECT r.region, o.order_id, o.amount
FROM top_regions r
JOIN orders o ON r.region = o.region
WHERE o.order_date >= '2026-01-01';
Conclusion
Formatting SQL is not just about making code look clean—it is about verifying query logic, making git diffs readable, and preventing syntax errors during code reviews.
Whether you rely on CLI tools like sqlfluff, IDE plugins, or lightweight browser utilities like Nutilz SQL Formatter, ensure your formatting pipeline respects lexical contexts, string literals, and dialect-specific operators.
Top comments (0)