martes, 29 de octubre de 2024

MYSQL CASE

 mysql> select * from orders;

+----------+-----------+------------+------------+----------+

| order_id | client_id | product_id | order_date | quantity |

+----------+-----------+------------+------------+----------+

|        1 |         1 |          1 | 2023-05-01 |        2 |

|        2 |         1 |          2 | 2023-05-03 |        1 |

|        4 |         3 |          1 | 2023-07-20 |        1 |

+----------+-----------+------------+------------+----------+

3 rows in set (0.00 sec)


mysql> 

mysql> 

mysql> 

mysql> SELECT 

    ->     order_id,

    ->     client_id,

    ->     product_id,

    ->     order_date,

    ->     quantity,

    ->     CASE 

    ->         WHEN quantity = 1 THEN 'Single'

    ->         WHEN quantity BETWEEN 2 AND 5 THEN 'Small Order'

    ->         WHEN quantity BETWEEN 6 AND 10 THEN 'Medium Order'

    ->         ELSE 'Large Order'

    ->     END AS order_category

    -> FROM 

    ->     orders;

+----------+-----------+------------+------------+----------+----------------+

| order_id | client_id | product_id | order_date | quantity | order_category |

+----------+-----------+------------+------------+----------+----------------+

|        1 |         1 |          1 | 2023-05-01 |        2 | Small Order    |

|        2 |         1 |          2 | 2023-05-03 |        1 | Single         |

|        4 |         3 |          1 | 2023-07-20 |        1 | Single         |

+----------+-----------+------------+------------+----------+----------------+

3 rows in set (0.00 sec)


mysql> 


domingo, 27 de octubre de 2024

Foreign Key

 

1. What is a Foreign Key?

A FOREIGN KEY in SQL is a constraint that creates a relationship between two tables. It links a column or group of columns in one table (child table) to a primary key or unique key in another table (parent table). This ensures referential integrity, meaning that any value in the foreign key column must exist in the primary key column of the parent table.

2. Creating Foreign Keys

Foreign keys can be defined:

  • During table creation with the CREATE TABLE statement.
  • After table creation with the ALTER TABLE statement.

2.1. Defining a Foreign Key in CREATE TABLE

Here’s the syntax for adding a foreign key while creating a table:

CREATE TABLE child_table (

    child_column datatype,

    ...

    FOREIGN KEY (child_column) REFERENCES parent_table(parent_column)

);

CREATE TABLE customers (

    customer_id INT PRIMARY KEY,

    name VARCHAR(100)

);

Example

CREATE TABLE orders (

    order_id INT PRIMARY KEY,

    order_date DATE,

    customer_id INT,

    FOREIGN KEY (customer_id) REFERENCES customers(customer_id)

);

In this example, customer_id in the orders table is a foreign key that references customer_id in the customers table.

2.2. Adding a Foreign Key with ALTER TABLE

To add a foreign key after creating a table, use the ALTER TABLE statement:

sql
ALTER TABLE child_table ADD FOREIGN KEY (child_column) REFERENCES parent_table(parent_column);

Example

sql
ALTER TABLE orders ADD FOREIGN KEY (customer_id) REFERENCES customers(customer_id);

3. Naming Foreign Key Constraints

You can give a foreign key constraint a specific name using the CONSTRAINT keyword. This makes it easier to reference and manage the constraint later.

sql
CREATE TABLE orders ( order_id INT PRIMARY KEY, order_date DATE, customer_id INT, CONSTRAINT fk_customer FOREIGN KEY (customer_id) REFERENCES customers(customer_id) );

In this example, the foreign key is named fk_customer.

4. Cascade Options for Foreign Keys

Foreign key constraints can define actions to take when a referenced row in the parent table is updated or deleted. Common cascade options include:

  • ON DELETE CASCADE: Deletes all rows in the child table that reference the deleted row in the parent table.
  • ALTER TABLE orders ADD FOREIGN KEY (client_id) REFERENCES clients(client_id) ON DELETE CASCADE;

  • ON UPDATE CASCADE: Updates the foreign key in the child table if the referenced key in the parent table is updated.
ALTER TABLE orders 
ADD FOREIGN KEY (client_id) REFERENCES clients(client_id) 
ON UPDATE CASCADE;

  • ON DELETE SET NULL: Sets the foreign key to NULL in the child table when the referenced row in the parent table is deleted.
ALTER TABLE orders ADD FOREIGN KEY (client_id) REFERENCES clients(client_id) ON DELETE SET NULL;
  • ON DELETE RESTRICT: Prevents the deletion of a row in the parent table if it is referenced in the child table.
  • ALTER TABLE orders ADD FOREIGN KEY (client_id) REFERENCES clients(client_id) ON DELETE RESTRICT;

Example with Cascade Options

sql
CREATE TABLE orders ( order_id INT PRIMARY KEY, order_date DATE, customer_id INT, CONSTRAINT fk_customer FOREIGN KEY (customer_id) REFERENCES customers(customer_id) ON DELETE CASCADE ON UPDATE CASCADE );

In this example:

  • If a customer_id in customers is deleted, all related orders will also be deleted (ON DELETE CASCADE).
  • If customer_id in customers is updated, the customer_id in orders is also updated (ON UPDATE CASCADE).

5. Managing Foreign Keys

Managing foreign keys includes dropping, disabling, and checking for any constraint violations.

5.1 Dropping a Foreign Key

To remove a foreign key constraint, use the DROP command with ALTER TABLE:

