Stored procedures and functions in PostgreSQL

Maria Garcia Feb 2026
2 tabs
-- Simple function
CREATE OR REPLACE FUNCTION get_full_name(
  first_name VARCHAR,
  last_name VARCHAR
)
RETURNS VARCHAR AS $$
BEGIN
  RETURN first_name || ' ' || last_name;
END;
$$ LANGUAGE plpgsql;

-- Usage
SELECT get_full_name('John', 'Doe');

-- Function with complex logic
CREATE OR REPLACE FUNCTION calculate_discount(
  customer_id INT,
  order_total DECIMAL
)
RETURNS DECIMAL AS $$
DECLARE
  customer_tier VARCHAR;
  discount_pct DECIMAL := 0;
BEGIN
  -- Get customer tier
  SELECT tier INTO customer_tier
  FROM customers
  WHERE id = customer_id;

  -- Calculate discount based on tier and total
  discount_pct := CASE customer_tier
    WHEN 'gold' THEN 0.15
    WHEN 'silver' THEN 0.10
    WHEN 'bronze' THEN 0.05
    ELSE 0
  END;

  -- Additional discount for large orders
  IF order_total > 1000 THEN
    discount_pct := discount_pct + 0.05;
  END IF;

  RETURN order_total * discount_pct;
END;
$$ LANGUAGE plpgsql;

-- Table-returning function
CREATE OR REPLACE FUNCTION get_user_orders(user_id_param INT)
RETURNS TABLE (
  order_id INT,
  order_date TIMESTAMP,
  total DECIMAL,
  status VARCHAR
) AS $$
BEGIN
  RETURN QUERY
  SELECT id, created_at, amount, status::VARCHAR
  FROM orders
  WHERE user_id = user_id_param
  ORDER BY created_at DESC;
END;
$$ LANGUAGE plpgsql;

-- Usage in SELECT
SELECT * FROM get_user_orders(123);

-- Function with loops
CREATE OR REPLACE FUNCTION generate_monthly_report(
  year_param INT
)
RETURNS TABLE (
  month INT,
  total_sales DECIMAL,
  total_orders INT
) AS $$
DECLARE
  month_num INT;
BEGIN
  FOR month_num IN 1..12 LOOP
    RETURN QUERY
    SELECT
      month_num,
      COALESCE(SUM(amount), 0),
      COUNT(*)::INT
    FROM orders
    WHERE EXTRACT(YEAR FROM created_at) = year_param
      AND EXTRACT(MONTH FROM created_at) = month_num;
  END LOOP;
END;
$$ LANGUAGE plpgsql;

-- Exception handling
CREATE OR REPLACE FUNCTION safe_divide(
  numerator DECIMAL,
  denominator DECIMAL
)
RETURNS DECIMAL AS $$
BEGIN
  RETURN numerator / denominator;
EXCEPTION
  WHEN division_by_zero THEN
    RETURN NULL;
  WHEN OTHERS THEN
    RAISE NOTICE 'Error: %', SQLERRM;
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;
2 files · sql Explain with highlit

Stored procedures encapsulate business logic in database. Functions return values; procedures don't (PostgreSQL 11+). I use functions for reusable calculations, data transformations. PL/pgSQL provides procedural language—variables, loops, conditionals. Functions can be called in SELECT statements. Procedures support transaction control—COMMIT/ROLLBACK. Triggers execute functions automatically on data changes. User-defined functions enable complex aggregations. Understanding when to use database logic versus application code is key. Functions reduce network overhead for complex operations. Security-definer functions run with creator privileges. Stored procedures improve performance for data-intensive operations but reduce portability.