MySQL: Stored Procedures INOUT

by Mark Nielsen
Copyright Dec 2021


  1. IN requires there to be an input.
  2. OUT requires you to have output in the character type defined. HINT: you should force a conversation or possibly error out.
  3. INOUT requires and input and it prints an output.

For more information read:

  1. https://dev.mysql.com/doc/refman/8.0/en/create-procedure.html
  2. https://dev.mysql.com/doc/connector-net/en/connector-net-tutorials-stored-procedures.html
  3. https://www.sqlshack.com/learn-mysql-the-basics-of-mysql-stored-procedures/
  4. https://www.softwaretestinghelp.com/mysql-stored-procedure/
  5. https://www.mysqltutorial.org/mysql-error-handling-in-stored-procedures/
  6. https://dev.mysql.com/doc/refman/8.0/en/declare-handler.html
  7. https://dev.mysql.com/doc/refman/8.0/en/condition-handling.html
  8. https://www.w3resource.com/mysql/mysql-procedure.php#MLST

MySQL Stored Procedures

Create these stored procedure in mysql.

Remember these important tips. Lets take the variable VAR1. It can be confusing to remember when to use "@VAR1" or "VAR1" at times.

  1. Whenever you "read" or "select" the variable VAR1 you must use "@VAR1"". This applies to the mysql shell or in stored procedures.
  2. Whenever you set VAR1 in the mysql shells, you must use "@VAR1".
  3. Whenever you set, or declare, or use in proc_parameters (IN, OUT, INOUT) VAR1 in a stored procedure, you must use "VAR1".

Simple function.

  create database if not exists test_sp;
  use test_sp;
  drop procedure if exists n;
  drop procedure if exists sp_i;
  drop procedure if exists sp_iio;
  drop procedure if exists sp_ioi;
  drop procedure if exists sp_io;
  drop procedure if exists sp_errorcheck;

DELIMITER //
CREATE PROCEDURE n()
    BEGIN
  SELECT now();
    END //

DELIMITER ;

-- Simple procedure. It takes a number and adds 10. 

DELIMITER //
CREATE PROCEDURE sp_i(IN no1 int4)
  BEGIN
    select 'start', no1;
    select no1 + 10 into no1;
    select 'end', no1;
	 
  END //
DELIMITER ;


--  Simple function, adds one day to time.
drop procedure if exists sp_o;
DELIMITER //
  CREATE PROCEDURE sp_o(out VAR1 int, inout VAR2 int)
    BEGIN
    select 'start', VAR1, VAR2, @GLOBAL1;
    set VAR1 = @GLOBAL1;
    select 4 into VAR2;
    select 'end', VAR1, VAR2, @GLOBAL1;
    END //
DELIMITER ;

--  Simple procdure, adds two numbers together.

drop procedure if exists sp_iio;
DELIMITER //
  CREATE PROCEDURE sp_iio(in no1 int, In no2 int, OUT no3 int, OUT no4 int)
    BEGIN
    DECLARE s int DEFAULT 0;
    set s = no1 + no2;
    select no3 + 1  into no3;
    set no4 = 5;
    select 'done', s, no1, no2, no3, no4;
    END //
DELIMITER ;
      

-- another way
DELIMITER //
CREATE PROCEDURE sp_ioi(inout no1 int, In no2 int)
  BEGIN
  set no1 = no1 + no2;
  select 'done', no1, no2;
END //
DELIMITER ;

-- simple one inout variables

DELIMITER //
  CREATE procedure sp_io(inout no1 int)
    BEGIN
    declare temp int default 100;
    select no1 + temp into no1;
    select 'done', no1, temp; 
END //
DELIMITER ;


-- simple one inout variables, with error

DELIMITER //
  CREATE PROCEDURE sp_errorcheck(inout no1 int)
BEGIN
    IF no1 is NULL then set no1 = -1; END IF; 
    set no1 = 1000; 
    select 'done', no1;
    END //