sql
ALTER TABLE child_table DROP FOREIGN KEY constraint_name;

Example

sql
ALTER TABLE orders DROP FOREIGN KEY fk_customer;

5.2 Disabling and Enabling Foreign Key Checks

In some databases (like MySQL), you can temporarily disable foreign key checks, which may be helpful for bulk inserts or data migrations.

  • Disable foreign key checks:

    sql
    SET foreign_key_checks = 0;
  • Enable foreign key checks:

    sql
    SET foreign_key_checks = 1;

6. Foreign Key Best Practices

  • Use Descriptive Names: Name constraints for easier management.
  • Avoid Cascading Deletes in critical tables to prevent accidental data loss.
  • Ensure Data Consistency: Insert data in the parent table before inserting in the child table to avoid foreign key violations.
  • Indexes on Foreign Keys: In many databases, it’s recommended to index foreign key columns for better query performance.

7. Common Errors and Troubleshooting

  • Error: "Cannot add or update a child row: a foreign key constraint fails."

    • Cause: This occurs if you attempt to add a foreign key value in the child table that doesn’t exist in the parent table.
    • Solution: Ensure that the referenced key exists in the parent table.
  • Error: "Cannot delete or update a parent row: a foreign key constraint fails."

    • Cause: This occurs if you attempt to delete or update a row in the parent table that has dependent rows in the child table.
    • Solution: Use ON DELETE CASCADE or manually delete the dependent rows first.

8. Advanced Example: Multiple Foreign Keys

You can create tables with multiple foreign keys, referencing different tables.

sql
CREATE TABLE order_items ( item_id INT PRIMARY KEY, order_id INT, product_id INT, quantity INT, FOREIGN KEY (order_id) REFERENCES orders(order_id) ON DELETE CASCADE, FOREIGN KEY (product_id) REFERENCES products(product_id) ON DELETE SET NULL );

In this example:

  • order_id references orders, and if an order is deleted, the related order_items entries are also deleted.
  • product_id references products, and if a product is deleted, product_id is set to NULL in order_items.

Get the list of tables in your database:

1) SHOW TABLES;


2) SHOW CREATE TABLE table_name;



ALTER TABLE group_members ADD CONSTRAINT fk_group_id FOREIGN KEY (group_id) REFERENCES call_groups(group_id) ON DELETE CASCADE ON UPDATE CASCADE;


Summary
  • Foreign keys maintain relationships and referential integrity between tables.
  • Cascade options (ON DELETE, ON UPDATE) allow flexibility in handling parent-child table relationships.
  • Naming constraints and indexing foreign key columns can improve readability and performance.
  • Disable foreign key checks temporarily when performing bulk operations if necessary.

With this guide, you should be well-equipped to define and manage foreign keys effectively in SQL databases!



sábado, 26 de octubre de 2024

INNER JOINS

 mysql> select * from sales;

+----+--------------+-------------+

| id | product_name | sale_amount |

+----+--------------+-------------+

|  1 | Product A    |      100.50 |

|  2 | Product B    |       75.25 |

|  3 | Product A    |      120.75 |

|  4 | Product C    |       50.00 |

|  5 | Product B    |       90.80 |

|  6 | Product R    |      300.00 |

+----+--------------+-------------+

6 rows in set (0.00 sec)


mysql> select * from clients;

+-----------+-------------+-------------------+

| client_id | client_name | registration_date |

+-----------+-------------+-------------------+

|         1 | Alice       | 2023-01-10        |

|         2 | Bob         | 2023-02-15        |

|         3 | Charlie     | 2023-03-20        |

|         4 | Ambiorix    | 2024-10-06        |

|         5 | Many        | 2024-10-11        |

+-----------+-------------+-------------------+

5 rows in set (0.00 sec)


mysql> select c.client_name,c.client_id,s.id,s.product_name from clients as c INNER JOIN sales as s on s.id=c.client_id;

+-------------+-----------+----+--------------+

| client_name | client_id | id | product_name |

+-------------+-----------+----+--------------+

| Alice       |         1 |  1 | Product A    |

| Bob         |         2 |  2 | Product B    |

| Charlie     |         3 |  3 | Product A    |

| Ambiorix    |         4 |  4 | Product C    |

| Many        |         5 |  5 | Product B    |

+-------------+-----------+----+--------------+

5 rows in set (0.00 sec)


mysql> select c.client_name,c.client_id,s.id,s.product_name from clients as c LEFT JOIN sales as s on s.id=c.client_id;

+-------------+-----------+------+--------------+

| client_name | client_id | id   | product_name |

+-------------+-----------+------+--------------+

| Alice       |         1 |    1 | Product A    |

| Bob         |         2 |    2 | Product B    |

| Charlie     |         3 |    3 | Product A    |

| Ambiorix    |         4 |    4 | Product C    |

| Many        |         5 |    5 | Product B    |

+-------------+-----------+------+--------------+

5 rows in set (0.00 sec)


mysql> select c.client_name,c.client_id,s.id,s.product_name from clients as c RIGHT JOIN sales as s on s.id=c.client_id;

+-------------+-----------+----+--------------+

| client_name | client_id | id | product_name |

+-------------+-----------+----+--------------+

| Alice       |         1 |  1 | Product A    |

| Bob         |         2 |  2 | Product B    |

| Charlie     |         3 |  3 | Product A    |

| Ambiorix    |         4 |  4 | Product C    |

| Many        |         5 |  5 | Product B    |

