PostgreSQL Function Basics
Introduction
Functions in PostgreSQL allow you to encapsulate logic that can be reused across your database operations. They're like mini-programs that run within your database, accepting input parameters, performing operations, and returning results. PostgreSQL functions can be written in SQL, PL/pgSQL (PostgreSQL's procedural language), or other languages like Python, Perl, or JavaScript.
In this tutorial, we'll focus on the fundamentals of PostgreSQL functions, covering their syntax, how to create them, and practical examples that demonstrate their power.
Why Use PostgreSQL Functions?
Before diving into the details, let's understand why functions are valuable:
- Code Reusability: Write logic once, use it multiple times
- Abstraction: Hide complex operations behind a simple interface
- Maintainability: Update logic in one place rather than throughout your codebase
- Security: Control access to underlying data while allowing operations on it
- Performance: Reduce network traffic by executing multiple operations server-side
Function Syntax Basics
The basic syntax for creating a function in PostgreSQL is:
CREATE [OR REPLACE] FUNCTION function_name(parameter_list)
RETURNS return_type
AS $$
BEGIN
-- function body
RETURN value;
END;
$$ LANGUAGE plpgsql;
Let's break down each part:
- CREATE [OR REPLACE] FUNCTION: This statement creates a new function or replaces an existing one.
- function_name: The name of your function, following PostgreSQL's naming rules.
- parameter_list: Input parameters with their data types, e.g.,
param1 INTEGER, param2 TEXT
. - RETURNS return_type: The data type of the value the function will return.
- AS : The function body enclosed in dollar-quoted string literals.
- LANGUAGE plpgsql: Specifies the language the function is written in.
Creating Your First PostgreSQL Function
Let's start with a simple example:
CREATE OR REPLACE FUNCTION add_numbers(a INTEGER, b INTEGER)
RETURNS INTEGER
AS $$
BEGIN
RETURN a + b;
END;
$$ LANGUAGE plpgsql;
This function takes two integers, adds them together, and returns the result.
To call this function:
SELECT add_numbers(5, 3);
Output:
add_numbers
-------------
8
Functions with SQL-Only Syntax
For simple functions that just return the result of a SQL expression, you can use a more concise syntax:
CREATE OR REPLACE FUNCTION multiply_numbers(a INTEGER, b INTEGER)
RETURNS INTEGER
AS $$
SELECT a * b;
$$ LANGUAGE sql;
Calling this function:
SELECT multiply_numbers(4, 5);
Output:
multiply_numbers
------------------
20
Function Parameters
PostgreSQL functions support several parameter modes:
- IN: Input-only parameters (default)
- OUT: Output-only parameters
- INOUT: Both input and output parameters
- VARIADIC: Variable number of parameters
Default Parameters
You can specify default values for parameters:
CREATE OR REPLACE FUNCTION greet(name TEXT DEFAULT 'Guest')
RETURNS TEXT
AS $$
BEGIN
RETURN 'Hello, ' || name || '!';
END;
$$ LANGUAGE plpgsql;
Now you can call the function with or without a parameter:
SELECT greet('Alice');
SELECT greet();
Output:
greet
--------------
Hello, Alice!
greet
-------------
Hello, Guest!
OUT Parameters
OUT parameters provide a way to return multiple values:
CREATE OR REPLACE FUNCTION get_rectangle_properties(
width NUMERIC,
height NUMERIC,
OUT area NUMERIC,
OUT perimeter NUMERIC
)
AS $$
BEGIN
area := width * height;
perimeter := 2 * (width + height);
END;
$$ LANGUAGE plpgsql;
When calling this function, you'll get a row with both values:
SELECT * FROM get_rectangle_properties(5.0, 3.0);
Output:
area | perimeter
------+-----------
15.0 | 16.0
Return Types
PostgreSQL functions can return various types:
Scalar Values
The examples above return scalar values (single values of a specific type).
Composite Types (Records)
Functions can return composite types, including existing table rows:
CREATE OR REPLACE FUNCTION create_person(p_name TEXT, p_age INTEGER)
RETURNS record
AS $$
DECLARE
person record;
BEGIN
person := (p_name, p_age);
RETURN person;
END;
$$ LANGUAGE plpgsql;
Table Types (Sets of Rows)
Functions can return sets of rows using RETURNS TABLE
or RETURNS SETOF
:
CREATE OR REPLACE FUNCTION get_numbers(n INTEGER)
RETURNS TABLE(num INTEGER, square INTEGER, cube INTEGER)
AS $$
BEGIN
FOR i IN 1..n LOOP
num := i;
square := i * i;
cube := i * i * i;
RETURN NEXT;
END LOOP;
END;
$$ LANGUAGE plpgsql;
When calling this function, you'll get a table:
SELECT * FROM get_numbers(3);
Output:
num | square | cube
-----+--------+------
1 | 1 | 1
2 | 4 | 8
3 | 9 | 27
Practical Examples
Let's look at some practical examples of PostgreSQL functions.
Example 1: Age Calculator
This function calculates the age in years based on a birthdate:
CREATE OR REPLACE FUNCTION calculate_age(birthdate DATE)
RETURNS INTEGER
AS $$
BEGIN
RETURN EXTRACT(YEAR FROM AGE(CURRENT_DATE, birthdate));
END;
$$ LANGUAGE plpgsql;
Usage:
SELECT calculate_age('1990-05-15');
Output (as of 2025):
calculate_age
--------------
35
Example 2: Email Validator
This function validates an email address using a simple regex pattern:
CREATE OR REPLACE FUNCTION is_valid_email(email TEXT)
RETURNS BOOLEAN
AS $$
BEGIN
RETURN email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$';
END;
$$ LANGUAGE plpgsql;
Usage:
SELECT is_valid_email('[email protected]'), is_valid_email('invalid-email');
Output:
is_valid_email | is_valid_email
----------------+----------------
true | false
Example 3: Transaction Function
This function transfers money from one account to another within a transaction:
CREATE OR REPLACE FUNCTION transfer_money(
sender_id INTEGER,
receiver_id INTEGER,
amount NUMERIC
)
RETURNS BOOLEAN
AS $$
DECLARE
sender_balance NUMERIC;
BEGIN
-- Check sender balance
SELECT balance INTO sender_balance FROM accounts WHERE id = sender_id;
IF sender_balance < amount THEN
RAISE EXCEPTION 'Insufficient funds';
END IF;
-- Update sender account
UPDATE accounts SET balance = balance - amount WHERE id = sender_id;
-- Update receiver account
UPDATE accounts SET balance = balance + amount WHERE id = receiver_id;
RETURN TRUE;
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'Transaction failed: %', SQLERRM;
RETURN FALSE;
END;
$$ LANGUAGE plpgsql;
This function demonstrates error handling and transactions in PostgreSQL.
Function Organization
As your database grows, you might want to organize functions using schemas:
CREATE SCHEMA utils;
CREATE OR REPLACE FUNCTION utils.format_phone(phone TEXT)
RETURNS TEXT
AS $$
BEGIN
RETURN REGEXP_REPLACE(phone, '(\d{3})(\d{3})(\d{4})', '(\1) \2-\3');
END;
$$ LANGUAGE plpgsql;
Call it with:
SELECT utils.format_phone('1234567890');
Output:
format_phone
--------------
(123) 456-7890
Function Lifecycle
Creating Functions
We've seen the CREATE FUNCTION
syntax above. Remember that function names can be overloaded with different parameter types.
Replacing Functions
Use CREATE OR REPLACE FUNCTION
to update an existing function:
CREATE OR REPLACE FUNCTION add_numbers(a INTEGER, b INTEGER)
RETURNS INTEGER
AS $$
BEGIN
-- Updated function body
RETURN a + b + 0; -- No functional change, just demonstrating replacement
END;
$$ LANGUAGE plpgsql;
Dropping Functions
To remove a function:
DROP FUNCTION [IF EXISTS] function_name(parameter_types);
For example:
DROP FUNCTION IF EXISTS add_numbers(INTEGER, INTEGER);
Function Information
You can get information about your functions from the PostgreSQL catalog:
SELECT proname, prosrc
FROM pg_proc
WHERE proname = 'add_numbers';
Or use the \df
command in psql
:
\df add_numbers
Function Flow Control
PL/pgSQL provides several flow control structures:
Conditional Logic
CREATE OR REPLACE FUNCTION get_grade(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;
Loops
CREATE OR REPLACE FUNCTION factorial(n INTEGER)
RETURNS INTEGER
AS $$
DECLARE
result INTEGER := 1;
BEGIN
FOR i IN 1..n LOOP
result := result * i;
END LOOP;
RETURN result;
END;
$$ LANGUAGE plpgsql;
Common Mistakes and Best Practices
Common Mistakes
- Forgetting to handle NULL values: Always consider how your function behaves with NULL inputs.
- Not considering edge cases: Test your functions with extreme values.
- Ignoring error handling: Add proper exception handling for robust functions.
Best Practices
- Add function comments: Document what your function does, its parameters, and return values.
- Use schema for organization: Group related functions in schemas.
- Test thoroughly: Write test cases for your functions.
- Consider security: Use
SECURITY DEFINER
carefully. - Be aware of transaction context: Understand how your function behaves within transactions.
Function Performance Considerations
- IMMUTABLE, STABLE, or VOLATILE: Declare your function with the appropriate volatility for optimization.
- Cost estimation: You can provide a
COST
hint to the query planner. - Parallel safety: Mark functions as
PARALLEL SAFE
when appropriate.
CREATE OR REPLACE FUNCTION square(n INTEGER)
RETURNS INTEGER
AS $$
SELECT n * n;
$$ LANGUAGE sql IMMUTABLE;
Function Diagram
Here's a visualization of the PostgreSQL function execution process:
Summary
PostgreSQL functions are powerful tools for encapsulating logic and improving database performance and maintainability. In this tutorial, we've covered:
- Basic function syntax and creation
- Different types of parameters
- Various return types
- Practical examples
- Function organization and lifecycle
- Flow control within functions
- Best practices for function development
With this foundation, you're ready to start creating and using functions in your PostgreSQL databases.
Additional Resources
- Practice by writing functions to solve specific problems in your database
- Experiment with more complex return types
- Learn about triggers, which use functions to respond to database events
- Explore other procedural languages supported by PostgreSQL
As you continue your PostgreSQL journey, functions will become an essential tool in your database development toolkit.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)