PostgreSQL RETURN PostgreSQL · Anonymous Block · RETURN

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. Use RETURN expression.
  • Composite return — RETURNS table_name or RETURNS some_record_type. Use RETURN row_variable or RETURN ROW(...).
  • Set-returning — RETURNS SETOF type or RETURNS TABLE(...). Use RETURN 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.

📌 Anonymous DO blocks have no RETURN. A 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:

CREATE OR REPLACE FUNCTION function_name(parameters) RETURNS return_type AS $$ DECLARE -- variables BEGIN -- statements RETURN expression; END; $$ LANGUAGE plpgsql;

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.
Example 1 — Returning a Scalar

The simplest case: a function that adds two numbers and returns the result:

PL/pgSQL — scalar return
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;
OUTPUT
psql — scalar return
postgres=#CREATE OR REPLACE FUNCTION add_numbers(...) RETURNS INTEGER ...
CREATE FUNCTION
postgres=#SELECT add_numbers(10, 20) AS result;
result
--------
30
(1 row)
Example 2 — Computing an Average

A function with intermediate variables. The body computes the sum, then divides by count, then returns the average:

PL/pgSQL — 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;
OUTPUT
psql — average
postgres=#SELECT get_average(80, 90, 100) AS class_average;
class_average
---------------------
90.0000000000000000
(1 row)
⚠️ The original tutorial's 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.
Example 3 — Recursive Factorial

A function can call itself. The classic factorial demonstrates recursion plus a base case via RETURN 1 to terminate:

PL/pgSQL — factorial
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;
OUTPUT
psql — factorial
postgres=#SELECT factorial(5) AS five_fact, factorial(10) AS ten_fact;
five_fact | ten_fact
-----------+----------
120 | 3628800
(1 row)

Note RETURNS BIGINT — factorials grow fast. factorial(20) is already in the hundreds of trillions; INTEGER would overflow well before that.

Example 4 — Multiple Return Paths

A function can have several RETURN statements. The first one reached terminates the function:

PL/pgSQL — multiple returns
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;
OUTPUT
psql — multiple returns
postgres=#SELECT grade_letter(95) AS top, grade_letter(72) AS mid, grade_letter(45) AS low;
top | mid | low
-----+-----+-----
A | C | F
(1 row)
Example 5 — Returning a Composite (Row)

A function can return an entire row by declaring RETURNS table_name and using a %ROWTYPE variable:

PL/pgSQL — composite return
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);
OUTPUT
psql — composite return
postgres=#SELECT * FROM get_employee(1);
employee_id | first_name | last_name | salary | hire_date
-------------+------------+-----------+----------+------------
1 | Alice | Johnson | 75000.00 | 2020-03-15
(1 row)

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:

StatementEffect
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.

  1. Always include LANGUAGE plpgsql — without it, Postgres tries to parse the body as plain SQL.
  2. Use CREATE OR REPLACE FUNCTION for deployment — re-running the script updates the function rather than erroring.
  3. Use sized return types when meaningful — BIGINT for factorials, NUMERIC(p,s) for monetary calculations, etc.
  4. Validate inputs with RAISE EXCEPTION rather than returning sentinel values — callers can't forget to check exceptions.
  5. Avoid unreachable code after RETURN — Postgres won't catch it but it confuses readers.
  6. For composite returns, prefer RETURNS table_name when the shape matches a real table; it gives clearer naming and self-documents.
  • RETURN expression ends a function and sends the value back to the caller.
  • Anonymous DO blocks have no caller — RETURN belongs only inside CREATE FUNCTION definitions.
  • LANGUAGE plpgsql is required at the end of CREATE 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_name and return a %ROWTYPE variable.
  • For invalid inputs, prefer RAISE EXCEPTION over returning sentinel values.