liveBook · Manning

Database function

As explained in Just Use Postgres!

A database function is a stored routine that accepts inputs, performs computation or data operations, and returns a value or result within the database.

A database function is a stored routine that accepts inputs, performs computation or data operations, and returns a value or result inside the database. Applications call it by name, while the database centralizes logic close to the data. section 2.6

Use database functions when:

  • Multiple clients or microservices must share complex business logic tightly coupled to stored data. section 2.6
  • Multistep operations would otherwise require several application-to-database round trips. section 2.6
  • A database-backed API needs to produce or consume messages through SQL and transactional data operations. section 11.1
Listing 2.20 Function that returns the product price
CREATE OR REPLACE FUNCTION products.get_product_price(product_id INT)
RETURNS NUMERIC(10, 2) AS $$
    SELECT price
    FROM products.catalog
    WHERE id = product_id;
$$ LANGUAGE sql;

A minimal SQL function returns a product price by accepting a product identifier, querying the catalog, and declaring a numeric return type. section 2.6

Shopping-cart workflow

A PostgreSQL function can add an item to a customer's pending order, create that order when necessary, merge repeated products, and return the resulting rows. A second function can find the pending order, calculate its total, mark it ordered, and return the checkout result. Calling the functions for customer 3 adds products priced at 13.99 and 19.99, then checkout returns a total of 47.97. section 2.6.1

Listing 2.22 Implementing the order_add_item function
CREATE OR REPLACE FUNCTION sales.order_add_item(customer_id_param INT,
  product_id_param INT, quantity_param INT)  
RETURNS TABLE (order_id UUID, prod_id INT,
  quantity INT, prod_price DECIMAL) AS $$
DECLARE
    pending_order_id UUID;  
BEGIN
    SELECT id INTO pending_order_id  
    FROM sales.orders
    WHERE customer_id = customer_id_param
      AND status = 'pending'
    LIMIT 1;

    IF pending_order_id IS NULL THEN  
        INSERT INTO sales.orders (customer_id, status)
        VALUES (customer_id_param, 'pending')
        RETURNING id INTO pending_order_id;
    END IF;

    MERGE INTO sales.order_items AS oi  
    USING (SELECT id, price FROM products.catalog 
      WHERE id = product_id_param) AS prod
    ON oi.product_id = prod.id AND oi.order_id = pending_order_id
    WHEN MATCHED THEN
        UPDATE SET quantity = quantity_param
    WHEN NOT MATCHED THEN
        INSERT (order_id, product_id, quantity, price)
        VALUES (pending_order_id, prod.id, quantity_param, prod.price);

    RETURN QUERY  
    SELECT oi.order_id, oi.product_id, oi.quantity, oi.price as prod_price
    FROM sales.order_items as oi
    WHERE oi.order_id = pending_order_id;
END;
$$ LANGUAGE plpgsql;

The order_add_item implementation shows parameters, local variables, conditional order creation, catalog lookup, and returned table results. section 2.6.1

Listing 2.24 Implementing the order_checkout function
CREATE OR REPLACE FUNCTION sales.order_checkout(customer_id_param INT) 
RETURNS TABLE (order_id UUID, customer_id INT, total_amount DECIMAL) AS $$
DECLARE
    pending_order_id UUID; 
    final_total_amount DECIMAL := 0;
BEGIN
    SELECT id INTO pending_order_id 
    FROM sales.orders as o
    WHERE o.customer_id = customer_id_param
      AND status = 'pending'
    LIMIT 1;

    IF pending_order_id IS NULL THEN 
        RAISE EXCEPTION 'No pending order found for customer %',
          customer_id_param;
    END IF;

    SELECT SUM(oi.quantity * oi.price) INTO final_total_amount 
    FROM sales.order_items oi
    WHERE oi.order_id = pending_order_id;

    UPDATE sales.orders  
    SET status = 'ordered',
        total_amount = final_total_amount,
        order_date = CURRENT_TIMESTAMP
    WHERE id = pending_order_id;

    UPDATE products.catalog  
    SET stock_quantity = stock_quantity - oi.quantity
    FROM sales.order_items oi
    WHERE products.catalog.id = oi.product_id
      AND oi.order_id = pending_order_id;

    RETURN QUERY  
    SELECT o.id, o.customer_id, o.total_amount
    FROM sales.orders as o
    WHERE o.id = pending_order_id;
END;
$$ LANGUAGE plpgsql;

The order_checkout implementation demonstrates exception handling, aggregation, updates, and a returned order summary. section 2.6.1

How Just Use Postgres! covers it

How deep it goes
Defines SQL functions and demonstrates parameters, return tables, conditionals, updates, exceptions, and replacement, without deep administration guidance.
Examples
Uses product-price lookup, pending shopping carts, item addition, checkout totals, and database-backed message APIs as examples.
What you can do after
Build it from scratch using CREATE OR REPLACE FUNCTION, call it from SQL, inspect returned rows, and centralize multistep data workflows.
How it connects
Connects database functions with SQL, PL/pgSQL, triggers, schemas, tables, transactions, and application or microservice clients.

Also explained in

Compare how 3 books explain database function →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Just Use Postgres!. The section links point to the parts of the book it draws on.