MySQL CREATE PROCEDURE MySQL · Stored Procedure · CREATE

Creating Stored Procedures

Master MySQL CREATE PROCEDURE — the basic syntax, DELIMITER usage, returning result sets, SELECT INTO for variable assignment, the CALL statement, DROP PROCEDURE IF EXISTS for safe deployment, and the full mysql CLI workflow. Covers procedures with no parameters and procedures with local variables.

The CREATE PROCEDURE statement defines a new stored procedure. The basic form:

DELIMITER $$ CREATE PROCEDURE sp_name(parameter_list) BEGIN statements; END $$ DELIMITER ;

Key points:

  • sp_name — the name you'll use to CALL the procedure. It must be unique within the database.
  • (parameter_list) — zero or more parameters separated by commas. Use () for no parameters. Each parameter has an optional mode (IN default, or OUT/INOUT), a name, and a data type.
  • BEGIN ... END — the procedure body. Can contain anything: SELECTs, DML, control flow, transactions, cursor loops.
  • DELIMITER $$ — temporarily switches the statement terminator from ; to $$ so the inner semicolons don't end the CREATE early. DELIMITER ; at the end restores it.
📌 GUI clients usually handle DELIMITER for you. MySQL Workbench, DBeaver, HeidiSQL, and similar tools detect the procedure boundaries automatically — you can omit the DELIMITER dance entirely there. It's only the bare mysql CLI and script files run with mysql < script.sql that need it.

The examples on this page use a familiar employees table. Run this once if it's not already loaded:

MySQL — Setup
CREATE DATABASE IF NOT EXISTS gowtham;
USE gowtham;

DROP TABLE IF EXISTS employees;

CREATE TABLE employees (
    employee_id     INT UNSIGNED PRIMARY KEY,
    first_name      VARCHAR(20),
    last_name       VARCHAR(25)    NOT NULL,
    salary          DECIMAL(10, 2) NOT NULL,
    department_id   INT
);

INSERT INTO employees VALUES
    (100, 'Steven',    'King',      24000.00,  9),
    (101, 'Neena',     'Kochhar',   17000.00,  9),
    (102, 'Lex',       'De Haan',   17000.00,  9),
    (103, 'Alexander', 'Hunold',     9000.00,  6),
    (104, 'Bruce',     'Ernst',      6000.00,  6),
    (105, 'David',     'Austin',     4800.00,  6),
    (106, 'Valli',     'Pataballa',  4800.00,  6),
    (107, 'Diana',     'Lorentz',    4200.00,  6),
    (108, 'Nancy',     'Greenberg', 12000.00, 10),
    (109, 'Daniel',    'Faviet',     9000.00, 10);

Verify with a quick SELECT:

MySQL — Verify
SELECT employee_id, first_name, last_name, salary, department_id
FROM   employees
ORDER  BY first_name;
OUTPUT
mysql — employees
mysql>SELECT employee_id, first_name, last_name, salary, department_id FROM employees ORDER BY first_name;
+-------------+------------+-----------+----------+---------------+
| employee_id | first_name | last_name | salary | department_id |
+-------------+------------+-----------+----------+---------------+
| 103 | Alexander | Hunold | 9000.00 | 6 |
| 104 | Bruce | Ernst | 6000.00 | 6 |
| 109 | Daniel | Faviet | 9000.00 | 10 |
| 105 | David | Austin | 4800.00 | 6 |
| 107 | Diana | Lorentz | 4200.00 | 6 |
| 102 | Lex | De Haan | 17000.00 | 9 |
| 108 | Nancy | Greenberg | 12000.00 | 10 |
| 101 | Neena | Kochhar | 17000.00 | 9 |
| 100 | Steven | King | 24000.00 | 9 |
| 106 | Valli | Pataballa | 4800.00 | 6 |
+-------------+------------+-----------+----------+---------------+
10 rows in set (0.00 sec)
Example 1 — A Simple Procedure with No Parameters

Wrap that SELECT in a procedure that any application can call:

MySQL — Create and call
DROP PROCEDURE IF EXISTS GetEmployees;

DELIMITER $$

CREATE PROCEDURE GetEmployees()
    READS SQL DATA
    COMMENT 'Return all employees ordered by first name'
BEGIN
    SELECT employee_id, first_name, last_name, salary, department_id
    FROM   employees
    ORDER  BY first_name;
END$$

DELIMITER ;

-- Run it
CALL GetEmployees();
OUTPUT
mysql — GetEmployees
mysql>CREATE PROCEDURE GetEmployees() ... END$$
Query OK, 0 rows affected (0.02 sec)
mysql>CALL GetEmployees();
+-------------+------------+-----------+----------+---------------+
| employee_id | first_name | last_name | salary | department_id |
+-------------+------------+-----------+----------+---------------+
| 103 | Alexander | Hunold | 9000.00 | 6 |
| 104 | Bruce | Ernst | 6000.00 | 6 |
| ... (8 more rows ordered by first_name) |
| 106 | Valli | Pataballa | 4800.00 | 6 |
+-------------+------------+-----------+----------+---------------+
10 rows in set (0.00 sec)

Notice the procedure returns a full result set just like a regular SELECT. That's procedures' big difference from functions: procedures can return result sets, functions can't.

