PL/pgSQL RETURN
Master PL/pgSQL RETURN statement — scalar returns from CREATE FUNCTION, the required LANGUAGE plpgsql clause, intermediate variables, recursive functions like factorial with proper BIGINT, multiple return paths in IF/ELSIF chains, and composite returns via %ROWTYPE. Anonymous DO blocks have no RETURN — that's a function-only construct.
The RETURN statement ends a function and sends a value back to the caller. The exact form depends on what the function returns:
- Scalar return —
RETURNS integer,RETURNS text, etc. UseRETURN expression. - Composite return —
RETURNS table_nameorRETURNS some_record_type. UseRETURN row_variableorRETURN ROW(...). - Set-returning —
RETURNS SETOF typeorRETURNS TABLE(...). UseRETURN NEXT/RETURN QUERY(covered in later modules).
This page covers scalar and composite returns. Set-returning patterns belong in the Functions module that comes later in this series.
DO $$ ... $$; block isn't a function — it has no caller waiting for a value, so RETURN doesn't apply. Anonymous blocks just execute and finish. RETURN belongs to functions, defined with CREATE FUNCTION.Inside a function with a scalar return type:
The RETURN statement immediately ends the function and sends expression back to the caller. Anything after a RETURN that's executed is unreachable code (Postgres won't catch this — it's on you).
LANGUAGE plpgsql is required. Postgres supports several procedural languages (PL/pgSQL, PL/Python, PL/Perl, ...). Without LANGUAGE plpgsql at the end, Postgres assumes LANGUAGE sql and tries to parse the body as plain SQL — which doesn't allow DECLARE/BEGIN/RETURN. You'll get a confusing parse error. The original tutorial omitted this line in one example.The simplest case: a function that adds two numbers and returns the result:
CREATE OR REPLACE FUNCTION add_numbers(a INTEGER, b INTEGER)
RETURNS INTEGER AS $$
BEGIN
RETURN a + b;
END;
$$ LANGUAGE plpgsql;
SELECT add_numbers(10, 20) AS result;
A function with intermediate variables. The body computes the sum, then divides by count, then returns the average:
CREATE OR REPLACE FUNCTION get_average(a NUMERIC, b NUMERIC, c NUMERIC)
RETURNS NUMERIC AS $$
DECLARE
total NUMERIC;
avg NUMERIC;
BEGIN
total := a + b + c;
avg := total / 3;
RETURN avg;
END;
$$ LANGUAGE plpgsql;
SELECT get_average(80, 90, 100) AS class_average;
get_average prose contradicted itself — one paragraph said the function "calculates the sum," another said it "calculates the average." This redesign clarifies that the function divides the sum by 3 to compute the average, matching what the body actually does.A function can call itself. The classic factorial demonstrates recursion plus a base case via RETURN 1 to terminate:
CREATE OR REPLACE FUNCTION factorial(n INTEGER)
RETURNS BIGINT AS $$
BEGIN
-- Guard against negative input
IF n < 0 THEN
RAISE EXCEPTION 'factorial undefined for negative input: %', n;
END IF;
-- Base case
IF n <= 1 THEN
RETURN 1;
END IF;
-- Recursive case
RETURN n * factorial(n - 1);
END;
$$ LANGUAGE plpgsql;
SELECT factorial(5) AS five_fact,
factorial(10) AS ten_fact;
Note RETURNS BIGINT — factorials grow fast. factorial(20) is already in the hundreds of trillions; INTEGER would overflow well before that.
A function can have several RETURN statements. The first one reached terminates the function:
CREATE OR REPLACE FUNCTION grade_letter(score INTEGER)
RETURNS CHAR AS $$
BEGIN
IF score >= 90 THEN RETURN 'A';
ELSIF score >= 80 THEN RETURN 'B';
ELSIF score >= 70 THEN RETURN 'C';
ELSIF score >= 60 THEN RETURN 'D';
ELSE RETURN 'F';
END IF;
END;
$$ LANGUAGE plpgsql;
SELECT grade_letter(95) AS top,
grade_letter(72) AS mid,
grade_letter(45) AS low;
A function can return an entire row by declaring RETURNS table_name and using a %ROWTYPE variable:
CREATE OR REPLACE FUNCTION get_employee(p_id INTEGER)
RETURNS employees AS $$
DECLARE
emp employees%ROWTYPE;
BEGIN
SELECT *
INTO emp
FROM employees
WHERE employee_id = p_id;
RETURN emp;
END;
$$ LANGUAGE plpgsql;
SELECT * FROM get_employee(1);
The function returns one row of the employees table. Calling it via SELECT * FROM get_employee(1) expands the composite into individual columns.
Two ways out of a function:
| Statement | Effect |
|---|---|
RETURN expr; | Normal exit. Caller gets the value. Transaction continues. |
RAISE EXCEPTION '...' | Error exit. Function aborts; caller's transaction rolls back unless they catch with EXCEPTION. |
For a "this input is invalid" case (negative factorial input, division by zero, etc.) RAISE EXCEPTION is the right tool — RETURN-ing a sentinel value like -1 is fragile because callers may forget to check.
- Always include
LANGUAGE plpgsql— without it, Postgres tries to parse the body as plain SQL. - Use
CREATE OR REPLACE FUNCTIONfor deployment — re-running the script updates the function rather than erroring. - Use sized return types when meaningful —
BIGINTfor factorials,NUMERIC(p,s)for monetary calculations, etc. - Validate inputs with RAISE EXCEPTION rather than returning sentinel values — callers can't forget to check exceptions.
- Avoid unreachable code after RETURN — Postgres won't catch it but it confuses readers.
- For composite returns, prefer
RETURNS table_namewhen the shape matches a real table; it gives clearer naming and self-documents.
RETURN expressionends a function and sends the value back to the caller.- Anonymous DO blocks have no caller — RETURN belongs only inside
CREATE FUNCTIONdefinitions. LANGUAGE plpgsqlis required at the end ofCREATE FUNCTION— omitting it breaks parsing.- Functions can have multiple RETURN statements; the first one reached terminates the function.
- For composite (row) returns, declare
RETURNS table_nameand return a%ROWTYPEvariable. - For invalid inputs, prefer
RAISE EXCEPTIONover returning sentinel values.