PL/pgSQL Parameter Modes
Master function parameter modes — IN (default), OUT (return values), and INOUT (both directions). Critical fixes: original WHERE clause referenced undefined film_title variable, and INOUT example used PERFORM which discards the modified value. Use SELECT INTO not PERFORM to capture INOUT outputs in a PL/pgSQL block.
PL/pgSQL functions support three parameter modes: IN, OUT, and INOUT. They control whether the function reads a parameter, writes to it, or both:
| Mode | Direction | Default value supplied by |
|---|---|---|
IN (default) | Caller → function | Caller; ignored after the call returns |
OUT | Function → caller | Function assigns; caller can't initialize |
INOUT | Both directions | Caller initializes; function can modify |
Most parameters are IN — that's the default if you don't specify a mode. OUT and INOUT exist for the special case where you want a function to return multiple values without bundling them into a composite type.
IN and INOUT — not OUT. Functions support all three. If you're writing a procedure, use INOUT for outputs (covered on the procedure pages).An IN parameter is data flowing into the function. Changes inside the function don't escape — the caller's value is unaffected. Most parameters fit this pattern.
CREATE OR REPLACE FUNCTION add_numbers(a INT, b INT) -- IN is implicit
RETURNS INT
LANGUAGE plpgsql
AS $$
BEGIN
RETURN a + b;
END;
$$;
SELECT add_numbers(5, 3);
An OUT parameter is data flowing out of the function — a way to "return" multiple named values without using a composite type. The function assigns to the OUT parameter; the caller receives it as part of the function's result.
When a function has OUT parameters, you don't write a RETURNS clause for the same values — the OUT parameters' types implicitly define the return shape.
Look up a film's length by title. Setup:
DROP TABLE IF EXISTS film;
CREATE TABLE film (
film_id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
length INT
);
INSERT INTO film (title, length) VALUES
('Film A', 90),
('Film B', 120),
('Film C', 150),
('Film D', 80),
('Film E', 30),
('Film F', 60);
CREATE OR REPLACE FUNCTION get_film_length(p_title VARCHAR)
RETURNS INT
LANGUAGE plpgsql
AS $$
DECLARE
film_length INT;
BEGIN
SELECT f.length INTO film_length
FROM film f
WHERE f.title = p_title;
RETURN film_length;
END;
$$;
SELECT get_film_length('Film A');
title VARCHAR, then in the WHERE clause referenced film_title (which doesn't exist anywhere). The function would fail to create with "column film_title does not exist". Fixed: parameter renamed to p_title (avoiding shadowing the column name) and used in the WHERE clause.
Naming tip: prefix parameters with p_ when they might conflict with column names. WHERE title = title is ambiguous — does that mean column = parameter, or column = column? Best to avoid the question entirely.
OUT parameters earn their keep when you want to return multiple named values:
CREATE OR REPLACE FUNCTION get_film_details(
p_film_id INT,
OUT title VARCHAR,
OUT length INT
)
LANGUAGE plpgsql
AS $$
BEGIN
SELECT f.title, f.length
INTO title, length
FROM film f
WHERE f.film_id = p_film_id;
END;
$$;
SELECT * FROM get_film_details(1);
Three things happening here:
- No
RETURNSclause — the OUT parameters define the shape of the result. - No
RETURNstatement in the body — assignments to the OUT params are the return value. - Called as
SELECT * FROM— treats the function call as a row source so the columns spread.
An INOUT parameter starts with a value supplied by the caller and ends with a value supplied by the function. Effectively a parameter that's both read and written.
A function that adjusts a length by a given amount — passes len in, modifies it, returns it:
CREATE OR REPLACE FUNCTION adjust_length(
INOUT len INT,
adjustment INT
)
LANGUAGE plpgsql
AS $$
BEGIN
len := len + adjustment;
END;
$$;
-- From SQL: returns the modified value
SELECT adjust_length(100, 20);
-- From a PL/pgSQL block: the variable is updated in place
DO $$
DECLARE
new_length INT := 100;
BEGIN
SELECT adjust_length(new_length, 20) INTO new_length;
RAISE NOTICE 'New length: %', new_length;
END
$$;
PERFORM adjust_length(new_length, 20) to call the function from a DO block. That's wrong: PERFORM discards the function's return value, so the modified new_length would never be captured. To get the value back into the caller's variable, you must use SELECT function_call(...) INTO variable. Fixed in the example above.| Want to… | Best fit |
|---|---|
| Pass data into a function | IN (the default) |
| Return a single computed value | Plain function with RETURNS type + RETURN |
| Return multiple named values | Multiple OUT parameters |
| Pass in, modify, and return one parameter | INOUT |
| Return many rows | RETURNS TABLE(...) or RETURNS SETOF (next pages) |
| Return a row from a table type | RETURNS table_name with a %ROWTYPE variable |
RETURNS TABLE(...) instead. A function with three OUT parameters and one returning a single-row TABLE are functionally similar; the TABLE form is cleaner when callers will always use it as a row source.- Default to IN parameters. Most data flows in, not out. Reach for OUT/INOUT only when you need to.
- Prefix parameters to avoid column-name collisions.
p_titlebeatstitlewhen the table has atitlecolumn. Saves you fromWHERE title = titleambiguity. - Use
SELECT ... INTOto capture INOUT results in a PL/pgSQL block, notPERFORM. PERFORM discards return values. - Don't mix RETURNS with OUT parameters — pick one. Either an explicit return type with
RETURNin the body, or OUT parameters that define the shape implicitly. - For multi-row outputs, use RETURNS TABLE or SETOF rather than collecting into OUT arrays. Cleaner and more idiomatic.
- Document the parameter direction in the function name or comment.
increment_counter(INOUT cnt)reads more clearly than justincrement_counter(cnt).
- Functions support three parameter modes:
IN(default, caller-to-function),OUT(function-to-caller),INOUT(both). - Most parameters are
IN— pass data into the function. Changes don't escape. OUTparameters let a function return multiple named values without a composite type. NoRETURNSclause needed when using OUT.INOUTis rare but useful when a function should mutate a passed value in place.- To capture INOUT/OUT results in a PL/pgSQL block, use
SELECT function(...) INTO var, notPERFORM. - For many rows, prefer
RETURNS TABLEorRETURNS SETOFover arrays of OUT parameters.