| NULL        |      NULL |  6 | Product R    |

+-------------+-----------+----+--------------+


sábado, 19 de octubre de 2024

Alter table

https://dev.mysql.com/doc/refman/8.4/en/alter-table.html

 https://dev.mysql.com/doc/refman/8.4/en/alter-table-examples.html

lunes, 7 de octubre de 2024

inner and left join 3 tables

 $query_ivr = "

    SELECT

        c.cliente_id,

        c.cliente_nombre,

        c.cliente_telefono,

        c.cliente_blacklist_status,

        SUM(co.compra_precio) AS total_compra,

        ivr.ivr_num_src,

        ivr.ivr_num_dst,

        ivr.ivr_caller_channel,

        ivr.ivr_call_status,

        ivr.ivr_calldate,

        b.balance_creditos

    FROM

        clientes AS c

    INNER JOIN

        compras AS co ON c.cliente_id = co.compra_cliente_id

    INNER JOIN

        ivr_calls AS ivr ON ivr.ivr_num_src = c.cliente_telefono AND ivr.ivr_call_status = 1

    LEFT JOIN

        balance AS b ON b.balance_cliente = c.cliente_id

    GROUP BY

        c.cliente_id,

        c.cliente_nombre,

        c.cliente_telefono,

        c.cliente_blacklist_status,

        ivr.ivr_num_src,

        ivr.ivr_num_dst,

        ivr.ivr_caller_channel,

        ivr.ivr_call_status,

        ivr.ivr_calldate,

        b.balance_creditos;";


  1. Added a LEFT JOIN:

    • The balance table is joined with LEFT JOIN to ensure that even if there is no corresponding entry in the balance table, you still get results from the other tables.
  2. Selected b.balance_creditos:

    • This column is included in the SELECT statement to retrieve the credit balance for each client.

Explanation:

  • Using a LEFT JOIN allows you to fetch client information even if they do not have any associated balance records.

domingo, 6 de octubre de 2024

triggers example

 DELIMITER //


CREATE TRIGGER ProductSellPriceUpdateCheck 

    AFTER UPDATE  

    ON Products FOR EACH ROW  

BEGIN

IF NEW.SellPrice <= NEW.BuyPrice THEN

INSERT INTO Notifications(Notification,DateTime) 

VALUES(CONCAT(NEW.ProductID,' was updated with a SellPrice of ', NEW.SellPrice,' which is the same or less than the BuyPrice'), NOW()); 

    END IF;

END //




DELIMITER //


CREATE TRIGGER ProductSellPriceInsertCheck 

    AFTER INSERT  

    ON Products FOR EACH ROW  

BEGIN

IF NEW.SellPrice <= NEW.BuyPrice THEN

INSERT INTO Notifications(Notification,DateTime) 

VALUES(CONCAT('A SellPrice same or less than the BuyPrice was inserted for ProductID ', NEW.ProductID), NOW()); 

    END IF;

END //



DELIMITER //


CREATE TRIGGER NotifyProductDelete 

    AFTER DELETE   

    ON Products FOR EACH ROW   

INSERT INTO Notifications(Notification, DateTime) 

    VALUES(CONCAT('The product with a ProductID ', OLD.ProductID,' was deleted'), NOW()); 

END //

DELIMITER ;

sábado, 5 de octubre de 2024

store procedures

 1. Create a Stored Procedure or Function

First, create your stored procedure or function in MySQL. For example, to create a stored procedure:


DELIMITER // CREATE PROCEDURE my_procedure(IN input_param INT) BEGIN SELECT input_param; END // DELIMITER ;

This procedure, my_procedure, will remain in the database system after you restart the MySQL server.

2. Check if the Procedure/Function Exists

After creating the procedure or function, you can verify its existence in MySQL by querying the information_schema:


SHOW PROCEDURE STATUS WHERE Db = 'your_database_name';

Or for functions:



SHOW FUNCTION STATUS WHERE Db = 'your_database_name';

Call procedure
CALL my_procedure(10);

--------

Here's how you can create both a stored procedure and a function in MySQL.

1. Creating a Stored Procedure

Let's create a stored procedure that takes two integer parameters, adds them together, and returns the result as an output parameter.


DELIMITER // CREATE PROCEDURE add_numbers(IN num1 INT, IN num2 INT, OUT result INT) BEGIN SET result = num1 + num2; END // DELIMITER ;
  • IN num1 and IN num2 are the input parameters.
  • OUT result is the output parameter, which will store the result of the addition.
  • The procedure uses the SET statement to calculate the sum and assign it to the result.

How to Call the Stored Procedure:


CALL add_numbers(10, 5, @output); SELECT @output; -- This will display the result (15)

2. Creating a Function

Let's create a function that also takes two integers, adds them, and returns the sum.


DELIMITER // CREATE FUNCTION add_two_numbers(num1 INT, num2 INT) RETURNS INT DETERMINISTIC BEGIN RETURN num1 + num2; END // DELIMITER ;
  • num1 and num2 are the input parameters.
  • RETURNS INT specifies that the function will return an integer value.
  • DETERMINISTIC indicates that the function always returns the same result for the same input parameters.

How to Call the Function:


SELECT add_two_numbers(10, 5) AS result; -- This will return 15

Key Differences:

  • Stored Procedure: More versatile, can handle multiple result sets and output parameters.
  • Function: Returns a single value and can be used directly in SQL queries (like SELECT, WHERE, etc.).

