This section is an introduction to Stored Programs in SQL.
SQL examples in this section will be using PostgreSQL syntax unless otherwise noted. There are no live in-browser examples as SQLite does not support stored procedures.
§1 Introduction to Stored Programs #
Stored programs (also known as “stored procedures” or “stored functions”) are executable code units that are stored within the database itself. They allow you to encapsulate complex SQL logic and execute it as a single unit. These programs are written using database-specific procedural languages. Unlike application-level programming logic, stored programs execute directly on the database server, close to the data, which can offer significant performance and security advantages.
§1.1 Database-Specific Procedural Languages #
Each major database management system (DBMS) typically has its own procedural language for writing stored programs, a few examples include:
- PL/pgSQL (PostgreSQL)
- SQL/PSM (MySQL)
- T-SQL (Microsoft SQL Server)
- PL/SQL (Oracle)
All of these languages extend SQL with procedural constructs like variables, conditional statements (IF/ELSE), loops, and error handling. The syntax and specific features can differ significantly between each DBMS. This makes stored programs largely non-portable between different DBMS.
§1.2 Pros and Cons of Stored Programs #
Pros:
- Stored programs are compiled and optimized once when they are created, then the compiled version is stored in the database. This leads to faster performance.
- Stored programs can be used to abstract underlying table structures. Database users can be granted permissions to execute a stored program without having direct access to the tables it manipulates. This can be used as a security control.
- Once a stored program is created, it can be called multiple times by different applications or users, promoting code reuse.
- A stored program can run complex code that would normally require multiple SQL statements. Instead of sending multiple SQL statements over the network, an application can send a single call to a stored program, which then executes all the necessary SQL statements on the database server, minimizing network traffic.
Cons:
- Stored programs are written in database-specific procedural languages (e.g., PL/pgSQL, T-SQL, PL/SQL). This means code written for one database system is generally not directly transferable to another without significant modification.
- Debugging stored programs can be more complex than debugging application-level code, often requiring specialized database tools and techniques.
- Managing versions of stored programs within a database can be more challenging than managing application code in a standard version control system.
- Unit testing stored programs can be more involved, as it often requires setting up specific database states and transaction contexts.
§2 Stored Functions #
A stored function is a named block of code that is stored and executed on the database server. It performs a specific task and returns a single value (scalar) or a table, which can be used in SQL queries or other database operations.
§2.1
Syntax for CREATE FUNCTION
#
The syntax for creating a function is similar to a procedure, but it includes a RETURNS clause to specify the data type of the value the function will return.
CREATE FUNCTION function_name (
[ parameter_name data_type [ DEFAULT value ], ... ]
)
RETURNS data_type -- Specifies the data type of the return value
LANGUAGE plpgsql -- Optional Specify which procedural language is being used
AS $$
DECLARE -- Optional
-- Can optionally declare temporary variables here
var_name data_type;
BEGIN
-- SQL statements and procedural logic go here
RETURN return_value;
END;
$$;
§2.2 Example: Creating a Simple Function #
Let’s create a function that calculates the full name of an employee.
CREATE FUNCTION GetFullName (
p_first_name VARCHAR(50),
p_last_name VARCHAR(50)
)
RETURNS VARCHAR(101) -- Max length of first_name + space + last_name
LANGUAGE plpgsql
AS $$
DECLARE
full_name VARCHAR(101);
BEGIN
-- SELECT ... INTO declared_var, lets you store data in a temporary variable
SELECT p_first_name || ' ' || p_last_name INTO full_name;
-- || is the SQL operator for string concatenation
RETURN full_name;
END;
$$;
This function is named GetFullName, and is called with two VARCHAR(50) parameters, it returns a single VARCHAR(101).
§2.3 Calling Functions in SQL Queries #
Functions can be called directly within SQL queries, just like built-in functions such as COUNT() or SUM().
SELECT
employee_id,
GetFullName(first_name, last_name) AS full_name,
email
FROM Employees;
employee_id | full_name | email
-------------+------------+------------------------
1 | John Doe | john.doe@example.com
2 | Jane Smith | jane.smith@example.com
§2.4 Example: Function for Salary Bonus Calculation #
This function calculates a “bonus amount” based on an employee’s salary and a given bonus percentage:
CREATE FUNCTION CalculateBonus (
p_salary DECIMAL(10, 2),
p_bonus_percentage DECIMAL(5, 2)
)
RETURNS DECIMAL(10, 2)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN ROUND(p_salary * (p_bonus_percentage / 100), 2); -- function logic
END;
$$;
SELECT
employee_id,
first_name,
last_name,
salary,
CalculateBonus(salary, 10.00) AS bonus_amount -- use function, Calculate a 10% bonus
FROM Employees;
employee_id | first_name | last_name | salary | bonus_amount
-------------+------------+-----------+----------+--------------
1 | John | Doe | 60000.00 | 6000.00
2 | Jane | Smith | 75000.00 | 7500.00
Here, the stored function receives two parameters: salary, and a hardcoded value 10.00. Any value that matches the function parameter data type can be utilized when calling the stored function.
§2.5 Updating an Existing function #
You can update an existing function by using a CREATE OR REPLACE statement. The CREATE statement will always fail if a stored function with the same name and parameters already exists.
CREATE OR REPLACE FUNCTION CalculateBonus ( --creates function or updates an existing function
p_salary DECIMAL(10, 2),
p_bonus_percentage DECIMAL(5, 2)
)
RETURNS DECIMAL(10, 2)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN ROUND(p_salary * (p_bonus_percentage / 100), 4); -- Calculate a 10% bonus, rounded to 4 decimals
END;
$$;
employee_id | first_name | last_name | salary | bonus_amount
-------------+------------+-----------+----------+--------------
1 | John | Doe | 60000.00 | 6000.0000
2 | Jane | Smith | 75000.00 | 7500.0000
§2.6 Function Overloading #
Function overloading is a feature in some database systems (like PostgreSQL, Oracle, and Microsoft SQL Server) that allows you to create multiple stored functions (or procedures) with the same name but with different input parameters.
The database system distinguishes between overloaded functions based on their signature, which includes the function’s name and the number, order, and data types of its input parameters. When a function is called, the database determines which specific version to execute by matching the provided arguments to the defined parameter lists.
-- Function 1: Calculates bonus based on a percentage
CREATE OR REPLACE FUNCTION CalculateBonus (
p_salary DECIMAL(10, 2),
p_bonus_percentage DECIMAL(5, 2)
)
RETURNS DECIMAL(10, 2)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN ROUND(p_salary * (p_bonus_percentage / 100), 2);
END;
$$;
-- Function 2: Calculates a fixed bonus amount
CREATE OR REPLACE FUNCTION CalculateBonus (
p_fixed_amount DECIMAL(10, 2)
)
RETURNS DECIMAL(10, 2)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN ROUND(p_fixed_amount * (10 / 100), 2);
END;
$$;
Calling overloaded functions:
-- Calls the version with (DECIMAL, DECIMAL) parameters
SELECT CalculateBonus(50000.00, 15.00) AS bonus_by_percentage;
-- Calls the version with (DECIMAL) parameter
SELECT CalculateBonus(2500.00) AS fixed_bonus;
Note: Not all database systems support function overloading. For instance: MySQL requires all stored functions and procedures within the same schema to have unique names regardless of their parameter lists.
§3 Stored Procedures #
A Stored Procedure is a named block of SQL statements that performs a specific task. Procedures can accept input parameters and can return output parameters, but they do not necessarily return a single value like a function. They are primarily used for performing actions or manipulating data.
§3.1
Syntax for CREATE PROCEDURE
#
The basic syntax for creating a stored procedure involves specifying the procedure’s name, its parameters (if any), and the body of the procedure. The procedure is written in the database’s specific procedural language. Unlike most standardized SQL queries/statements, procedures are highly specific to the database engine being used.
CREATE PROCEDURE procedure_name (
[ parameter_name [ IN | OUT | INOUT ] data_type [ DEFAULT value ], ... ]
)
LANGUAGE plpgsql -- Or other procedural language like T-SQL, PL/SQL
AS $$
BEGIN
-- SQL statements and procedural logic here
END;
$$;
Parameters:
IN: (Default) The parameter is passed into the procedure. Its value can be used inside the procedure but cannot be changed by the procedure.OUT: The parameter is used to pass a value out of the procedure. Its initial value isNULLinside the procedure.INOUT: The parameter is passed into the procedure, and its value can be modified and passed back out.
§3.2 Example: Creating a Simple Procedure #
Let’s create a procedure to add a new employee.
CREATE TABLE Employees (
employee_id SERIAL PRIMARY KEY,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE,
hire_date DATE DEFAULT CURRENT_DATE,
salary DECIMAL(10, 2) CHECK (salary >= 0)
);
-- create the stored procedure
CREATE PROCEDURE AddEmployee (
IN p_first_name VARCHAR(50),
IN p_last_name VARCHAR(50),
IN p_email VARCHAR(100),
IN p_salary DECIMAL(10, 2)
)
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO Employees (first_name, last_name, email, salary)
VALUES (p_first_name, p_last_name, p_email, p_salary);
END;
$$;
In this example, we created a procedure to add a new employee. This procedure is named AddEmployee, and takes an employee’s first name, last name, email, and salary as input and inserts this information into the Employees table.
§3.3 Executing Simple Procedures #
Procedures are executed using the CALL statement (in PostgreSQL, MySQL, and Microsoft SQL Server 2016+). Other server versions may use EXEC or EXECUTE.
CALL AddEmployee('John', 'Doe', 'john.doe@example.com', 60000.00);
CALL AddEmployee('Jane', 'Smith', 'jane.smith@example.com', 75000.00);
SELECT * FROM Employees;
employee_id | first_name | last_name | email | hire_date | salary
-------------+------------+-----------+------------------------+------------+----------
1 | John | Doe | john.doe@example.com | 2025-11-08 | 60000.00
2 | Jane | Smith | jane.smith@example.com | 2025-11-08 | 75000.00
§3.4
Example: Procedure with OUT Parameter
#
An OUT procedure is mostly useful when used with client environment, such as psql or calling from application code (such as Python). It allows the procedure to return a value back to the calling application.
Note: Do not worry if you do not understand the python code samples in these sections, these are just demonstrations of application code calling the stored procedures.
CREATE PROCEDURE GetEmployeeCount (
OUT p_employee_count INTEGER
)
LANGUAGE plpgsql
AS $$
BEGIN
SELECT COUNT(*) INTO p_employee_count FROM Employees;
END;
$$;
Here is a basic demonstration of calling a procedure with an OUT parameter from a Python application, using psycopg2 (a common PostgreSQL adapter):
import psycopg2
# Establish a connection to your PostgreSQL database
conn = psycopg2.connect(
host="localhost",
database="your_database_name",
user="your_username",
password="your_password"
)
cur = conn.cursor()
try:
# Call the procedure. For OUT parameters, psycopg2 returns them as a tuple.
cur.callproc('GetEmployeeCount')
employee_total = cur.fetchone()[0] # Fetch the result, which is a tuple containing the OUT parameter value
print(f"Total Employees: {employee_total}")
except Exception as e:
print(f"An error occurred: {e}")
finally:
cur.close()
conn.close()
Total Employees: 2
§3.5
Example: Procedure with IN Parameter
#
An IN parameter allows you to pass values into a procedure to influence its execution. This is the most common type of parameter.
CREATE OR REPLACE PROCEDURE UpdateEmployeeSalary (
IN p_employee_id INTEGER,
IN p_new_salary DECIMAL(10, 2)
)
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE Employees
SET salary = p_new_salary
WHERE employee_id = p_employee_id;
END;
$$;
Demonstration calling a procedure with IN parameters from a Python application using psycopg2. the line that uses the procedure is highlighted:
import psycopg2
# Establish a connection to your PostgreSQL database
conn = psycopg2.connect(
host="localhost",
database="your_database_name",
user="your_username",
password="your_password"
)
cur = conn.cursor()
try:
# Call the procedure with IN parameters.
# The parameters are passed as a tuple after the procedure name.
employee_id_to_update = 1
new_salary_value = 65000.00
cur.callproc('UpdateEmployeeSalary', (employee_id_to_update, new_salary_value))
conn.commit() # Commit the transaction to save changes
except Exception as e:
conn.rollback() # Rollback in case of error
print(f"An error occurred: {e}")
finally:
cur.close()
conn.close()
This example has no output, the UpdateEmployeeSalary procedure has no return values.
§4 Procedures vs. Functions #
While both stored procedures and functions encapsulate SQL logic, they have distinct characteristics and typical use cases.
| Feature | Stored Procedure | Stored Function |
|---|---|---|
| Return Value | Does not necessarily return a value; can “return” multiple values via OUT parameters or result sets. | Must return a single scalar value or a table. |
| Usage in SQL | Cannot be directly called within SELECT, WHERE, or HAVING clauses. Executed using CALL (or EXEC). | Can be called directly within SELECT, WHERE, or HAVING clauses, and other SQL expressions. |
| DML Operations | Can perform Data Manipulation Language (DML) operations (e.g., INSERT, UPDATE, DELETE). | Generally, functions are designed to be side-effect free (e.g., not perform DML). Some DBMS allow DML in functions, but it’s often discouraged or restricted (PostgreSQL requires VOLATILE or STABLE for DML). |
| Transaction Control | Can contain transaction control statements (e.g., COMMIT, ROLLBACK). | Typically cannot contain transaction control statements. |
| Parameters | Can have IN, OUT, and INOUT parameters. | Typically only have IN parameters. |
§4.1 When to use Stored Procedures vs. Stored Functions: #
Use Stored Procedures for tasks that involve data modification, complex business logic, or when you need to return multiple result sets or OUT parameters. A few examples:
- Batch Data Processing: When you need to process a large number of records, such as archiving old data, updating statuses, or performing complex calculations across many rows.
- Example: A procedure that iterates through
Ordersand marks orders older than 1 year asARCHIVED, moving them to anOrderHistorytable.
- Example: A procedure that iterates through
- Complex Business Logic with Multiple Steps: When a single logical operation requires several SQL statements, conditional logic, and potentially error handling.
- Example: A procedure to
ProcessNewOrderthat inserts intoOrders,OrderItems, updatesProductInventory, and logs the order, all within a single transaction.
- Example: A procedure to
- Security and Access Control: To grant users permission to perform specific operations without giving them direct access to the underlying tables.
- Example: Granting a Sales role(user) permission to execute an
AddCustomerprocedure, but not directINSERTprivileges on theCustomerstable.
- Example: Granting a Sales role(user) permission to execute an
- Data Migration or ETL (Extract, Transform, Load) Processes: For orchestrating data movement and transformation between different tables or databases.
- Example: A procedure that extracts data from a staging table, transforms it, and loads it into a production table.
Use Stored Functions for calculations, data transformations, or when you need to use the result directly within a SQL query (e.g., in a SELECT list or WHERE clause). Examples:
- Custom Calculations in Queries: When you need to perform a specific calculation repeatedly within
SELECTstatements orWHEREclauses.- Example: A function
CalculateSalesTax(amount DECIMAL(10,2))that returns the sales tax for a given amount, which can then be used inSELECT product_name, price, CalculateSalesTax(price) AS tax FROM Products;.
- Example: A function
- Data Formatting or Transformation: To standardize the format of data retrieved from the database.
- Example: A function
FormatPhoneNumber(raw_number VARCHAR(20))that takes a raw phone number string and returns it in a standardized format (e.g., “(XXX) XXX-XXXX”).
- Example: A function
- Complex Conditional Logic for Filtering: When a WHERE clause requires intricate logic that is too cumbersome to write directly in every query.
- Example: A function
IsPremiumCustomer(customer_id INTEGER)that returnsTRUEif a customer meets certain criteria (e.g., total purchases over $5,000 and active for more than 2 years), used inSELECT * FROM Customers WHERE IsPremiumCustomer(customer_id);.
- Example: A function
- Generating Derived Values: For creating new values based on existing data without modifying the underlying tables.
- Example: A function
GetAge(date_of_birth DATE)that calculates a person’s current age.
- Example: A function
§4.2 When NOT to use Stored Procedures/Functions: #
There is no explicit rule for when you can or can not use stored programs, but they are generally not recommended for situations such as:
- For basic
INSERT,UPDATE,DELETE, orSELECTstatements that don’t involve complex logic, a stored program might add unnecessary overhead. - If your application needs to be easily portable across different database systems, database-specific procedural languages will not easily migrate.
- Don’t use a stored program if a simple SQL query or application-level logic is sufficient and clearer.
- For very complex logic that is difficult to debug within the database environment, it might be better handled in application code where more robust debugging tools are available.
§5 SQL Procedure/Function Comparison with Application Code: #
From a conceptual standpoint, the distinction between procedures and functions in SQL has parallels in how you might design application code (such as Python or Java):
SQL Procedures ≈ Code functions with side effects (or multiple return values):
A application code function that modifies global state, writes to a file, or updates a database (without returning a single value) is analogous to a stored procedure. If a Python function returns multiple values, it often does so via a tuple or a dictionary, similar to how a procedure might use OUT parameters or result sets.
# Python equivalent of a procedure (modifies data, no single return)
def update_user_profile(user_id, new_email):
# ...Logic to update user email
print(f"User {user_id} email updated to {new_email}")
# no return data
SQL Functions ≈ Pure code functions (single return value, no side effects):
A code function that takes inputs, performs a calculation, then returns a single result without modifying any external state is comparable to a stored function.
# Python equivalent of a function (calculates and returns a single value)
def calculate_tax(amount, tax_rate):
return amount * tax_rate
The key difference is that SQL stored programs execute within the database server, while your client-code executes on the application server. This distinction has significant impact on performance and security as discussed in the “Benefits” section.
§6 More Resources #
Stored Procedures
- https://www.postgresql.org/docs/current/sql-createprocedure.html
- https://dev.mysql.com/doc/refman/8.0/en/create-procedure.html
- https://learn.microsoft.com/en-us/sql/relational-databases/stored-procedures/stored-procedures-database-engine?view=sql-server-ver17
- https://www.datacamp.com/tutorial/sql-stored-procedure
Stored Functions
- https://www.postgresql.org/docs/current/sql-createfunction.html
- https://dev.mysql.com/doc/refman/8.0/en/create-function.html
- https://learn.microsoft.com/en-us/sql/relational-databases/user-defined-functions/user-defined-functions?view=sql-server-ver17
Specific Database Procedural Languages
- https://www.postgresql.org/docs/current/plpgsql.html
- https://learn.microsoft.com/en-us/sql/t-sql/language-reference?view=sql-server-ver16
- https://dev.mysql.com/doc/refman/8.4/en/create-procedure.html
Questions #
- Name two advantages and two disadvantages of using stored programs (procedures or functions).
- What is the primary difference between a stored function and a stored procedure?
- Which procedural language is used to write stored programs in PostgreSQL?
- Can stored programs written for PostgreSQL be directly used in MySQL? Why or why not?
- Explain the concept of function overloading in the context of stored functions. Do all DBMS support this concept?
- When would you typically choose to use a stored procedure over a stored function? Provide an example.
- Provide one scenario where a stored function is preferable to a stored procedure.
- What is the SQL statement to run a stored procedure named
my_procedure()in PostgreSQL, provide the full SQL statement.
- Explain the purpose of the
IN,OUT, andINOUTparameters used in stored procedures.
- How do you update an existing stored function in PostgreSQL (without dropping it)?
For the remaining questions, assume you are working with a database containing two tables:
CREATE TABLE accounts (
account_id SERIAL PRIMARY KEY,
customer_name VARCHAR(100) NOT NULL,
balance DECIMAL(12,2) NOT NULL DEFAULT 0.00,
created_date DATE DEFAULT CURRENT_DATE,
status VARCHAR(20) DEFAULT 'active' -- e.g., 'active', 'frozen', 'closed'
);
INSERT INTO accounts (account_id, customer_name, balance, created_date, status)
VALUES
(1, 'Alice Johnson', 5000.00, '2024-01-15', 'active'),
(2, 'Bob Smith', 12000.00, '2023-03-22', 'active');
CREATE TABLE transactions (
transaction_id SERIAL PRIMARY KEY,
account_id INT REFERENCES accounts(account_id),
txn_type VARCHAR(10) CHECK (txn_type IN ('deposit', 'withdrawal', 'transfer_out', 'transfer_in')),
amount DECIMAL(12,2) NOT NULL,
txn_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO transactions (account_id, txn_type, amount, txn_date)
VALUES
-- Alice's transactions
(1, 'deposit', 5000.00, '2024-01-15 09:30:00'),
(1, 'withdrawal', 1200.00, '2024-02-01 14:20:00'),
(1, 'deposit', 800.00, '2024-03-10 11:05:00'),
-- Bob's transactions
(2, 'deposit', 12000.00, '2023-03-22 10:00:00'),
(2, 'withdrawal', 2500.00, '2024-01-05 16:45:00');
You can use the empty database from Assignment 6 to test your stored procedures/functions.
For each question, im expecting a SQL statement, not the output. you may use ... OR REPLACE ... for all answers to make testing easier.
Answer each question in isolation, do not re-use functionality defined for other questions unless explicitly requested.
- Create a function named
get_balancethat takes an account_id and returns the current balance as DECIMAL(12,2).
- Create a function named
depositthat:
- Accepts parameters
p_account_id INTandp_amount DECIMAL(12,2) - Adds the amount to the account’s balance
- Inserts a record into
transactionswithtxn_type = 'deposit' - Returns the new balance
HINT: use a temporary variable
- Create a function named
withdrawthat:
- Accepts
p_account_id INTandp_amount DECIMAL(12,2) - Removes the amount from the account’s balance
- Inserts a record into
transactionswithtxn_type = 'withdrawal' - Returns the updated balance
- Create a stored procedure called
transfer_fundsthat accepts:
p_from_id INTp_to_id INTp_amount DECIMAL(12,2)and runs the following:- Withdraw from source account
- Deposit into destination account
- Log transfer
transfer_outandtransfer_infor each account in thetransactionstable
- Write the SQL command to execute the
transfer_fundsprocedure to transfer $300.00 from account 1 to account 2.
- Create a function
get_interest_amountwhich takesp_balance DECIMAL(12,2)and returnsNUMERIC(5,4), calculate interest as 0.25%.
- Create a procedure named
apply_interestwhich takesaccount_id INT, and adds 0.02% to thebalance