DELIMITER ;

  call n();
  call sp_i(1);

  set @temp1 = 2000;
  set @temp2 = 2000;
  call sp_iio(1,1, @temp2, @temp2);
  select 'iio GOOD', @temp1, @temp2;

  set @no3 = 2000;
  set @no4 = 2000;
  call sp_iio(1,1, @no3, @no4);
  select 'iio BAD ', @no3, @no3;

  set @io = 3000;
  call sp_ioi(@io,1);
  select 'ioi', @io; 

  call sp_io(@io);
  select 'io', @io;

  call sp_errorcheck(@io);
  select 'ec', @io;

  call sp_errorcheck(@io);
  select 'ec', @io;

  set @GLOBAL1 = 3; 
  set @i1 = 1;
  set @i2 = 2;
  select "env", @i1, @i2;
  call sp_o(@i1, @i2);
  select "env", @i1, @i2;

  set @i1 = 1;
  select "env", @i1;
  call sp_i(@i1);
  select "env", @i1;
	  

IN example

IN variables can be static or environment variables. They are only used in the stored procedure. If any environment variable is used, it is not changed when the stored procedure ends.
MariaDB [test_sp]>   set @i1 = 1;
--------------
set @i1 = 1
--------------

Query OK, 0 rows affected (0.000 sec)

MariaDB [test_sp]>   select "env", @i1;
--------------
select "env", @i1
--------------

+-----+------+
| env | @i1  |
+-----+------+
| env |    1 |
+-----+------+
1 row in set (0.000 sec)

MariaDB [test_sp]>   call sp_i(@i1);
--------------
call sp_i(@i1)
--------------

+-------+------+
| start | no1  |
+-------+------+
| start |    1 |
+-------+------+
1 row in set (0.000 sec)

+-----+------+
| end | no1  |
+-----+------+
| end |   11 |
+-----+------+
1 row in set (0.000 sec)

Query OK, 1 row affected (0.000 sec)

MariaDB [test_sp]>   select "env", @i1;
--------------
select "env", @i1
--------------

+-----+------+
| env | @i1  |
+-----+------+
| env |    1 |
+-----+------+
1 row in set (0.000 sec)

MariaDB [test_sp]>

OUT and INOUT example

The important thing to remember about OUT defined variables are:
  1. Variables with "@" refer to global variables outside the stored procedure. Other variables inside the stored procedure have no "@".
  2. If the variable in the mysql environment exists, it is set to NULL. ### CHECK THIS.
  3. A variable must be submitted to an OUT defined variables.
  4. A variable submitted to OUT becomes NULL and NOT copied. Then the value can be set and exported to the MySQL environment.

For OUT, We see the @i1 variable is submitted as "1" to the stored procedure. We see the sp immediately makes it NULL. At the end it changes it to "3" and the environment variables @i1 becomes "3".

For INOUT, @i2 is submitted as "2". It does not change upon entering. It s changed to "4" and then the environment variable is changed.

MariaDB [test_sp]>   set @GLOBAL1 = 3;
--------------
set @GLOBAL1 = 3
--------------

Query OK, 0 rows affected (0.000 sec)

MariaDB [test_sp]>   set @i1 = 1;
--------------
set @i1 = 1
--------------

Query OK, 0 rows affected (0.000 sec)

MariaDB [test_sp]>   set @i2 = 2;
--------------
set @i2 = 2
--------------

Query OK, 0 rows affected (0.000 sec)

MariaDB [test_sp]>   select "env", @i1, @i2;
--------------
select "env", @i1, @i2
--------------

+-----+------+------+
| env | @i1  | @i2  |
+-----+------+------+
| env |    1 |    2 |
+-----+------+------+
1 row in set (0.000 sec)

MariaDB [test_sp]>   call sp_o(@i1, @i2);
--------------
call sp_o(@i1, @i2)
--------------

+-------+------+------+-------+----------+
| start | VAR1 | VAR2 | @VAR2 | @GLOBAL1 |
+-------+------+------+-------+----------+
| start | NULL |    2 | NULL  |        3 |
+-------+------+------+-------+----------+
1 row in set (0.000 sec)

+-----+------+------+-------+----------+
| end | VAR1 | VAR2 | @VAR2 | @GLOBAL1 |
+-----+------+------+-------+----------+
| end |    3 |    4 | NULL  |        3 |
+-----+------+------+-------+----------+
1 row in set (0.000 sec)

Query OK, 1 row affected (0.000 sec)

MariaDB [test_sp]>   select "env", @i1, @i2;
--------------
select "env", @i1, @i2
--------------

+-----+------+------+
| env | @i1  | @i2  |
+-----+------+------+
| env |    3 |    4 |
+-----+------+------+
1 row in set (0.000 sec)

MariaDB [test_sp]> 

Flow Control

