Expert: design, speed and engines · Procedures, triggers, SQL engines and NoSQL · topic 52 of 62 · 35 min
Stored procedures and functions
Explanation
What they are
So far every query you wrote was sent by you (or an app) one statement at a time. A stored procedure is different: a named piece of code that lives inside the database itself, with parameters, variables, IF and loops. You create it once, and afterwards anyone with permission runs it by name. A stored function is similar but returns a value, so you can use it inside a SELECT.
Why teams use them
- Logic near the data: a ten-step job (check stock, create the order, reduce stock) runs inside the server, without sending each step over the network. Fewer round trips means less waiting.
- One place for a rule: every app that calls place_order gets the same checks, whether it is the website, the mobile app or a nightly script.
- Permissions: you can let a user EXECUTE a procedure without giving them UPDATE on the tables underneath. That is common in banks and older enterprise systems.
Why many teams avoid them
- Harder to test and debug than normal application code. Unit-test tools, debuggers and code review are weaker inside the database.
- Harder to version: the code lives in the database, so you must keep the CREATE scripts in Git and apply them with migrations, or production drifts from what the team thinks it runs.
- Vendor lock-in: PL/pgSQL, MySQL procedures and T-SQL are three different languages. Moving engines means rewriting them.
- Hidden logic: a developer reading the app may not know that a procedure also changes a second table.
A balanced rule: keep business rules in application code by default, and use procedures or functions for data-heavy steps that clearly benefit from running next to the data, or when your organisation requires access only through procedures.
PostgreSQL: a function in PL/pgSQL
PL/pgSQL is PostgreSQL's procedural language. The body is written between $$ marks, which are just quotes that let you use single quotes freely inside.
-- PostgreSQL only
CREATE OR REPLACE FUNCTION customer_total(p_customer INT)
RETURNS BIGINT
LANGUAGE plpgsql
AS $$
DECLARE
v_total BIGINT;
BEGIN
SELECT SUM(oi.qty * oi.price) INTO v_total
FROM orders o JOIN order_items oi ON oi.order_id = o.id
WHERE o.customer_id = p_customer AND o.status = 'Delivered';
RETURN COALESCE(v_total, 0);
END;
$$;
SELECT customer_total(1); -- 4028
SELECT customer_total(3); -- 0 (Priya's only order was cancelled)PostgreSQL 11 added real procedures with CREATE PROCEDURE. A procedure returns nothing by default, is run with CALL, and, unlike a function, may COMMIT inside its body.
-- PostgreSQL 11+ only
CREATE PROCEDURE mark_delivered(p_order INT)
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE orders SET status = 'Delivered' WHERE id = p_order;
END;
$$;
CALL mark_delivered(105);MySQL: DELIMITER and CREATE PROCEDURE
The body of a MySQL procedure contains semicolons, but the mysql command-line client normally treats a semicolon as "run this now". DELIMITER changes that marker for a moment, so the whole CREATE PROCEDURE is sent as one statement. OUT parameters hand a result back in a session variable such as @total.
-- MySQL only
DELIMITER //
CREATE PROCEDURE customer_total(IN p_customer INT, OUT p_total BIGINT)
BEGIN
SELECT COALESCE(SUM(oi.qty * oi.price), 0) INTO p_total
FROM orders o JOIN order_items oi ON oi.order_id = o.id
WHERE o.customer_id = p_customer AND o.status = 'Delivered';
END //
DELIMITER ;
CALL customer_total(1, @total);
SELECT @total; -- 4028SQL Server: T-SQL with @parameters and EXEC
-- SQL Server only
CREATE PROCEDURE dbo.CustomerTotal
@CustomerId INT
AS
BEGIN
SET NOCOUNT ON;
SELECT COALESCE(SUM(oi.qty * oi.price), 0) AS total
FROM orders AS o JOIN order_items AS oi ON oi.order_id = o.id
WHERE o.customer_id = @CustomerId AND o.status = 'Delivered';
END;
GO
EXEC dbo.CustomerTotal @CustomerId = 1; -- total: 4028SQLite: no stored procedures
SQLite runs inside your app, so it has no server to store procedures on. Apps keep that logic in application code. If you need a custom function inside SQL, the app registers a user-defined function on its connection, for example with Python's sqlite3 module:
import sqlite3
con = sqlite3.connect(':memory:')
con.create_function('gst', 1, lambda price: round(price * 1.18, 2))
print(con.execute('SELECT gst(899)').fetchone())
# Output: (1060.82,)Common mistakes
- Keeping procedure code only in the database. Fix: store every CREATE OR REPLACE script in Git and deploy it with migrations.
- Forgetting DELIMITER in the MySQL client, so the CREATE stops at the first semicolon with a syntax error. Fix: wrap it in DELIMITER // ... // DELIMITER ;.
- Building SQL by gluing strings inside a procedure (dynamic SQL). It is just as open to SQL injection as app code. Fix: pass values as parameters (USING in PL/pgSQL, sp_executesql in SQL Server).
- Returning NULL when there are no rows. Fix: wrap SUM in COALESCE(..., 0) when callers expect a number.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Stored procedures and functions · 1/8
Until now, every statement was sent to the database one by one. Today we store code inside the database itself.
Prefer a video by a teacher? Find free videos on “stored procedure vs function postgresql mysql sql server tutorial” on YouTube ↗
In real life: A bank's fund-transfer procedure
Many older banking systems expose money movement only through stored procedures. The teller app calls something like transfer_funds(from_account, to_account, amount); the procedure checks the balance, writes both entries and the audit row in one transaction, and the app's database user has no direct UPDATE rights on the accounts table. Newer systems often move this logic into a service layer instead, but the idea of "one controlled door to the data" is the same.
-- PostgreSQL-style sketch (simplified)
CALL transfer_funds(p_from => 1001, p_to => 2002, p_amount => 5000);
-- inside: check balance, debit, credit, insert audit row, all in one transactionPractice (0/4 done)
Read the PostgreSQL function customer_total shown in this topic. On the SHOP data, Priya Nair (customer 3) has one order, and it is Cancelled. What does SELECT customer_total(3); return?
A MySQL script starts with DELIMITER // and ends the procedure with END //. Why?
SQLite has no stored functions, so write the query a customer_total function would run, for every customer at once. Return id, name and delivered_total: the sum of qty * price over that customer's Delivered orders, and 0 for customers with none. One row per customer (6 rows).
On paper (or in a PostgreSQL sandbox if you have one), write a procedure place_order(p_customer, p_product, p_qty) for the SHOP tables. It must refuse the order when stock is too low, create the order, add the order line at the current price and reduce the stock.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- TriggersProcedures, triggers, SQL engines and NoSQL · same module
- MySQL vs PostgreSQL vs SQLite vs SQL ServerProcedures, triggers, SQL engines and NoSQL · same module
- NoSQL: documents, key-value, wide-column and graphProcedures, triggers, SQL engines and NoSQL · same module
- What is a database?Backend Development · Databases and SQL basics
- Numbers and mathsFunctions, CASE and NULL
- Reading query plansIndexes, query plans and views
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What is the main difference between a stored function and a stored procedure in PostgreSQL?
Answer: A function returns a value and is used inside queries; a procedure is run with CALL and may COMMIT inside
Functions are called in SELECT and return a value. Procedures (PostgreSQL 11+) are run with CALL and can control transactions.
Which is a real DISADVANTAGE of putting business logic in stored procedures?
Answer: Each engine has its own procedure language, so moving engines means rewriting them
PL/pgSQL, MySQL procedures and T-SQL differ: that is vendor lock-in. Testing and versioning are also harder.
How do you run a SQL Server procedure named dbo.CustomerTotal for customer 1?
Answer: EXEC dbo.CustomerTotal @CustomerId = 1;
SQL Server uses EXEC (or EXECUTE), with parameters that start with @.
Your app uses SQLite and needs a custom gst() function inside SQL. What do you do?
Answer: Register a user-defined function from the app, for example con.create_function in Python
SQLite has no stored procedures or CREATE FUNCTION; the host app registers functions on its connection.
Why can stored procedures help with security?
Answer: A user can be allowed to EXECUTE a procedure without direct UPDATE rights on the tables
Granting EXECUTE on a procedure gives "one controlled door". Dynamic SQL inside a procedure can still be injected.
How do you quote a column named order (a reserved word) in SQL Server?Related
Answer: [order]
SQL Server uses square brackets (and double quotes when QUOTED_IDENTIFIER is on). MySQL uses backticks; PostgreSQL and SQLite use double quotes.
Which of these is a SQL database?Related
Answer: PostgreSQL
PostgreSQL stores data in tables and uses SQL. MongoDB is a NoSQL document database.
From What is a database?
In SQLite, you write INSERT INTO tickets (customer_id, subject, status) VALUES (3, 'Hi', DEFAULT). What happens?Related
Answer: A syntax error: SQLite does not accept DEFAULT in VALUES
Leave the column out instead. PostgreSQL, MySQL and SQL Server do accept DEFAULT there.
From INSERT
In PostgreSQL, what does REFRESH MATERIALIZED VIEW CONCURRENTLY need?Related
Answer: A UNIQUE index on the materialized view
CONCURRENTLY compares old and new rows, which needs a unique index; readers are not blocked.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 5 more topics in “Procedures, triggers, SQL engines and NoSQL” before its exam: Triggers, MySQL vs PostgreSQL vs SQLite vs SQL Server, NoSQL: documents, key-value, wide-column and graph, MongoDB and Redis in practice, Choosing a database, and keeping it safe.
- Module 11 exam: procedures, triggers, engines and NoSQL · 13 questions · 25 min, without help. 6 topics of this module still to finish first.
- Then the next module, Interview questions and practice projects. 62 topics left in the course before the final exam.