Stored Procedures
CREATE PROCEDURE and invoked with CALL rather than used inside a SELECT. They look similar to functions on the surface, but exist to solve a problem functions structurally can't.Creating and calling a procedure
CREATE PROCEDURE transfer_funds( from_account integer, to_account integer, amount numeric ) LANGUAGE plpgsql AS $$ BEGIN UPDATE accounts SET balance = balance - amount WHERE id = from_account; UPDATE accounts SET balance = balance + amount WHERE id = to_account; COMMIT; END; $$; CALL transfer_funds(101, 202, 50.00);
The key difference: transaction control
COMMIT and ROLLBACK directly inside its own body, as shown above. This was the whole reason procedures were added to PostgreSQL: certain workflows — especially long-running batch jobs — genuinely need to commit intermediate progress rather than holding one giant transaction open for the entire run.A batch procedure that commits progress
CREATE PROCEDURE archive_old_orders()
LANGUAGE plpgsql
AS $$
DECLARE
batch_count integer;
BEGIN
LOOP
INSERT INTO orders_archive
SELECT * FROM orders
WHERE status = 'delivered' AND order_date < now() - interval '1 year'
LIMIT 1000;
GET DIAGNOSTICS batch_count = ROW_COUNT;
EXIT WHEN batch_count = 0;
DELETE FROM orders
WHERE id IN (
SELECT id FROM orders_archive
WHERE archived_at IS NULL
LIMIT 1000
);
COMMIT; -- commit each batch instead of one huge transaction
END LOOP;
END;
$$;COMMIT or ROLLBACK inside a CREATE FUNCTION body raises an error — a function is always a participant in whatever transaction is already running, never in control of it. This is the single most important practical distinction between the two.Functions vs. procedures
Function | Procedure | |
|---|---|---|
Defined with |
|
|
Invoked with |
|
|
Returns a value? | Yes ( | No return value (can use |
Can | No | Yes |
Typical use | Computations, lookups, transformations used within queries | Multi-step workflows, batch jobs, administrative tasks |
When to reach for a procedure
If what you're writing needs to return a value to be used in a query, it's a function. If it's an operational task — a multi-step workflow, a batch process that should commit its progress incrementally, or administrative maintenance logic — reach for a procedure instead.
CREATE PROCEDURE+CALL(PostgreSQL 11+), as opposed toCREATE FUNCTION+SELECT.Only procedures can
COMMIT/ROLLBACKtheir own transaction internally — functions cannot.Procedures don't return a value the way functions do; they can use
OUT/INOUTparameters if needed.Reach for a procedure for batch jobs and multi-step workflows; reach for a function for anything used inside a query.