Both the stored procedure and function will remain available on the MySQL server even after it restarts.



Explanation of the Code:


CREATE PROCEDURE GetLowestCost (OUT LowestCost DECIMAL(6,2)) SELECT MIN(Cost) INTO LowestCost FROM Orders;
  1. CREATE PROCEDURE: This statement is used to define a new stored procedure in MySQL.

  2. GetLowestCost: The name of the stored procedure being created.

  3. (OUT LowestCost DECIMAL(6,2)):

    • This specifies that LowestCost is an OUT parameter.
    • The OUT keyword means that this parameter is used to return a value from the procedure.
    • DECIMAL(6,2) defines the data type of LowestCost as a decimal number with up to 6 digits, 2 of which are after the decimal point.
  4. SELECT MIN(Cost): This part of the procedure selects the minimum value of the Cost column from the Orders table. The MIN() function returns the smallest value in a column.

  5. INTO LowestCost: This stores the result of MIN(Cost) into the LowestCost variable, which is the OUT parameter.

  6. FROM Orders: This indicates that the data is being selected from the Orders table.

What This Stored Procedure Does:

  • When you call this stored procedure, it will find the lowest cost from the Orders table and return that value in the LowestCost variable.

Example Usage:

To call the stored procedure and capture the output:


CALL GetLowestCost(@lowest); SELECT @lowest; -- This will display the lowest cost from the Orders table.

In this example, @lowest will hold the value returned by the LowestCost OUT parameter.



Stored Procedure vs. Function:

  • Stored Procedures:

    • Can perform actions like INSERT, UPDATE, DELETE, and SELECT.
    • Can return multiple result sets.
    • Can have IN, OUT, and INOUT parameters.
    • Cannot be used directly in SQL statements like a SELECT.
  • Functions:

    • Primarily used to compute and return a value.
    • Can only return a single value.
    • Can be used in SQL statements (like SELECT, WHERE, etc.).
    • Cannot perform actions like INSERT, UPDATE, or DELETE (only SELECT).

1. Using Stored Procedures

a) Creating a Stored Procedure

Here’s an example of a stored procedure that takes two numbers as input, adds them, and returns the result.

sql
DELIMITER // CREATE PROCEDURE add_numbers(IN num1 INT, IN num2 INT, OUT result INT) BEGIN SET result = num1 + num2; END // DELIMITER ;
  • IN: Input parameter (you pass values to it).
  • OUT: Output parameter (it returns a value to you).

b) Calling the Stored Procedure

Once the stored procedure is created, you can call it using the CALL statement.

sql
CALL add_numbers(5, 10, @output); SELECT @output; -- This will return 15

Here, @output captures the result of the stored procedure.


2. Using Functions

a) Creating a Function

A function takes input, performs a calculation, and returns a value. Let’s create a function to calculate the square of a number.

sql
DELIMITER // CREATE FUNCTION square_number(num INT) RETURNS INT DETERMINISTIC BEGIN RETURN num * num; END // DELIMITER ;
  • DETERMINISTIC means the function will always return the same result for the same input.
  • The function returns an integer value.

b) Using the Function

You can use functions directly in SQL queries, unlike stored procedures.

sql
SELECT square_number(4); -- This will return 16

You can also use the function in other SQL statements, like WHERE or GROUP BY clauses:

sql
SELECT * FROM my_table WHERE square_number(my_column) > 100;

Differences in Calling Stored Procedures and Functions

  1. Stored Procedure:

    • Called using the CALL statement.
    • It can accept multiple input (IN) and output (OUT) parameters.
    • It cannot be used directly in a SELECT statement.

    Example:

    sql
    CALL add_numbers(10, 20, @output);
  2. Function:

    • Called like a built-in MySQL function (e.g., SUM, AVG).
    • Can return only one value.
    • Can be used in SELECT, WHERE, and ORDER BY statements.

    Example:

    sql
    SELECT square_number(5);

Real-Life Example

a) Stored Procedure Example:

Let’s say you want to create a stored procedure that fetches the details of a customer by their ID.




DELIMITER // CREATE PROCEDURE get_customer_details(IN customer_id INT) BEGIN SELECT * FROM customers WHERE id = customer_id; END // DELIMITER ;

You can call this procedure like this:




CALL get_customer_details(123);

This would return all the details of the customer whose ID is 123.

b) Function Example:

Let’s create a function that returns the total amount a customer has spent based on their orders.


DELIMITER // CREATE FUNCTION total_spent(customer_id INT) RETURNS DECIMAL(10,2) DETERMINISTIC BEGIN DECLARE total DECIMAL(10,2); SELECT SUM(order_total) INTO total FROM orders WHERE customer_id = customer_id; RETURN total; END // DELIMITER ;

Now you can use this function in a query:

SELECT total_spent(123) AS total FROM dual;

This will return the total amount spent by the customer with ID 123.


---------------


Creating a stored procedure in a database that uses only input variables is quite straightforward. Below, I'll guide you through the process using MySQL as an example. The steps are similar in other relational databases, but the syntax might differ slightly.

Example: MySQL Stored Procedure

1. Create a Table

First, let's create a simple table to work with:

sql

viernes, 4 de octubre de 2024

writing-mysql-procedures/

MySQL Stored Procedures: How and why with examples

14 min read

MySQL allows you to define stored procedures on your databases that can execute arbitrary SQL statements whenever you invoke them, including updating data in one or more tables. This tutorial will teach you how to use stored procedures and give you some ideas about what they're good for.

