Format SQL stored procedures by treating three layers separately: the CREATE PROCEDURE (or CREATE FUNCTION) header and parameter list, the BEGIN/END block that holds the body, and the dialect-specific control-flow syntax inside it — IF/THEN, LOOP, WHILE, TRY/CATCH. Query formatting advice covers SELECT statements in exhaustive detail; almost none of it survives contact with a 200-line procedure.
The reason is structural, not stylistic. PostgreSQL wraps a PL/pgSQL body in dollar-quoting ($$ ... $$), so the outer parser treats the whole thing as one opaque string. MySQL needs a client-side DELIMITER change before BEGIN/END will even parse. SQL Server's BEGIN/END is closer to plain T-SQL, but it still isn't part of the SELECT grammar most formatters were built around. A tool that formats queries beautifully can leave a procedure body completely untouched, or subtly break it.
This post covers what to fix by hand and how, dialect by dialect — parameter list alignment, BEGIN/END indentation, DECLARE sections, and IF/LOOP/WHILE blocks — with real examples in PostgreSQL's PL/pgSQL, SQL Server's T-SQL, and MySQL. If you haven't read our general SQL formatting best practices, start there for the query-level rules; this post picks up where that one stops, plus a checklist you can run before committing a procedure.
Run a PL/pgSQL procedure through a generic SQL formatter and the header gets tidied up, but everything between the $$ markers comes back exactly as it went in. That's not a bug — PostgreSQL's own parser treats a dollar-quoted string as a single token, so a formatter respecting that grammar has no statements to reformat inside it, only one long string it correctly leaves alone.
MySQL has a related but different problem. A stored procedure body needs DELIMITER $$ before it and DELIMITER ; after it, so the mysql client doesn't split the procedure on every internal semicolon. DELIMITER is a client command, not SQL, so a formatter that normalizes whitespace around unrecognized tokens can quietly turn "DELIMITER ;" into "DELIMITER;" — breaking the very command it's supposed to restore.
T-SQL avoids both problems. Everything inside a stored procedure's BEGIN/END is ordinary T-SQL, so a formatter can indent the SELECT, INSERT, and UPDATE statements inside it just fine. What it typically won't do is indent the BEGIN/END wrapper itself relative to the CREATE PROCEDURE header, or apply procedure-specific conventions like keeping SET NOCOUNT ON on its own line.