Two different flow controls are used: "if" and "case".
drop procedure if exists sp_flow;
DELIMITER //
CREATE PROCEDURE sp_flow(IN n int4)
  BEGIN
    DECLARE s VARCHAr(20) default 'unknown';

    IF n > 10 THEN SET s = 'High'; end if;
    CASE 
      WHEN n = 10 THEN set s = 'nothing';
      WHEN n < 10 THEN set s = 'Low';
      ELSE begin end; 
    end case;
    select s;
  end;
//
delimiter ;
	       
call sp_flow(11);
call sp_flow(10);
call sp_flow(9);
call sp_flow("a");
  
  MariaDB [test_sp]> call sp_flow(11);
    +------+
    | s    |
    +------+
    | High |
    +------+
    1 row in set (0.000 sec)

    Query OK, 0 rows affected (0.000 sec)

    MariaDB [test_sp]> call sp_flow(10);
    +---------+
    | s       |
    +---------+
    | nothing |
    +---------+
    1 row in set (0.000 sec)

    Query OK, 0 rows affected (0.000 sec)

    MariaDB [test_sp]> call sp_flow(9);
    call sp_flow("a");
    +------+
    | s    |
    +------+
    | Low  |
    +------+
    1 row in set (0.000 sec)

    Query OK, 0 rows affected (0.000 sec)

    MariaDB [test_sp]> call sp_flow("a");
    ERROR 1366 (22007): Incorrect integer value: 'a' for column ``.``.`n` at row 1
    
  

Error trapping

drop procedure if exists sp_error;
DELIMITER //
CREATE PROCEDURE sp_error(IN n int4)
  BEGIN

  declare error1 int default 0;
  declare error2 int default 0;
  declare s1 text; 
  declare s2 text;
  declare n2 int default 0;

  set n2=n;

select "intitial", n, n2;
begin
  declare c1 cursor for select 1;
  DECLARE continue HANDLER FOR SQLEXCEPTION set error1 = 1;
  open c1;
  set n = n + 1;
  close c1;
end;

select "add 1", n, n2;

begin
  declare c2 cursor for select 1;
  DECLARE continue HANDLER FOR SQLEXCEPTION set error2 = 1;
  open c2;
  set n = @n + "a";
  close c2;
end;

select "add 'a'", n, n2;

  CASE
    WHEN error1 = 0 THEN set s1 = 'first exception not activated. Good';
    WHEN error1 = 1 THEN set s1 = 'first execption activated. Bad';
    ELSE begin end;
  end case;

  CASE
    WHEN error2 = 0 THEN set s2 = '2nd exception not activated. Bad';
    WHEN error2 = 1 THEN set s2 = '2nd exception activated. Good';
    ELSE begin end;
   end case;

  select n, n2, error1, error2, s1, s2; 
end ;
//
delimiter ;

call sp_error(1);

Notice how "n" becomes NULL when an error occurs. This is very very bad. The error trapping still works. But I would recommend:
  1. Check the final result.
  2. I an error occurs, restore "n" to a backup.
Those tasks are left to the reader.
--------------
call sp_error(1)
--------------

+----------+------+------+
| intitial | n    | n2   |
+----------+------+------+
| intitial |    1 |    1 |
+----------+------+------+
1 row in set (0.000 sec)

+-------+------+------+
| add 1 | n    | n2   |
+-------+------+------+
| add 1 |    2 |    1 |
+-------+------+------+
1 row in set (0.000 sec)

+---------+------+------+
| add 'a' | n    | n2   |
+---------+------+------+
| add 'a' | NULL |    1 |
+---------+------+------+
1 row in set (0.000 sec)

+------+------+--------+--------+-------------------------------------+-------------------------------+
| n    | n2   | error1 | error2 | s1                                  | s2                            |
+------+------+--------+--------+-------------------------------------+-------------------------------+
| NULL |    1 |      0 |      1 | first exception not activated. Good | 2nd exception activated. Good |
+------+------+--------+--------+-------------------------------------+------OA-------------------------+
1 row in set (0.001 sec)

Query OK, 0 rows affected (0.001 sec)

MariaDB [test_sp]>


Default values

  1. You can have default values with declared variables using DECLARE.
  2. You cannot have default values with variables made from IN, OUT, or INOUT. One option is to pass NULL and then have a NULL check in the stored procedure and set the value if it is NULL. NOTE: The variable will be NULL by default is not declared. Best to set it to NULL to make sure.