What's a stored procedure?

A stored procedure is a set of SQL statements that you invoke with the CALL keyword. They can accept parameters that change how the procedure operates. Here's a simple classic example using MySQL stored procedure syntax: defining a stored procedure that inserts values into multiple tables.

mysql> CREATE TABLE employees (
    id bigint primary key auto_increment, 
    first_name varchar(100), 
    last_name varchar(100));

mysql> CREATE TABLE birthdays (
    emp_id bigint,
    birthday date,
    constraint foreign key (emp_id) references employees(id)
);

mysql> delimiter \\

mysql> CREATE procedure new_employee(
    first char(100), 
    last char(100), 
    birthday date)
BEGIN
    INSERT INTO employees (first_name, last_name) VALUES (first, last);
    SET @id = (SELECT last_insert_id());
    INSERT INTO birthdays (emp_id, birthday) VALUES (@id, birthday);
END;
//

mysql> delimiter ;

mysql> call new_employee("tim", "sehn", "1980-02-03");
Query OK, 1 row affected (0.02 sec)

mysql> mysql> SELECT * FROM birthdays;
+--------+------------+
| emp_id | birthday   |
+--------+------------+
|      1 | 1980-02-03 |
+--------+------------+
1 row in set (0.00 sec)

mysql> SELECT * FROM employees;
+----+------------+-----------+
| id | first_name | last_name |
+----+------------+-----------+
|  1 | tim        | sehn      |
+----+------------+-----------+
1 row in set (0.00 sec)

This procedure inserts a row into both the employees and birthdays tables, using the generated employee ID from the first INSERT to do the second.

Let's break down the definition of this stored procedure and see what each part does.

Defining a stored procedure

The procedure we defined above looks like this. I'm commenting every element in the definition to make it clear what it does.

delimiter // -- since our stored procedure contains multiple statement separated with ";",
             -- we need to tell the MySQL shell not to try to execute the statement when
             -- it encounters a ";" like it normally would. Instead, it should wait for "//" 

CREATE procedure new_employee( -- the name of our procedure is new_employee
    first char(100), -- the first param is called "first" and is a character string
    last char(100),  -- the second param is called "last" and is a character string
    birthday date) -- the third param is called "birthday" and is a date
BEGIN -- since our procedure body has multiple statements, we wrap them in a BEGIN .. END block
    INSERT INTO employees (first_name, last_name) VALUES (first, last); -- insert into employees table
    SET @id = (SELECT last_insert_id()); -- assign the auto-generated ID from the INSERT to a variable
    INSERT INTO birthdays (emp_id, birthday) VALUES (@id, birthday); -- INSERT into the second table
END; -- end of the BEGIN .. END block
//

delimiter ; -- now that we're done defining our procedure, change the delimiter back to ";"

So a procedure has a number of parts that must be declared in the order above. They are:

  • The name of the procedure. Procedure names must be unique in a schema.
  • A list of parameters (could be none). Each parameter must have:
    • IN or OUT. Out-params are how MySQL returns values from stored procedures (see next section). Params are IN by default.
    • A name, which must be unique in the parameter list
    • A type, which MySQL will convert automatically if possible (like how the string "1980-02-03" got converted to a date)
  • BEGIN starts the body of the procedure, like a curly brace in most programming languages. This is optional if your procedure has only a single statement
  • One or more statements for the body of the procedure, each ending in ;
  • END ends the body of the procedure

Also note the use of DELIMITER. This isn't a SQL statement, it's a command to the MySQL shell. These are only necessary if you're using the MySQL shell to define your procedure. If you're using another tool, like a GUI workbench, it will already correctly interpret the ; characters in the body of the procedure.

OUT parameters

MySQL stored procedures don't use the return keyword to return a value to the caller like other programming languages. Instead, you declare parameters as OUT rather than IN, and then they're set in the procedure body. Here's a simple example.

delimiter //

CREATE PROCEDURE birthday_count(
    IN bday date,
    OUT count int)
BEGIN
    SET count = (SELECT count(*) FROM birthdays WHERE birthday = bday);
END
//

delimiter ;

-- couple other birthdays
CALL new_employee('aaron', 'son', '1985-01-10');
CALL new_employee('brian', 'hendriks', '1985-01-10');

SET @count = 0;

call birthday_count('1985-01-10', @count);
Query OK, 0 rows affected (0.00 sec)

SELECT @count;
+--------+
| @count |
+--------+
|      2 |
+--------+
1 row in set (0.00 sec)

Note that count is used two different ways:

  • Inside the stored procedure, it's used as a parameter that gets a value assigned to it with a SET statement.. You could also use the SELECT .. INTO syntax for this purpose.
  • Outside the stored procedure, the variable @count is initialized before the call, passed into the procedure, and then selected to get the result.

Generally speaking, if your procedure needs to return a value, it probably makes more sense to write it as a function instead, but there are situations where OUT parameters are important.

Variables

Stored procedures can set session variables (@var), or global variables (@@var), or local variables. The latter are defined only in the scope of an execution of a call of the procedure. Which one you use depends on what you're trying to accomplish. It's considered best practice to use the DECLARE syntax to declare local variables for the procedure, and then to return any values necessary back to the calling scope with OUT params.

Declaring and using variables looks like this:

CREATE PROCEDURE combined_birthday_count(
    IN bday1 date,
    IN bday2 date,
    OUT count int)
