DEV Community

Rasika Dangamuwa
Rasika Dangamuwa

Posted on

Why Naive SQL Formatters Break Queries (and How Parser-Based Formatting Works)

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';
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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::date uses 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 keeping WITH TIES attached to TOP rather than treating WITH as 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';
Enter fullscreen mode Exit fullscreen mode

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)