Format, minify, validate, analyze, and translate SQL queries across dialects.
Why formatting exposes logic bugs, where the five major engines diverge, and what a prepared statement actually protects.
The predicate WHERE status = 'active' OR role = 'admin' AND deleted_at IS NULL does not filter what it appears to: AND binds tighter than OR, so the engine reads it as status = 'active' OR (role = 'admin' AND deleted_at IS NULL). Putting each predicate on its own line with explicit parentheses turns that bug into something a reviewer can see. The same trap appears when a filter on the right table of a LEFT JOIN moves from ON into WHERE: the NULL-extended rows fail the comparison and the LEFT JOIN silently degrades into an INNER JOIN. And because SQL uses three-valued logic, column <> NULL is never true — you must write IS NOT NULL.
ISO/IEC 9075 defines the core language, but every engine layers its own syntax on top. To quote identifiers containing spaces or reserved words, PostgreSQL and Oracle use double quotes, MySQL uses backticks and SQL Server uses square brackets. Pagination is LIMIT 10 OFFSET 20 on PostgreSQL, MySQL and SQLite; OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY on SQL Server and Oracle 12c and later; and TOP 10, with no offset, on older SQL Server releases. Concatenation is || on PostgreSQL, Oracle and SQLite, while MySQL requires CONCAT() because there || means logical OR unless PIPES_AS_CONCAT is enabled. PostgreSQL also folds unquoted identifiers to lowercase, Oracle to uppercase.
A prepared statement sends the template first, the engine compiles a plan, and the values travel separately afterwards, so a parameter can never turn into executable syntax. Placeholders differ per engine: $1 and $2 on PostgreSQL, ? on MySQL and SQLite, @name on SQL Server and :name on Oracle. What parameters cannot cover are identifiers: you cannot bind a table name, a column name or an ORDER BY direction, so an ORDER BY assembled by string concatenation remains injectable and must be resolved through an allowlist. They also give you nothing if the value ends up concatenated inside an EXECUTE or sp_executesql call.
The usual cause is wrapping the column in a function: WHERE DATE(created_at) = '2026-01-01' or WHERE UPPER(email) = ... cannot use an ordinary B-tree index, because the index stores the column value, not the value of the expression. Rewrite the predicate as a range over the bare column, or create an expression index. Comparing mismatched types, which forces an implicit cast, and using a composite index on (a, b, c) without filtering on its leftmost prefix break index usage the same way. EXPLAIN ANALYZE shows which path the planner chose.
The parser discards whitespace, so execution time does not change. One case does matter: SQL Server and Oracle key their plan cache entries on a hash of the literal statement text, so the same query with different indentation occupies separate cache entries. Minifying also helps when a query has to fit on a single line inside a configuration file, a structured log line or a JSON field.
For readability, when the same intermediate result is referenced several times, or when you need recursion via WITH RECURSIVE, the only way to walk hierarchies. Mind the performance details: through PostgreSQL 11 every CTE was materialized and acted as an optimization fence; from version 12 non-recursive CTEs referenced once are inlined into the outer query unless you write AS MATERIALIZED. MySQL only supports CTEs from version 8.0 onwards.