BEGIN
    DECLARE count1, count2 int;
    SET count1 = (SELECT count(*) FROM birthdays WHERE birthday = bday1);
    SET count2 = (SELECT count(*) FROM birthdays WHERE birthday = bday2);
    SET count = count1 + count2;
END

mysql> call combined_birthday_count('1980-02-03', '1985-01-10', @count);
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @count;
+--------+
| @count |
+--------+
|      3 |
+--------+

Here we are declaring two local variables count1 and count2, both of the int type. The syntax is very similar to parameters, with the added wrinkle that you can define more than one variable with the same type in the same statement.

Note that DECLARE statements must occur at the beginning of the procedure, before other kinds of statements, similar to older versions of C that require all variables to be declared before other statements in a function.

Control flow statements

Just like any other programming language, MySQL stored procedures support conditional logic by means of a set of control flow statements like IFLOOP, etc. Using these statements makes it possible to implement any logic you can imagine in your stored procedures.

IF

IF statements execute one of N statements depending on a condition. Multiple ELSEIF cases can follow the IF, and an optional ELSE can end the block. The THEN keyword that follows IF and ELSEIF begins a block of statements similar to BEGIN. Finally, the entire IF statement must be terminated by END IF. Let's see an example.

CREATE PROCEDURE birthday_message(
    bday date,
    OUT message varchar(100))
BEGIN
    DECLARE count int;
    DECLARE name varchar(100);
    SET count = (SELECT count(*) FROM birthdays WHERE birthday = bday);
    IF count = 0 THEN
        SET message = "Nobody has this birthday";
    ELSEIF count = 1 THEN
        SET name = (SELECT concat(first_name, " ", last_name)
            FROM employees join birthdays
            on emp_id = id
            WHERE birthday = bday);
        SET message = (SELECT concat("It's ", name, "'s birthday"));
    ELSE
        SET message = "More than one employee has this birthday";
    END IF;
END

call birthday_message (now(), @message);
Query OK, 0 rows affected, 1 warning (0.00 sec)

SELECT @message;
+--------------------------+
| @message                 |
+--------------------------+
| Nobody has this birthday |
+--------------------------+
1 row in set (0.00 sec)

call birthday_message ('1980-02-03', @message);
Query OK, 0 rows affected (0.01 sec)

SELECT @message;
+--------------------------+
| @message                 |
+--------------------------+
| It's tim sehn's birthday |
+--------------------------+
1 row in set (0.00 sec)

call birthday_message ('1985-01-10', @message);
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @message;
+------------------------------------------+
| @message                                 |
+------------------------------------------+
| More than one employee has this birthday |
+------------------------------------------+
1 row in set (0.00 sec)

CASE

CASE statements are another way of expressing conditional logic, when the same expression is evaluated for every logical branch, similar to the switch statement found in many programming languages. We can implement the same procedure above using case statements instead. Note that you end a CASE block with a END CASE statement.

CREATE PROCEDURE birthday_message(
    bday date,
    OUT message varchar(100))
BEGIN
    DECLARE count int;
    DECLARE name varchar(100);
    SET count = (SELECT count(*) FROM birthdays WHERE birthday = bday);
    CASE count
        WHEN 0 THEN
            SET message = "Nobody has this birthday";
        WHEN 1 THEN
            SET name = (SELECT concat(first_name, " ", last_name)
                FROM employees join birthdays
                on emp_id = id
                WHERE birthday = bday);
            SET message = (SELECT concat("It's ", name, "'s birthday"));
        ELSE
            SET message = "More than one employee has this birthday";
    END CASE;
END

Compared to using IF and ELSEIF, the CASE version makes it clearer that the choice of execution path depends on the value of the count variable.

Loop constructs

To repeat the same set of statements more than once, use a loop construct. MySQL provides three different loop constructs to choose from: WHILEREPEAT, and LOOP. Which one you use is mostly a matter of personal preference and how easy you find them to read for a particular situation.

For this example, we'll write a procedure that computes the Nth fibonnaci number and assigns it to an out parameter.

WHILE

First let's look at the WHILE keyword:

CREATE PROCEDURE fib(n int, out answer int)
BEGIN 
    DECLARE i int default 2;
    DECLARE p, q int default 1;
    SET answer = 1;
    WHILE i < n DO
        SET answer = p + q;
        SET p = q;
        SET q = answer;
        SET i = i + 1;
    END WHILE;
END;

(Let's make sure it works first.)

SET @answer = 1; call fib(6, @answer); SELECT @answer;
Query OK, 0 rows affected (0.00 sec)

+---------+
| @answer |
+---------+
|       8 |
+---------+

SET @answer = 1; call fib(7, @answer); SELECT @answer;
Query OK, 0 rows affected (0.00 sec)

+---------+
| @answer |
+---------+
|      13 |
+---------+

Note the use of the DEFAULT keyword on the DECLARE statements, which we hadn't used before. This assigns an initial value to the variable. Unlike other languages, MySQL integer variables do not default to 0 or any other value, but instead are initialized to NULL by default (which you don't want for calculation).

REPEAT

Now that our procedure works, let's write a version using REPEAT:

CREATE PROCEDURE fib(n int, out answer int)
BEGIN
    DECLARE i int default 1;
    DECLARE p int default 0;
    DECLARE q int default 1;
    SET answer = 1;
    REPEAT
        SET answer = p + q;
        SET p = q;
        SET q = answer;
        SET i = i + 1;
    UNTIL i >= n END REPEAT;
