Skip to content

Expert: design, speed and engines · Procedures, triggers, SQL engines and NoSQL · topic 52 of 62 · 35 min

Stored procedures and functions

Explanation

A stored procedure or stored function is a named block of SQL code saved inside the database and run by name. It keeps logic next to the data, saves round trips and lets you grant "run this" instead of "touch these tables". The price is code that is harder to test, version and move to another engine. PostgreSQL, MySQL and SQL Server have them; SQLite does not.

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.

Think of it like this: Think of a bank counter. Instead of letting customers walk into the vault (the tables), the bank gives them a form: "withdraw ₹X from account Y". The clerk behind the counter follows fixed steps. A stored procedure is that form and clerk: callers can only do what the procedure allows.

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.

sql
-- 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)
On the SHOP data. COALESCE turns "no delivered orders" (NULL) into 0.

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.

sql
-- 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.

sql
-- 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;   -- 4028

SQL Server: T-SQL with @parameters and EXEC

sql
-- 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: 4028
Parameters start with @. SET NOCOUNT ON stops the "1 row affected" messages. GO is a batch separator used by SQL Server tools, not part of T-SQL.

SQLite: 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:

python
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,)
The function exists only for this connection, written in Python, not stored in the database file.

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

Stored procedures and functions

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.

sql
-- 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 transaction
Named arguments with => are PostgreSQL syntax.

Practice (0/4 done)

Task 1 · Question

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?

Task 2 · Question

A MySQL script starts with DELIMITER // and ends the procedure with END //. Why?

Task 3 · Code · SQL

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).

Task 4 · Practice

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

Question 1

1.What is the main difference between a stored function and a stored procedure in PostgreSQL?

Question 2

2.Which is a real DISADVANTAGE of putting business logic in stored procedures?

Question 3

3.How do you run a SQL Server procedure named dbo.CustomerTotal for customer 1?

Question 4

4.Your app uses SQLite and needs a custom gst() function inside SQL. What do you do?

Question 5

5.Why can stored procedures help with security?

Read more (free)

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.

From MySQL vs PostgreSQL vs SQLite vs SQL Server

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.

From Views and materialized views

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: Triggers →

Next steps

  1. 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.
  2. Module 11 exam: procedures, triggers, engines and NoSQL · 13 questions · 25 min, without help. 6 topics of this module still to finish first.
  3. Then the next module, Interview questions and practice projects. 62 topics left in the course before the final exam.

← Previous: Views and materialized views