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 returnRETURNS integer, RETURNS text, etc. Use RETURN expression.
  • Composite returnRETURNS table_name or RETURNS some_record_type. Use RETURN row_variable or RETURN ROW(...).
  • Set-returningRETURNS 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 meaningfulBIGINT 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.