END;

Unlike WHILEREPEAT loops check the loop condition at the end of the loop, not the beginning. So they always execute the body of the loop at least once. Because of this, we needed to adjust our initial variable values for the n=1 and n=2 cases.

LOOPITERATE and LEAVE

Finally let's look at LOOP. Unlike REPEAT and WHILELOOP has no built-in exit condition, making it very easy to write an infinite loop. You have to use a label and code an explicit LEAVE statement to exit the loop. Here's the same procedure again, with the loop1 label applied:

CREATE PROCEDURE fib(n int, out answer int)
BEGIN
    DECLARE i int default 2;
    DECLARE p, q int default 1;
    SET answer = 1;
    loop1: LOOP
        IF i >= n THEN
            LEAVE loop1;
        END IF;
        SET answer = p + q;
        SET p = q;
        SET q = answer;
        SET i = i + 1;
    END LOOP loop1;
END;

Note that the loop1 label occurs both before the LOOP as well as at the end of it. The LEAVE statement terminates the loop.

In addition to the LEAVE keyword, it can sometimes be useful or clearer to use the ITERATE keyword. Here's a version of the loop that uses ITERATE:

CREATE PROCEDURE fib(n int, out answer int)
BEGIN
    DECLARE i int default 2;
    DECLARE p, q int default 1;
    SET answer = 1;
    loop1: LOOP
        IF i < n THEN
            SET answer = p + q;
            SET p = q;
            SET q = answer;
            SET i = i + 1;
            ITERATE loop1;
        END IF;
        LEAVE loop1;
    END LOOP loop1;
END;

Which way you choose to write a LOOP depends on what you're trying to do and the cleanest way to express it. You can also use ITERATE to conditionally skip parts of a loop under certain circumstances.

Exception handling

MySQL stored procedures support terminating execution with an error with a special SIGNAL keyword. You can also define your own named error conditions to use in stored procedures you write for readability. This can be used to halt the operation of a transaction and cause it to be rolled back, just like any native MySQL error. This can be useful when implementing complex data integrity checks.

The syntax to signal an error is unfamiliar but straightforward. This example comes straight out of the MySQL docs:

CREATE PROCEDURE p (pval INT)
BEGIN
  DECLARE specialty CONDITION FOR SQLSTATE '45000';
  IF pval = 0 THEN
    SIGNAL SQLSTATE '01000';
  ELSEIF pval = 1 THEN
    SIGNAL SQLSTATE '45000'
      SET MESSAGE_TEXT = 'An error occurred';
  ELSEIF pval = 2 THEN
    SIGNAL specialty
      SET MESSAGE_TEXT = 'An error occurred';
  ELSE
    SIGNAL SQLSTATE '01000'
      SET MESSAGE_TEXT = 'A warning occurred', MYSQL_ERRNO = 1000;
    SIGNAL SQLSTATE '45000'
      SET MESSAGE_TEXT = 'An error occurred', MYSQL_ERRNO = 1001;
  END IF;
END;

What are stored procedures used for?

Now that you understand how to define a stored procedures in MySQL, you might be asking yourself why you would want to. What are they good for? What do people use these things to do? Stored procedures are a general tool you can use to do all sorts of things, but here are some of the more common ones in our experience.

System maintenance

Sometimes it's convenient or desirable to encode business logic directly in the database, in the same place where the data is defined and stored. This approach means that the logic to maintain key database properties isn't spread out somewhere else in a distributed system (like a batch job), but directly in the database itself.

For example, consider an online storefront, where you store an order history for every customer. It might make sense to archive details about these orders, like tracking information and return requests, once they get old enough that it's unlikely they'll be useful any longer. Archiving old data means a smaller database, which means lower storage bills for your backups and possibly faster execution time on queries.

Here's a procedure that deletes order information older than a cutoff provided:

CREATE PROCEDURE archive_order_history(cutoff date)
BEGIN
    -- sanity check: don't proceed if we are given a date in the last year
    IF datediff(now(), date) < 365 THEN
        SIGNAL SQLSTATE '45000'
            SET MESSAGE_TEXT = 'attempted to archive recent order history';
    END IF;
    -- clear out any orders older than the cutoff, lots of tables to delete from here
    DELETE FROM order_updates WHERE update_date < cutoff;
    DELETE FROM order_history WHERE order_date < cutoff;
    DELETE FROM order_feedback WHERE feedack_date < cutoff;
    DELETE FROM support_requests WHERE order_date < cutoff;
END

Note that this procedure encapsulates the logic of deleting old data from many tables into a single procedure. As the database schema continues to evolve, new logic can be added here as necessary.

Procedures like this one tend to be run on a periodic basis, and you can schedule MySQL to run them for you automatically:

CREATE EVENT archive_order_history_event
ON SCHEDULE
    EVERY 1 DAY
    STARTS TIMESTAMP(CURDATE(), '02:00:00')
DO
BEGIN
    CALL archive_order_history(DATE_SUB(CURDATE(), INTERVAL 1 YEAR));
END;

You can also alter this procedure to perform work in small chunks, if the data is truly massive and there's any concern about interfering with other write throughput. Just run the DELETE with a LIMIT clause in a loop:

