SQL Functions and PL/pgSQL Functions
coreintermediateA SQL function's body is a single SQL statement (or a short sequence of them) — no branching, no loops, no local variables. A PL/pgSQL function's body is a full procedural block with IF/CASE, loops, variables and exception handling, written in PostgreSQL's own procedural language.
Think of it as
A SQL function is a named, parameterized SQL statement — the planner can often inline it directly into the calling query, the same way a view is folded in. A PL/pgSQL function is a small program: it has a BEGIN/END block, local variables, control flow and its own exception mechanism, and the planner treats it as an opaque call rather than something it can see inside of. Reach for a SQL function when the whole job is "run this query with these parameters"; reach for PL/pgSQL the moment the job needs a decision, a loop, or more than one statement whose result feeds the next.
What we're doing: Write the same "clamp a value between 0 and 100" logic as a SQL function (via CASE) and as a PL/pgSQL function (via IF), and compare.
- 1–4
- The SQL function's entire body is one SELECT — no BEGIN/END, no variables.
- 6–16
- The PL/pgSQL function needs a procedural block just to express the same three-way branch.
clamp_sql | clamp_plpgsql
-----------+---------------
100 | 100Why this works: Both produce the same result here, which is precisely the point: when the logic really is "one expression," a SQL function says that directly and stays eligible for planner inlining, while a PL/pgSQL function forces the planner to treat it as an opaque black box even though nothing procedural is actually happening. The choice should follow what the logic needs, not habit.
Defaulting to PL/pgSQL for logic that is really just one SQL expression
Wrong
Better
What you see: A query that calls full_name() in its WHERE clause or SELECT list performs worse than the equivalent inline expression would, and EXPLAIN shows it as an opaque function call rather than expanded into the plan.
Why: A single-statement SQL function is a candidate for inlining — PostgreSQL can fold it directly into the surrounding query the same way it folds in a simple view, letting the planner reason about it like ordinary SQL. A PL/pgSQL function of any complexity is always called as an opaque unit, even when its body is trivial, which forecloses that optimization for no procedural benefit.
- SQL function
- Body is one or more plain SQL statements
- No variables, no control flow
- Can be inlined by the planner into the caller
- PL/pgSQL function
- Body is a procedural block (DECLARE/BEGIN/END)
- IF, CASE, LOOP, variables, exceptions
- Always an opaque call to the planner
SQL function vs PL/pgSQL function
Remember: SQL function = a named SQL statement, inlineable by the planner. PL/pgSQL function = a small program with variables, control flow and exceptions, called as an opaque unit. Pick based on whether the logic needs a decision or a loop, not by default.
See also: parameters return types and control flow · procedures vs functions

