mysql> SELECT product_id,if(isnull(price)=0,'A','B') as p from products;
sábado, 28 de diciembre de 2024
testing null
lunes, 25 de noviembre de 2024
Prepare statement
-- Step 1: Prepare an INSERT statement to insert a user into the users table
PREPARE insert_stmt FROM 'INSERT INTO users (name, email) VALUES (?, ?)';
-- Step 2: Set values for placeholders
SET @name = 'John Doe', @email = 'john.doe@example.com';
-- Step 3: Execute the prepared INSERT statement with the provided values
EXECUTE insert_stmt USING @name, @email;
-- Step 4: Deallocate the prepared INSERT statement
DEALLOCATE PREPARE insert_stmt;
-- Step 5: Prepare a SELECT statement to retrieve user by ID
PREPARE select_stmt FROM 'SELECT id, name, email FROM users WHERE id = ?';
-- Step 6: Set the value for the ID of the user to retrieve
SET @id = 1;
-- Step 7: Execute the SELECT statement with the ID
EXECUTE select_stmt USING @id;
-- Step 8: Deallocate the prepared SELECT statement
DEALLOCATE PREPARE select_stmt;
lunes, 18 de noviembre de 2024
MySQL Transactions
In MySQL, transactions allow you to group multiple SQL statements into a single unit of work. This ensures that the changes made by those statements are executed together, and if something goes wrong, the transaction can be rolled back to maintain data integrity. Transactions are especially useful when you need to perform a series of operations that should either all succeed or all fail.
Basic Commands for MySQL Transactions
Start a Transaction
To start a transaction, you use theSTART TRANSACTION
orBEGIN
command.
To save all the changes made during the transaction, you use the
COMMIT
command. Once a transaction is committed, the changes become permanent.3 Rollback a Transaction
If something goes wrong during the transaction (e.g., a constraint violation), you can use the ROLLBACK
command to undo all the changes made since the transaction started.
ROLLBACK;
4 Savepoints
A savepoint allows you to create a point within a transaction to which you can roll back without affecting the entire transaction.
SAVEPOINT savepoint_name;
By default, MySQL runs in autocommit mode, meaning every SQL statement is treated as a transaction. If you want to disable autocommit for your session (so you can manually manage transactions), use the following command:
In the above example:
- We start a transaction with
START TRANSACTION
. - We perform some
INSERT
operations. - If everything is successful, we use
COMMIT
to save the changes to the database. - If an error occurs during the transaction (e.g., inserting invalid data), we could roll back the transaction with
ROLLBACK
to ensure the database stays in a consistent state.
Isolation Levels in MySQL
MySQL supports different transaction isolation levels, which control the visibility of changes made within a transaction to other transactions. The isolation level can be set as follows:
Here are the available isolation levels:
- READ UNCOMMITTED: Transactions can see uncommitted changes made by other transactions.
- READ COMMITTED: Transactions can only see committed changes from other transactions.
- REPEATABLE READ: Ensures that if a transaction reads a value, it will see the same value if it reads it again later. This is the default isolation level in MySQL.
- SERIALIZABLE: The highest isolation level. It ensures no other transactions can read, write, or even lock the same rows.
Example:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
Key Points to Remember
- Atomicity: A transaction ensures that all the changes are applied or none at all. If a transaction is rolled back, none of the operations will take effect.
- Consistency: Transactions bring the database from one valid state to another, ensuring integrity.
- Isolation: Transactions are isolated from each other. Depending on the isolation level, the changes in one transaction may or may not be visible to others.
- Durability: Once a transaction is committed, the changes are permanent, even if the system crashes.
Conclusion
Transactions in MySQL are essential for maintaining data integrity and consistency, especially in systems where multiple operations need to be executed together. By using START TRANSACTION
, COMMIT
, and ROLLBACK
, you can ensure that your database operations are reliable and robust.
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:
sqlALTER TABLE child_table
ADD FOREIGN KEY (child_column) REFERENCES parent_table(parent_column);
Example
sqlALTER 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.
sqlCREATE 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.
- ON DELETE SET NULL: Sets the foreign key to
NULL
in the child table when the referenced row in the parent table is deleted.
- 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
sqlCREATE 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
incustomers
is deleted, all relatedorders
will also be deleted (ON DELETE CASCADE
). - If
customer_id
incustomers
is updated, thecustomer_id
inorders
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
:
sqlALTER TABLE child_table
DROP FOREIGN KEY constraint_name;
Example
sqlALTER 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:
sqlSET foreign_key_checks = 0;
Enable foreign key checks:
sqlSET 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.
sqlCREATE 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
referencesorders
, and if an order is deleted, the relatedorder_items
entries are also deleted.product_id
referencesproducts
, and if a product is deleted,product_id
is set toNULL
inorder_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;";
Added a LEFT JOIN:
- The
balance
table is joined withLEFT JOIN
to ensure that even if there is no corresponding entry in thebalance
table, you still get results from the other tables.
- The
Selected
b.balance_creditos
:- This column is included in the
SELECT
statement to retrieve the credit balance for each client.
- This column is included in the
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;
CREATE PROCEDURE
: This statement is used to define a new stored procedure in MySQL.
GetLowestCost
: The name of the stored procedure being created.
(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.
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.
INTO LowestCost
: This stores the result of MIN(Cost)
into the LowestCost
variable, which is the OUT parameter.
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.
sqlDELIMITER //
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.
sqlCALL 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.
sqlDELIMITER //
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.
sqlSELECT square_number(4); -- This will return 16
You can also use the function in other SQL statements, like WHERE
or GROUP BY
clauses:
sqlSELECT * FROM my_table WHERE square_number(my_column) > 100;
Differences in Calling Stored Procedures and Functions
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:
sqlCALL add_numbers(10, 20, @output);
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:
sqlSELECT 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
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
orOUT
. Out-params are how MySQL returns values from stored procedures (see next section). Params areIN
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 theSELECT .. 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 IF
, LOOP
, 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: WHILE
, REPEAT
, 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 WHILE
, REPEAT
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.
LOOP
, ITERATE
and LEAVE
Finally let's look at LOOP
. Unlike REPEAT
and WHILE
, LOOP
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/