CREATE PROCEDURE archive_order_history(cutoff date)
BEGIN
    DECLARE count int default 1;
    WHILE COUNT > 0 DO
        DELETE FROM order_history WHERE order_date < cutoff LIMIT 100;
        -- See how many are left to delete
        -- Make sure to stop our table scan once we find a matching row
        SET count = (SELECT count(*) FROM (
            SELECT order_id FROM order_history WHERE order_date < cutoff LIMIT 1));
        COMMIT; -- commit our batch of 100 deletes
    END WHILE;
END

Time-based compliance

Some regulations, such as GDPR, require online businesses to remove customer data upon request or face heavy fines. Usually there is a grace period of some number of days for the customer to change their mind, during which time you don't want to delete their data if possible. Here again it may make sense to implement a stored procedure to encapsulate the logic of what a deletion request does, then run it on a timer.

First, create a table that logs when a customer requests data removal:

CREATE TABLE removal_requests(
    customer_id bigint primary key,
    request_date date,
    removal_date date
);

Now define a procedure to delete all personally identifiable data associated with a customer.

CREATE PROCEDURE delete_customer(id_to_remove int)
BEGIN
    -- clear out any customer information
    UPDATE cust_info SET first_name = "REDACTED", last_name = "REDACTED" 
        WHERE cust_id = id_to_remove;
    DELETE FROM customer_addresses WHERE cust_id = id_to_remove;
    DELETE FROM reviews WHERE cust_id = id_to_remove;
    UPDATE order_history SET delivery_instructions = "REDACTED" 
        WHERE cust_id = id_to_remove;
END

Notice that this procedure completely encapsulates the logic required to comply with the regulation. We can't just delete the customer record because that would cause referential constraint violations, or cascading deletion, in other tables storing information we're required to keep for financial reasons. Instead, we perform a mix of deletions where possible and updates to redact personally identifiable information where it's not.

Finally we schedule an event to delete customers whose time is up:

CREATE EVENT customer_removal_event
ON SCHEDULE
    EVERY 1 DAY
    STARTS TIMESTAMP(CURDATE(), '03:00:00')
DO
BEGIN
    DECLARE cust_id int;
    cust_loop: REPEAT
        SET cust_id = (SELECT customer_id FROM removal_requests 
            WHERE removal_date >= now() limit 1);
        IF cust_id IS NULL
            LEAVE cust_loop;
        CALL delete_customer(cust_id);
    UNTIL cust_id IS NULL END REPEAT cust_loop; -- redundant end condition on this loop
END;

Data integrity checks and complex updates

For some complex table relationships, it might be desirable to define the logic of inserting or updating the data in the tables in a procedure. This has the advantage of allowing you to execute arbitrarily complex logic for validation, things that can't be expressed with simple FOREIGN KEY or CHECK constraints.

Consider a library system with tables for books, holds, and checkouts.

CREATE TABLE books(
    isbn varchar(100) primary key,
    title varchar(255),
    ...
);

CREATE TABLE holds(
    isbn varchar(100),
    patron_id bigint,
    unique key (isbn, patron_id)
);

CREATE TABLE checkouts(
    isbn varchar(100),
    patron_id bigint
    date date,
    KEY (isbn, patron_id)
);

We can define a procedure to handle all the business logic we want to consider when checking out a book:

CREATE PROCEDURE checkout(
    checkout_isbn varchar(100),
    borrower_patron_id bigint)
BEGIN
    DECLARE current_checkouts int;
    SET current_checkouts = (SELECT COUNT(*) FROM checkouts WHERE patron_id = borrower_patron_id);
    IF current_checkouts > 20 THEN
        SIGNAL SQLSTATE '45000'
            SET MESSAGE_TEXT = 'Too many books checked out';
    END;

    DELETE FROM holds WHERE isbn = checkout_isbn and patron_id = borrower_patron_id;
    INSERT INTO checkouts VALUES (checkout_isbn, borrower_patron_id, now());
END;

It's also possible (and more common) to define this sort of logic in application code. But the advantage of defining logic at the database level is that multiple applications can use this stored procedure without needing to each understand the business logic around checking out a book. The logic can be updated in one place when policy like the max number of books per patron changes.

In modern architectures, this role is more typically filled by a web service than a database, but there are still a surprising number of older systems out there where this isn't an option, where multiple legacy applications still connect directly to and update a shared database. For these systems, it's useful to have this kind of trick up our sleeves.

Advanced topic: cursors

The examples above all limit results from SQL queries in procedures to a single row, and usually a single value. There's a lot you can do with this pattern, but for some situations you really need to examine multiple rows in a loop to answer some question. For those situations, MySQL provides cursors. For obvious reasons, it's a lot more difficult to write correct stored procedures that require cursors than ones that don't.

This simple example is straight out of the MySQL docs.

CREATE PROCEDURE curdemo()
BEGIN
  DECLARE done INT DEFAULT FALSE;
  DECLARE a CHAR(16);
  DECLARE b, c INT;
  DECLARE cur1 CURSOR FOR SELECT id,data FROM test.t1;
  DECLARE cur2 CURSOR FOR SELECT i FROM test.t2;
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

  OPEN cur1;
  OPEN cur2;

  read_loop: LOOP
    FETCH cur1 INTO a, b;
    FETCH cur2 INTO c;
    IF done THEN
      LEAVE read_loop;
    END IF;
    IF b < c THEN
      INSERT INTO test.t3 VALUES (a,b);
    ELSE
      INSERT INTO test.t3 VALUES (a,c);
    END IF;
  END LOOP;

  CLOSE cur1;
  CLOSE cur2;
END;

 

https://www.dolthub.com/blog/2024-01-17-writing-mysql-procedures/