DELIMITER $$
CREATE PROCEDURE procedure_name ([IN|OUT|INOUT] param1 datatype, ...)
BEGIN
-- SQL statements
END$$
DELIMITER ;
Example:
DELIMITER $$
CREATE PROCEDURE GetAllUsers()
BEGIN
SELECT * FROM users;
END$$
DELIMITER ;
CALL procedure_name([param1, ...]);
Example:
CALL GetAllUsers();
IN
(default): Input onlyOUT
: Output onlyINOUT
: Input and output
Example:
CREATE PROCEDURE AddNumbers(IN a INT, IN b INT, OUT sum INT)
BEGIN
SET sum = a + b;
END;
DECLARE var_name datatype [DEFAULT value];
SET var_name = value;
- IF:
IF condition THEN -- statements ELSEIF condition THEN -- statements ELSE -- statements END IF;
- CASE:
CASE var WHEN value1 THEN -- statements WHEN value2 THEN -- statements ELSE -- statements END CASE;
- LOOP/WHILE/REPEAT:
[label:] LOOP ... END LOOP [label]; [label:] WHILE condition DO ... END WHILE [label]; [label:] REPEAT ... UNTIL condition END REPEAT [label];
DECLARE cursor_name CURSOR FOR SELECT_statement;
OPEN cursor_name;
FETCH cursor_name INTO var1, var2, ...;
CLOSE cursor_name;
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
-- statements;
DROP PROCEDURE [IF EXISTS] procedure_name;
DELIMITER $$
CREATE FUNCTION function_name(param1 datatype, ...)
RETURNS datatype
[DETERMINISTIC | NOT DETERMINISTIC]
[READS SQL DATA | MODIFIES SQL DATA | NO SQL | CONTAINS SQL]
BEGIN
-- SQL statements
RETURN value;
END$$
DELIMITER ;
Example:
DELIMITER $$
CREATE FUNCTION AddNumbers(a INT, b INT)
RETURNS INT
DETERMINISTIC
BEGIN
RETURN a + b;
END$$
DELIMITER ;
SELECT function_name([param1, ...]);
Example:
SELECT AddNumbers(2, 3);
- Functions must return a value using
RETURN
. - Functions cannot modify data (no
INSERT
,UPDATE
,DELETE
). - Functions cannot use
CALL
,COMMIT
,ROLLBACK
, or dynamic SQL.
DROP FUNCTION [IF EXISTS] function_name;
-
List Procedures/Functions:
SHOW PROCEDURE STATUS [LIKE 'pattern']; SHOW FUNCTION STATUS [LIKE 'pattern'];
-
Show Create:
SHOW CREATE PROCEDURE procedure_name; SHOW CREATE FUNCTION function_name;
-
Change Delimiter:
UseDELIMITER $$
to avoid conflicts with;
inside procedure/function bodies.
DELIMITER $$
CREATE PROCEDURE MultiplyNumbers(IN a INT, IN b INT, OUT result INT)
BEGIN
SET result = a * b;
END$$
DELIMITER ;
CALL MultiplyNumbers(4, 5, @output);
SELECT @output;
DELIMITER $$
CREATE FUNCTION Square(x INT)
RETURNS INT
DETERMINISTIC
BEGIN
RETURN x * x;
END$$
DELIMITER ;
SELECT Square(6); -- Returns 36
Stored procedures in MySQL can return result sets (tables) directly using a SELECT
statement.
Example:
DELIMITER $$
CREATE PROCEDURE GetAllUsers()
BEGIN
SELECT * FROM users;
END$$
DELIMITER ;
Usage:
CALL GetAllUsers();
- The result of the
SELECT
inside the procedure is returned as a result set (table) to the caller. - You can have multiple
SELECT
statements to return multiple result sets.
MySQL UDFs (scalar functions) cannot return a result set (table).
They must return a single value (scalar, e.g., INT, VARCHAR, etc.).
- Views: If you want a reusable "table" result, use a VIEW.
- Stored Procedures: Use a stored procedure if you need to return a table.
- JSON: In MySQL 5.7+ you can return a JSON array from a function, but not a true table.
Example: Function Returning JSON
DELIMITER $$
CREATE FUNCTION GetUserEmails()
RETURNS JSON
DETERMINISTIC
BEGIN
RETURN (SELECT JSON_ARRAYAGG(email) FROM users);
END$$
DELIMITER ;
Usage:
SELECT GetUserEmails();
Feature | Stored Procedure | User Defined Function |
---|---|---|
Return Table (Result Set) | ✅ Yes (via SELECT) | ❌ No (only scalar) |
Return Scalar Value | ✅ Yes (via OUT param) | ✅ Yes (via RETURN) |
Use in SELECT clause | ❌ No | ✅ Yes |
- To return a table: Use a stored procedure with a
SELECT
statement. - To return a single value: Use a function.
- To use in a SELECT statement: Use a function (but only for scalar values).
If you need to return different columns or tables, you can use dynamic SQL in stored procedures:
DELIMITER $$
CREATE PROCEDURE GetTableRows(IN tbl_name VARCHAR(64))
BEGIN
SET @sql = CONCAT('SELECT * FROM ', tbl_name);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END$$
DELIMITER ;
Usage:
CALL GetTableRows('users');
In summary:
- Stored procedures can return tables (result sets) via
SELECT
. - Functions cannot return tables, only scalar values (or JSON as a workaround).
Tip:
Always use DELIMITER
when creating procedures/functions to avoid syntax errors.
This guide covers the two types of stored routines in MySQL and how to use them to return single values or entire result sets (tables).
- Stored Procedures: Pre-compiled SQL statements that can be executed by
CALL
ing them. They can haveIN
,OUT
, andINOUT
parameters and can return result sets to the client. - User-Defined Functions (UDFs): Pre-compiled routines that must return a single scalar value. They are used directly in SQL queries like built-in functions.
Feature | Stored Procedure | User-Defined Function |
---|---|---|
Invocation | CALL procedure_name(); |
SELECT function_name(); (Used in expressions) |
Return Value | No direct return value. Uses OUT /INOUT parameters or sends a result set to the client. |
Must return a single scalar value (e.g., INT, VARCHAR). |
Parameters | IN , OUT , INOUT |
Only IN parameters are supported. |
Usage | Can execute DML (INSERT, UPDATE, DELETE), DDL, transactions, and return result sets. | Primarily for calculations. Cannot perform DML. Cannot use transactions. |
Purpose | To perform complex business logic, transactions, or a series of actions. | To encapsulate a calculation or formula for reuse in queries. |
This is essential when creating routines from a command-line client. It changes the end-of-statement character from ;
to something else (commonly $$
or //
), allowing the routine's body, which contains semicolons, to be passed to the server as a single statement.
-- Change the delimiter
DELIMITER $$
CREATE PROCEDURE ...
BEGIN
-- Your code with semicolons here;
END$$
-- Change it back to the default
DELIMITER ;
CREATE PROCEDURE procedure_name ([parameter_mode] parameter_name data_type, ...)
[CHARACTERISTICS]
BEGIN
-- Variable declarations
-- SQL statements and logic
END
parameter_mode
:IN
: (Default) Pass a value into the procedure.OUT
: Pass a variable out of the procedure. Its initial value inside isNULL
.INOUT
: Pass a variable in and out. The procedure can read and modify it.
This procedure finds the number of employees in a department and returns the count via an OUT
parameter.
DELIMITER $$
CREATE PROCEDURE GetEmployeeCountByDept(
IN p_dept_id INT,
OUT p_employee_count INT
)
BEGIN
SELECT COUNT(*)
INTO p_employee_count
FROM employees
WHERE department_id = p_dept_id;
END$$
DELIMITER ;
You use the CALL
statement and pass session variables for OUT
and INOUT
parameters.
-- Declare a session variable to hold the output
SET @dept_id = 10;
SET @count = 0;
-- Call the procedure
CALL GetEmployeeCountByDept(@dept_id, @count);
-- See the result
SELECT @count AS 'Employee Count';
CREATE FUNCTION function_name (parameter_name data_type, ...)
RETURNS return_data_type
[CHARACTERISTICS]
BEGIN
-- Variable declarations
-- Logic (must have a RETURN statement)
END
RETURNS return_data_type
: This is mandatory.CHARACTERISTICS
: Common ones are:DETERMINISTIC
: Always produces the same result for the same inputs.NOT DETERMINISTIC
: (Default) Result can change even with the same inputs.READS SQL DATA
: The function reads data from tables but does not modify it.
This function takes a date of birth and calculates the person's current age.
DELIMITER $$
CREATE FUNCTION CalculateAge(
p_birth_date DATE
)
RETURNS INT
DETERMINISTIC
BEGIN
RETURN TIMESTAMPDIFF(YEAR, p_birth_date, CURDATE());
END$$
DELIMITER ;
Use it directly in SQL statements just like any built-in function.
-- Use it in a SELECT statement
SELECT first_name, last_name, CalculateAge(birth_date) AS age
FROM employees;
-- Use it in a WHERE clause
SELECT first_name, last_name
FROM employees
WHERE CalculateAge(birth_date) > 40;
A common requirement is to have a routine return a full table of data.
Key Point: MySQL functions cannot return a table. MySQL procedures can. This is the standard way to return a result set.
Here are the primary methods to achieve this.
Create a procedure that simply executes a SELECT
query. The client application that CALL
s the procedure will receive the rows as if it had run the query directly.
Example:
DELIMITER $$
CREATE PROCEDURE GetActiveUsers()
BEGIN
-- This SELECT statement's result is what gets "returned" to the caller
SELECT
user_id,
username,
email,
created_at
FROM
users
WHERE
is_active = 1
ORDER BY
username;
END$$
DELIMITER ;
How to Call:
CALL GetActiveUsers();
-- The client will receive a grid/table of the active users.
- Pros: Simple, clean, efficient, and the most idiomatic way to do this in MySQL.
- Cons: You cannot use the result directly in another SQL query (e.g.,
SELECT * FROM (CALL GetActiveUsers())
is invalid).
Use this when you need to generate a result set and then use it in subsequent queries within the same session.
Example:
DELIMITER $$
CREATE PROCEDURE GenerateSalesReport(IN p_min_total DECIMAL(10, 2))
BEGIN
-- Best practice: Drop the temp table if it already exists
DROP TEMPORARY TABLE IF EXISTS temp_sales_report;
-- Create the temporary table to hold the results
CREATE TEMPORARY TABLE temp_sales_report (
customer_id INT,
customer_name VARCHAR(255),
total_spent DECIMAL(10, 2)
);
-- Insert the complex query result into the temporary table
INSERT INTO temp_sales_report (customer_id, customer_name, total_spent)
SELECT c.id, c.name, SUM(o.amount) AS total
FROM customers c
JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name
HAVING total > p_min_total;
END$$
DELIMITER ;
How to Call (Two Steps):
-- Step 1: Call the procedure to create and populate the temporary table
CALL GenerateSalesReport(500.00);
-- Step 2: Now you can query the temporary table like a normal table
SELECT * FROM temp_sales_report ORDER BY total_spent DESC;
- Pros: The result is "composable" and can be used in
JOIN
s or other queries. - Cons: More complex; requires multiple statements from the client.
For applications and APIs, you can have a procedure return a single JSON
string (via an OUT
parameter) that contains your entire result set.
Example:
DELIMITER $$
CREATE PROCEDURE GetUsersAsJson(OUT p_users_json JSON)
BEGIN
SELECT
JSON_ARRAYAGG(
JSON_OBJECT(
'userId', user_id,
'username', username,
'email', email
)
)
INTO p_users_json
FROM users WHERE is_active = 1;
END$$
DELIMITER ;
How to Call:
CALL GetUsersAsJson(@my_users);
SELECT @my_users;
- Pros: Returns a single, clean data structure, great for web APIs.
- Cons: Result is not directly usable in other SQL queries; client must parse the JSON.
Goal | Recommended Method | Why? |
---|---|---|
Simple data retrieval for an application. | Method 1: Procedure with SELECT |
It's the simplest, most direct, and most efficient method. It's the standard MySQL way. |
Generate a dataset to JOIN or filter with other SQL. |
Method 2: TEMPORARY TABLE |
This is the only way to make the output "composable" within subsequent SQL statements. |
Feed a web service/API that prefers JSON. | Method 3: Return JSON |
Provides a clean, modern interface and decouples the client from the DB schema. |
Save a complex SELECT by name (no logic needed). |
Consider a VIEW instead. |
A VIEW is a virtual table and is often simpler if you don't need procedural logic. |
- Variables:
DECLARE my_var INT DEFAULT 0;
(Must be at the top of aBEGIN...END
block).SET my_var = 10;
SELECT column INTO my_var FROM table WHERE ...;
(Query must return 1 row).
- IF Statement:
IF condition THEN ... ELSEIF ... ELSE ... END IF;
- CASE Statement:
CASE ... WHEN ... THEN ... ELSE ... END CASE;
- Loops:
WHILE
,REPEAT
,LOOP
. UseLEAVE
to exit andITERATE
to skip to the next iteration. - Cursors: For iterating over multi-row result sets inside a procedure. The pattern is:
DECLARE CURSOR
,DECLARE HANDLER FOR NOT FOUND
,OPEN
,LOOP
withFETCH
, andCLOSE
. - Error Handling:
DECLARE {CONTINUE | EXIT} HANDLER FOR {SQLEXCEPTION | SQLSTATE '...'};
Action | Stored Procedure | User-Defined Function |
---|---|---|
Show Create Syntax | SHOW CREATE PROCEDURE procedure_name; |
SHOW CREATE FUNCTION function_name; |
List All Routines | SHOW PROCEDURE STATUS WHERE Db = 'db_name'; |
SHOW FUNCTION STATUS WHERE Db = 'db_name'; |
Drop (Delete) | DROP PROCEDURE [IF EXISTS] procedure_name; |
DROP FUNCTION [IF EXISTS] function_name; |