Opsdeck SQL Formatter & Linter
Format SQL and catch UPDATE/DELETE without WHERE, SELECT * in views and cross joins.
Try an example: Report query with joins, grouping and a date range · Lint: dangerous statements · Lower-case keywords, 4-space indent, a CTE and CASE
How it works
Formats SQL statements with clauses on their own lines, one select item per line, indented AND/OR conditions, subqueries and CREATE TABLE bodies, and keywords in the case you choose; comments are kept and formatting twice changes nothing. The linter flags UPDATE or DELETE without WHERE, SELECT * in views, comma joins without a condition, “= NULL”, NOT IN with a subquery, leading-wildcard LIKE, destructive statements and INSERT without a column list. It is the formatter of the Opsdeck Database desk.
- The SQL is split into tokens (keywords and names, quoted names, strings including E'…' and $tag$…$tag$, numbers, operators, comments) and into statements at semicolons outside parentheses and BEGIN … END blocks.
- Each statement is written again from its tokens: main clauses (SELECT, FROM, JOIN, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT, UNION, INSERT INTO, VALUES, UPDATE, SET, DELETE FROM, RETURNING) start a line; select items get one line each; AND/OR of WHERE, ON and HAVING are indented (the AND of BETWEEN stays); subqueries and CREATE TABLE bodies are indented; function calls and IN lists stay inline. Since the layout depends only on the tokens, formatting the output again returns it unchanged.
- The linter works on the same tokens per query level: missing WHERE in UPDATE/DELETE, * in the select list of a view, commas between tables in FROM (Cartesian product when there is no WHERE), = / <> NULL in conditions (but not in SET assignments), NOT IN (SELECT …), LIKE patterns that start with % or _, TRUNCATE/DROP/DROP COLUMN, and INSERT without a column list.
Sources
- PostgreSQL documentation: SQL Syntax (lexical structure) (The PostgreSQL Global Development Group)
- PostgreSQL wiki: Don’t Do This (NOT IN, = NULL, …) (PostgreSQL wiki)
Part of Opsdeck
-
Opsdeck Developer & ops workbench
One deck for CI failures, cron, traces, k8s events, API diffs, query plans and Terraform.
Related tools
- Opsdeck Schema Diff & Migration Generator (PostgreSQL, MySQL, SQLite) – Compare two DDL versions: ordered migration, rollback script and data-loss warnings.
- Query Plan Visualizer – PostgreSQL EXPLAIN (ANALYZE) or MySQL EXPLAIN JSON → plan tree, hot nodes and index suggestions.
- Data Lineage Explorer – Paste SQL or dbt models: table lineage graph, column lineage and upstream/downstream of any field.
How to use the Opsdeck SQL Formatter & Linter
Format SQL into a consistent layout and find statements that change every row, cross joins, SELECT * in views and NULL comparison mistakes.
What it is for
SQL arrives in every shape: one long line from a log, a query copied from an ORM, a migration written by three people. This tool rewrites it in one consistent layout, so it is easy to read and diffs stay small, and it checks the statements for patterns that cause real incidents, such as an UPDATE without WHERE.
It is a formatter and a pattern linter, not a database. It does not know your tables, so it cannot tell whether a column exists or a query is slow; for plans use the Query Plan Visualizer.
Worked example
The second example pastes five short statements in lower case: an update accounts set balance = 0, a delete from sessions, a select * from orders, customers, a view defined with select * and a query with closed_at = null, not in (select …) and like '%crash'.
The tool answers 5 statements formatted; the linter found 3 errors, 2 warnings and 3 notes.
The errors are the UPDATE and the DELETE without WHERE, which change every row, and the comma join with no condition: "the result is every combination of rows (a Cartesian product)". The warnings are SELECT * in the view, whose column list freezes at creation time in PostgreSQL and MySQL, and closed_at = NULL, which is never true. The notes are the plain SELECT *, the NOT IN subquery, which returns no rows at all if the subquery returns a NULL, and the leading-wildcard LIKE, which cannot use an ordinary index.
In the formatted output each clause starts its own line (SELECT, FROM, WHERE), select items get one line each, the second and third conditions start with an indented AND, and the subquery is indented inside its parentheses.
How to read the result
- Formatted SQL: copy it back into your editor. Names, string literals and comments are never changed, only whitespace and keyword case.
- Lint findings: level, rule name, statement number, line and an explanation. Errors are always worth fixing; warnings almost always; notes depend on context.
- The summary counts the findings per level; errors are repeated as warnings at the top of the result.
Tips and common mistakes
- Formatting is idempotent: running the output through the tool again changes nothing, so it is safe in a pre-commit step.
- Choose lower-case keywords if your team writes them that way; the layout stays the same.
UPDATE t SET x = NULLis an assignment and is not flagged; only comparisons inWHERE,ON,HAVINGandCASE WHENare.- If a full-table update is really intended, write
WHERE trueso readers see that it is deliberate. - The
ANDinsideBETWEEN … AND …stays on its line; only logicalAND/ORstart new lines.
FAQ
Which dialects does it format?
The tokenizer understands PostgreSQL, MySQL and SQLite syntax, including E'…' and $$…$$ strings, backtick and bracket names and :: casts. Vendor-specific procedural code is formatted only roughly.
Does it reorder or rewrite my query?
No. It changes whitespace and keyword case only; the order of tokens stays exactly as you wrote it.
Why is SELECT * only a note outside views?
In ad-hoc queries it is often fine. In application code and especially in views it hides which columns you depend on, so the view rule is stricter.
Can it check many files at once?
Paste several statements separated by semicolons; each one is formatted and linted, and findings name the statement number and line.
Guide last updated:
For scripts and AI agents
- JSON API:
POST https://opsdecksqlformatter.usefulweb.app/api/v1/calculate(same calculation as this page) - Interface description: OpenAPI 3.1 · AI guide: llms.txt
- MCP server (Streamable HTTP):
https://opsdecksqlformatter.usefulweb.app/mcp· tool nameopsdeck_sql_formatter
Example request
curl -s -X POST https://opsdecksqlformatter.usefulweb.app/api/v1/calculate \
-H 'content-type: application/json' \
-d '{}'