50 Functions and PL/pgSQL
PostgreSQL lets you define your own functions. The simplest are SQL functions — a function body that is one or more SQL statements; PostgreSQL can often inline them. The more powerful are PL/pgSQL functions — written in a procedural language built into the server, with variables, loops, conditions, and exceptions. PL/pgSQL is what you use for triggers, complex business logic, and one-off maintenance routines. There are also PL/Python, PL/Perl, and others, but PL/pgSQL is always available and is what you should learn first.
What you’ll learn
- The
CREATE FUNCTIONsyntax - The difference between
LANGUAGE sqlandLANGUAGE plpgsql - Volatility categories:
IMMUTABLE,STABLE,VOLATILE - Variables,
IF,LOOP, andRAISE NOTICEin PL/pgSQL SECURITY DEFINERand thesearch_pathwarning
Example
A function declaration includes a name, argument list, return type, language, optional volatility, and body. The body is bracketed by dollar quotes ($$ ... $$) — these avoid the need to escape quotes inside the body. You can use $tag$ ... $tag$ if you ever need nested dollar quotes.
The volatility categories are critical for performance:
IMMUTABLE: same inputs always produce the same output, no database state involved. The planner can constant-fold these into expressions and even pre-evaluate them at planning time. Use for math, string formatting, deterministic transforms.STABLE: same inputs produce the same output within a single statement, but may differ across statements (e.g. functions that read tables). This allows optimizations such as use in index scan conditions; it does not promise one evaluation per statement.VOLATILE(default): may return different results even within one statement (e.g.random(),clock_timestamp(), anything that modifies data);now()isSTABLE. The planner re-evaluates every time.
Wrong volatility leads to wrong query plans — most beginners leave everything VOLATILE and pay the cost. Mark functions as IMMUTABLE / STABLE whenever true.
Run the example
To run:
Common pitfalls
- Defining a
SECURITY DEFINERfunction (runs as the function owner) without settingsearch_pathis a known privilege-escalation pattern. Use only trusted schemas, explicitly putpg_templast, and qualify sensitive names; for example,SET search_path = pg_catalog, app, pg_tempifappis trusted. - Marking a function
IMMUTABLEwhen it actually reads tables — the planner caches a wrong value. ReserveIMMUTABLEfor true math/string functions. - Overloading by argument types is allowed but easy to abuse.
recent_orders(numeric)andrecent_orders(integer)are different functions; small calls may unexpectedly target the wrong one. - PL/pgSQL uses semicolon-terminated statements. Keywords and unquoted identifiers are case-insensitive; quoted identifiers and string contents are case-sensitive. Be careful with reserved words inside the function body.
Stored procedures (CREATE PROCEDURE, PostgreSQL 11+) are like functions but can manage transactions (COMMIT, ROLLBACK inside the body). Use functions for things that return values; procedures for batch operations that need transaction control.
Write an IMMUTABLE SQL function discount(price, pct) that returns the discounted price. Apply it in a query: SELECT id, total, discount(total, 10) FROM orders_example;. Then write a PL/pgSQL function that returns the largest order amount per customer using a loop or a single SQL query (the SQL form is much faster — try both to see).
Downloads
SQL file: functions-and-plpgsql.sql · All lesson files · Setup guide