Example 2 — A Procedure with a Local Variable

Count the employees earning at least 17,000, store the count in a local variable, then return it:

MySQL — DECLARE + SELECT INTO
DROP PROCEDURE IF EXISTS GetTotalEmp;

DELIMITER $$

CREATE PROCEDURE GetTotalEmp()
    READS SQL DATA
BEGIN
    DECLARE total_emp INT DEFAULT 0;

    SELECT COUNT(*)
    INTO   total_emp
    FROM   employees
    WHERE  salary >= 17000;

    SELECT total_emp;
END$$

DELIMITER ;

CALL GetTotalEmp();
OUTPUT
mysql — GetTotalEmp
mysql>CALL GetTotalEmp();
+-----------+
| total_emp |
+-----------+
| 3 |
+-----------+
1 row in set (0.00 sec)

Three employees — Steven King, Neena Kochhar, Lex De Haan — earn at least 17,000.

💡 SELECT INTO is how you assign a query result to a variable inside a procedure. It must return exactly one row — if the SELECT returns zero rows the variable stays NULL (and the NOT FOUND condition fires); if it returns multiple rows you get error 1172. Use LIMIT 1 or aggregate functions like COUNT / MAX / MIN to guarantee a single row.

Once created, run the procedure with CALL:

CALL procedure_name([args...]);
  • Use CALL proc() for procedures with no parameters — note the empty parentheses are required.
  • For procedures with parameters, supply literals or user variables (@x) in order. Local procedure variables can't be used here — only at the next level up.
  • Procedures that SELECT something send the result set straight back to the client.
  • Procedures with OUT / INOUT parameters need user variables (@total) so the caller can read them after the call.

DROP PROCEDURE removes a procedure from the database:

DROP PROCEDURE [IF EXISTS] procedure_name;

Always include IF EXISTS in deployment scripts — without it, dropping a non-existent procedure throws an error and aborts the script:

MySQL — Safe drop
DROP PROCEDURE IF EXISTS GetTotalEmp;
OUTPUT
mysql — drop procedure
mysql>DROP PROCEDURE IF EXISTS GetTotalEmp;
Query OK, 0 rows affected (0.01 sec)
⚠️ DROP PROCEDURE takes only the name, no parentheses. Writing DROP PROCEDURE GetTotalEmp(); is a syntax error. The original tutorial showed this — copy carefully.

If you're working in the command line, the full session looks like this:

OUTPUT
mysql — full workflow
vriddh@prod-01:~$mysql -u root -p
Enter password: ********
Welcome to the MySQL monitor. Commands end with ; or \g.
mysql>USE gowtham;
Database changed
mysql>DELIMITER $$
mysql>CREATE PROCEDURE GetTotalEmp()
->BEGIN
-> DECLARE total_emp INT DEFAULT 0;
-> SELECT COUNT(*) INTO total_emp FROM employees WHERE salary >= 17000;
-> SELECT total_emp;
->END$$
Query OK, 0 rows affected (0.02 sec)
mysql>DELIMITER ;
mysql>CALL GetTotalEmp();
+-----------+
| total_emp |
+-----------+
| 3 |
+-----------+
1 row in set (0.00 sec)
mysql>

Notice the prompt changes from mysql> to -> on continuation lines while you're typing a multi-line statement. The statement only executes when MySQL sees the current delimiter — that's why we need $$ at the end of END$$.

⚠️ Don't pass passwords on the command line. The original tutorial showed mysql -u root -pWelcome1 — passwords given that way appear in your shell history (~/.bash_history) and in the output of ps visible to other users on the system. Use -p alone (interactive prompt) or store credentials in ~/.my.cnf with permissions 0600.
  1. Always use DROP PROCEDURE IF EXISTS at the top of your CREATE script. Makes deployments idempotent.
  2. Declare characteristics like READS SQL DATA, MODIFIES SQL DATA, DETERMINISTIC. Self-documenting and helps the optimizer.
  3. Add a COMMENT describing what the procedure does. Visible in SHOW CREATE PROCEDURE later.
  4. Use descriptive names with consistent casing — get_employees or GetEmployees, not sp1. Pick a convention and stick to it.
  5. Default to SELECT INTO for single-row variable assignments. SET x = (SELECT ...) works too but is less idiomatic.
  6. Handle the no-rows case. A SELECT INTO that returns no rows raises NOT FOUND — declare a handler or check first.
  7. Use LIMIT 1 on SELECT INTO when the query might return multiple rows; otherwise you'll hit error 1172.
  • CREATE PROCEDURE name(params) BEGIN ... END defines a stored procedure. Wrap it in DELIMITER $$ / DELIMITER ; when running through the mysql CLI.
  • Procedures can return result sets via embedded SELECT statements — that's their big difference from stored functions.
  • SELECT col INTO var FROM ... assigns a query result to a local variable. Must return exactly one row.
  • Invoke with CALL procedure_name(args); — empty parentheses required even for no-parameter procedures.
  • DROP PROCEDURE IF EXISTS name; removes a procedure safely. Note: no parentheses on the name.
  • Add characteristics like READS SQL DATA, MODIFIES SQL DATA, DETERMINISTIC, COMMENT. They document the procedure and help the optimizer.