All articles
169 articles · updated weekly See our Tools
All articles
Tips

Unformatted SQL Is Technical Debt You Pay at Code Review

Poorly formatted SQL doesn't break anything — but it hides bugs, increases review time, and becomes a bad template. How to standardize without ceremony.

Unformatted SQL Is Technical Debt You Pay at Code Review
COVER · Tips

You open the PR and the query has 40 columns on a single line. You know what it does. The reviewer doesn't. You'll explain in the comments, the reviewer will approve without understanding, and six months from now nobody will remember why there's a LEFT JOIN there.

This post is about why poorly formatted SQL is real technical debt — and how to solve it without ceremony.


What unreadable SQL hides

Put these two queries side by side:

select u.id, u.name, u.email, o.total, o.created_at from users u left join orders o on u.id = o.user_id where u.active = true and o.total > 100 order by o.created_at desc limit 50
SELECT
  u.id,
  u.name,
  u.email,
  o.total,
  o.created_at
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE
  u.active = true
  AND o.total > 100
ORDER BY o.created_at DESC
LIMIT 50

The result is identical. The second takes three extra seconds to write and half an hour less to review.

The difference isn't aesthetic. In the first version, the LEFT JOIN disappears in the noise — and LEFT JOIN matters. It means the query also returns users without orders, with o.total as NULL. If the business expected only users with orders above $100, that's a bug. In the second version, the intent is immediately visible: you see the LEFT JOIN, pause, and ask "is that really what we want here?"

Formatting isn't about looking pretty. It's about making logic auditable.

The conventions that matter in practice

There's no universal SQL standard, but some choices consistently reduce friction:

Keywords in uppercase. SELECT, FROM, WHERE, JOIN — uppercase. Columns and tables in lowercase. This creates immediate visual contrast between structure and data, without needing syntax highlighting.

One column per line in SELECT. When the column list goes beyond three, one per line. This makes git diff easier, makes it easier to comment on a specific column in the PR, and makes it easier to add or remove without touching other lines.

WHERE conditions aligned. Each condition on its own line, with AND and OR at the beginning (not at the end). The reason is simple: commenting out a condition at the beginning of a line is more direct than hunting for the AND at the end of the previous line.

WHERE
  u.active = true
  AND o.total > 100
  -- AND o.status = 'paid'  ← easy to comment out during debug

Explicit JOINs, never comma in FROM. FROM users, orders WHERE users.id = orders.user_id is valid in every SQL dialect. It's also the fastest way to accidentally produce a cartesian product when you forget the join condition. Use explicit JOIN, always.

The problem with standardizing manually

The real obstacle isn't knowing the conventions — it's applying them consistently on a team where everyone has a different editor, a different plugin, and a different level of patience for manual formatting.

The solution is to automate. For queries that will go into code (migrations, raw ORM queries, scripts), the ideal is to have a formatter in the pipeline. For day-to-day use — pasting a query in a ticket, sharing in Slack, reviewing before running on a production database — I use the SQL Formatter directly in the browser. Paste the query, choose the dialect (PostgreSQL, MySQL, T-SQL), and it comes out formatted. Especially useful when you receive a query from someone else and need to understand it before anything else.

Formatting in migrations is different

An observation worth separating: SQL in migration files has additional rules beyond formatting.

Migrations are read by humans during review and by machines during execution — and are rarely reverted after being applied in production. This means each migration needs to be self-explanatory without external context. Inline comments are mandatory when the query has any non-obvious logic:

-- Remove duplicate rows keeping the most recent entry per user_id
-- Safe to run: no FK references this table
DELETE FROM user_sessions
WHERE id NOT IN (
  SELECT MAX(id)
  FROM user_sessions
  GROUP BY user_id
);

Clean formatting + intent comment = migration that anyone on the team can audit six months later without asking anything in Slack.

Readability is a team decision, not a personal one

Unreadable SQL is almost never the result of laziness — it's the result of context. The person wrote it directly in the console, tested it, copied and pasted. It works. Nobody will refactor what already works.

The problem is when this pattern replicates. One unformatted query becomes a template for the next. A year from now, the codebase has fifty queries that nobody can review in less than ten minutes each.

The change doesn't need to be radical: automatic formatter in pre-commit for .sql files, convention documented in the README, and the habit of formatting before sharing. The cost is low. The return shows up the first time someone catches a bug in a query during review instead of in production.

HT
Author
Hugo Tanaka
Second-generation Japanese-Brazilian who grew up watching his father debug logistics spreadsheets in BASIC. Full-stack developer focused on tooling and DX — the role that finally brought code, writing and an obsession with productivity into one place. Writes for people who already know how to code and need to solve a problem now.
View profile