homework assignment 5 1. stored procedures (sps)comet.lehman.cuny.edu/jung/cmp420758/hw5.pdf · 1...

35
1 Homework Assignment 5 In this homework, you will practice how to create database objects such as stored procedures, stored functions, triggers, and events. This homework is a reference for your project phase 2. 1. Stored Procedures (SPs) Stored procedure is a piece of code (procedure) consisting of declarative and procedural SQL statements stored in the catalog of a database that can be activated by calling it from a program, a trigger, or another stored procedure. The following Figure shows how a stored procedure is processed. The left block represents the program from which the procedure is called, the middle block represents the database server, and the right side represents the database and its catalog. The process begins when the procedure is called from the program (step 1). The database server receives this call and finds the matching procedure in the catalog (step 2). Next, the procedure is executed (step 3). After execution, a code is returned indicating that the procedure was processed correctly (step 4). No communication takes place between the database server and the program during the execution of the procedure. Four types of stored programs Stored procedure: can be called from an application that has access to the database Stored function: can be called from SQL statement. A stored function works much like the MySQL built in functions Trigger: is executed in response to an INSERT, UPDATE, or DELETE statement on a specified table Event: is executed at a scheduled time Stored procedures and stored functions are often referred to as stored routines

Upload: truongdieu

Post on 07-May-2018

246 views

Category:

Documents


2 download

TRANSCRIPT

1

Homework Assignment 5

In this homework, you will practice how to create database objects such as stored procedures, stored

functions, triggers, and events. This homework is a reference for your project phase 2.

1. Stored Procedures (SPs)

Stored procedure is a piece of code (procedure) consisting of declarative and procedural SQL statements

stored in the catalog of a database that can be activated by calling it from a program, a trigger, or another

stored procedure.

The following Figure shows how a stored procedure is processed. The left block represents the program

from which the procedure is called, the middle block represents the database server, and the right side

represents the database and its catalog. The process begins when the procedure is called from the program

(step 1). The database server receives this call and finds the matching procedure in the catalog (step 2).

Next, the procedure is executed (step 3). After execution, a code is returned indicating that the procedure

was processed correctly (step 4). No communication takes place between the database server and the

program during the execution of the procedure.

Four types of stored programs

Stored procedure: can be called from an application that has access to the database

Stored function: can be called from SQL statement. A stored function works much like the MySQL

built in functions

Trigger: is executed in response to an INSERT, UPDATE, or DELETE statement on a specified table

Event: is executed at a scheduled time

Stored procedures and stored functions are often referred to as stored routines

2

Example: a script that creates and calls a stored procedure

USE ap;

DROP PROCEDURE IF EXISTS test;

-- Change statement delimiter from semicolon to double front slash

DELIMITER //

CREATE PROCEDURE test()

BEGIN

DECLARE sum_balance_due_var DECIMAL(9, 2);

SELECT SUM(invoice_total - payment_total - credit_total)

INTO sum_balance_due_var

FROM invoices

WHERE vendor_id = 95;

-- for testing, the vendor with an ID of 37 has a balance due

IF sum_balance_due_var > 0 THEN

SELECT CONCAT('Balance due: $', sum_balance_due_var) AS message;

ELSE

SELECT 'Balance paid in full' AS message;

END IF;

END//

-- Change statement delimiter from semicolon to double front slash

DELIMITER ;

CALL test();

The response from the system

SQL statements for controlling the flow of execution

MySQL provides statements that can be used within scripts to add functionality similar to that provided

by procedural programming languages.

IF...ELSEIF...ELSE // controls the flow of execution base

REPEAT...UNTIL...END REPEAT //repeats statements while a condition is true

3

DECLARE CURSOR F

DECLARE...HANDLER //defines a handler that’s executed when a stored program encounters an error

A SQL statement used within stored programs

SELECT: returns a result set to the calling program. Or retrieves data from the database and stores it so it

can be processed by the stored program.

Example: a stored procedure that displays a message

USE ap;

DROP PROCEDURE IF EXISTS test;

DELIMITER //

CREATE PROCEDURE test()

BEGIN

SELECT 'This is a test.' AS message;

END//

DELIMITER ;

CALL test();

How to declare and set variables

A variable stores a value that can change as the procedure executes. A variable must have a name that is

different from the names of any columns used in any SELECT statement within the stored program. To

distinguish a variable from a column, you can add a suffix like “_var” to the variable name.

The syntax for declaring a variable

DECLARE variable_name data_type [DEFAULT literal_value];

The syntax for setting a variable to a literal value or an expression

SET variable_name = literal_value_or_expression;

The syntax for setting a variable to a selected value

SELECT column_1[, column_2]... INTO variable_name_1[, variable_name_2]...

Example: A stored procedure that uses variables

USE ap;

DROP PROCEDURE IF EXISTS test;

DELIMITER //

4

CREATE PROCEDURE test()

BEGIN

DECLARE max_invoice_total DECIMAL(9,2);

DECLARE min_invoice_total DECIMAL(9,2);

DECLARE percent_difference DECIMAL(9,4);

DECLARE count_invoice_id INT;

DECLARE vendor_id_var INT;

SET vendor_id_var = 95;

SELECT MAX(invoice_total), MIN(invoice_total), COUNT(invoice_id)

INTO max_invoice_total, min_invoice_total, count_invoice_id

FROM invoices WHERE vendor_id = vendor_id_var;

SET percent_difference = (max_invoice_total - min_invoice_total) /

min_invoice_total * 100;

SELECT CONCAT('$', max_invoice_total) AS 'Maximum invoice',

CONCAT('$', min_invoice_total) AS 'Minimum invoice',

CONCAT('%', ROUND(percent_difference, 2)) AS 'Percent difference',

count_invoice_id AS 'Number of invoices';

END//

DELIMITER ;

CALL test();

The response from the system

How to code the IF statements

The syntax of the IF statement

IF boolean_expression THEN

statement_1; [statement_2;]...

[ELSEIF boolean_expression THEN

statement_1; [statement_2;]...]...

[ELSE statement_1; [statement_2;]...]

END IF;

You can nest an IF statement within another IF statement or within other SQL statements such as

statements for coding loops. You can also code parentheses around the Boolean expressions in an IF

statement.

5

(e.g., IF(first_invoice_due_date < NOW()) THEN … )

Example

USE ap;

DROP PROCEDURE IF EXISTS test;

DELIMITER //

CREATE PROCEDURE test()

BEGIN

DECLARE first_invoice_due_date DATE;

SELECT MIN(invoice_due_date)

INTO first_invoice_due_date

FROM invoices

WHERE invoice_total - payment_total - credit_total > 0;

IF first_invoice_due_date < NOW() THEN

SELECT 'Outstanding invoices overdue!';

ELSEIF first_invoice_due_date = SYSDATE() THEN

SELECT 'Outstanding invoices are due today!';

ELSE

SELECT 'No invoices are overdue.';

END IF;

-- the IF statement rewritten as a Searched CASE statement

/*

CASE

WHEN first_invoice_due_date < NOW() THEN

SELECT 'Outstanding invoices overdue!' AS Message;

WHEN first_invoice_due_date = NOW() THEN

SELECT 'Outstanding invoices are due today!' AS Message;

ELSE

SELECT 'No invoices are overdue.' AS Message;

END CASE;

*/

END//

DELIMITER ;

CALL test();

The response from the system

6

SELECT NOW(),CURDATE(),CURTIME(), SYSDATE();

CASE Statements

The syntax of the simple CASE statement

CASE expression

WHEN expression_value_1 THEN

statement_1; [statement_2;]...

[WHEN expression_value_2

THEN statement_1; [statement_2;]...]...

[ELSE

statement_1; [statement_2;]...]

END CASE;

The syntax of the searched CASE statement

CASE

WHEN boolean_expression THEN

statement_1; [statement_2;]...

[WHEN boolean_expression THEN

statement_1; [statement_2;]...]...

[ELSE

statement_1; [statement_2;]...]

END CASE;

You can use a simple CASE statement or a searched CASE statement to execute one or more statements

depending on a value that’s returned by an expression.

Example:

USE ap;

DROP PROCEDURE IF EXISTS test;

DELIMITER //

CREATE PROCEDURE test()

7

BEGIN

DECLARE terms_id_var INT;

SELECT terms_id INTO terms_id_var

FROM invoices WHERE invoice_id = 4;

CASE terms_id_var

WHEN 1 THEN

SELECT 'Net due 10 days' AS Terms;

WHEN 2 THEN

SELECT 'Net due 20 days' AS Terms;

WHEN 3 THEN

SELECT 'Net due 30 days' AS Terms;

ELSE

SELECT 'Net due more than 30 days' AS Terms;

END CASE;

-- rewritten as a Searched CASE statement

/*

CASE

WHEN terms_id_var = 1 THEN

SELECT 'Net due 10 days' AS Terms;

WHEN terms_id_var = 2 THEN

SELECT 'Net due 20 days' AS Terms;

WHEN terms_id_var = 3 THEN

SELECT 'Net due 30 days' AS Terms;

ELSE

SELECT 'Net due more than 30 days' AS Terms;

END CASE;

*/

END//

DELIMITER ;

CALL test();

The response

8

Coding Loops

While Loop Syntax

WHILE boolean_expression DO

statement_1; [statement_2;]...

END WHILE;

Three types of loops: a WHILE loop, a REPEAT loop, and a simple loop. You can use LEAVE statement

to go to the end of a loop. You can use the ITERATE statement to go to the beginning of a loop.

See the following example:

USE ap;

DROP PROCEDURE IF EXISTS test;

DELIMITER //

CREATE PROCEDURE test()

BEGIN

DECLARE i INT DEFAULT 1;

DECLARE s VARCHAR(400) DEFAULT '';

-- WHILE loop

/*

WHILE i < 4 DO

SET s = CONCAT(s, 'i=', i, ' | ');

SET i = i + 1;

END WHILE;

*/

-- REPEAT loop

REPEAT

SET s = CONCAT(s, 'i=', i, ' | ');

SET i = i + 1;

UNTIL i = 4

END REPEAT;

-- LOOP with LEAVE statement

/*

testLoop : LOOP

SET s = CONCAT(s, 'i=', i, ' | ');

SET i = i + 1;

IF i = 4 THEN

LEAVE testLoop;

9

END IF;

END LOOP testLoop;

*/

SELECT s AS message;

END//

DELIMITER ;

CALL test();

Using a CURSOR

By default, SQL statements work with an entire result set rather than individual rows. However,

sometimes, you may need to work with the data in a result set one row at a time. To do that, CURSOR is

used.

The syntax for using a cursor

Declare a cursor

DECLARE cursor_name CURSOR FOR select_statement;

Declare an error handler for when no rows are found in the cursor

DECLARE CONTINUE HANDLER FOR NOT FOUND handler_statement;

Open the cursor

OPEN cursor_name;

Get column values from the row and store them in a series of variables

FETCH cursor_name INTO variable1 [, variable2][, variable3]...;

Close the cursor CLOSE cursor_name;

Example:

USE ap;

DROP PROCEDURE IF EXISTS test;

DELIMITER //

CREATE PROCEDURE test()

10

BEGIN

DECLARE invoice_id_var INT;

DECLARE invoice_total_var DECIMAL(9,2);

DECLARE row_not_found TINYINT DEFAULT FALSE;

DECLARE update_count INT DEFAULT 0;

DECLARE invoices_cursor CURSOR FOR

SELECT invoice_id, invoice_total FROM invoices

WHERE invoice_total - payment_total - credit_total > 0;

DECLARE CONTINUE HANDLER FOR NOT FOUND

SET row_not_found = TRUE;

OPEN invoices_cursor;

WHILE row_not_found = FALSE DO

FETCH invoices_cursor INTO invoice_id_var, invoice_total_var;

IF invoice_total_var > 1000 THEN

UPDATE invoices

SET credit_total = credit_total + (invoice_total * .1)

WHERE invoice_id = invoice_id_var;

SET update_count = update_count + 1;

END IF;

END WHILE;

CLOSE invoices_cursor;

SELECT CONCAT(update_count, ' row(s) updated.');

END//

DELIMITER ;

CALL test();

Declaring a condition handler

Commonly used MySQL error codes

11

Built-in named conditions

NOT FOUND – occurs when a program attempts to use a FETCH statement or a SELECT statement to

retrieve data and no data is found

SQLEXCEPTION – Occurs when any error condition other than the NOT FOUND condition occurs

SQLWARNING – Occurs when any error condition other than the NOT FOUND condition occurs or

when any warning messages occur

The syntax for declaring a condition handler

DECLARE {CONTINUE|EXIT} HANDLER

FOR { mysql_error_code| SQLSTATE sqlstate_code| named_condition }

handler_actions;

How to declare a condition handler for a MySQL error code

DECLARE CONTINUE HANDLER FOR 1329

SET row_not_found = TRUE

How to declare a condition handler for a SQLSTATE code

DECLARE CONTINUE HANDLER FOR SQLSTATE '02000'

SET row_not_found = TRUE

How to declare a condition handler for a named condition

DECLARE CONTINUE HANDLER FOR NOT FOUND

SET row_not_found = TRUE

To continue execution when an error occurs, use the CONTINUE keyword. To exit the current block of

code when an error occurs, use the EXIT keyword.

For a complete list of the MySQL error codes and their corresponding SQLSTATE codes, you can search

the MySQL reference manual for “Server Error Codes”.

12

Example: a stored procedure that does not handle errors

USE ap;

DROP PROCEDURE IF EXISTS test;

DELIMITER //

CREATE PROCEDURE test()

BEGIN

INSERT INTO general_ledger_accounts VALUES (130, 'Cash');

SELECT '1 row was inserted.';

END//

DELIMITER ;

CALL test();

The response

A stored procedure that uses a CONTINUE handler

USE ap;

DROP PROCEDURE IF EXISTS test;

DELIMITER //

CREATE PROCEDURE test()

BEGIN

DECLARE duplicate_entry_for_key INT DEFAULT FALSE;

DECLARE CONTINUE HANDLER FOR 1062

SET duplicate_entry_for_key = TRUE;

INSERT INTO general_ledger_accounts VALUES (130, 'Cash');

IF duplicate_entry_for_key = TRUE THEN

SELECT 'Row was not inserted - duplicate key encountered.' AS message;

ELSE

SELECT '1 row was inserted.' AS message;

END IF;

END//

13

DELIMITER ;

CALL test();

Response from the system

Example: a stored procedure that uses an EXIT handler to handle an error

USE ap;

DROP PROCEDURE IF EXISTS test;

DELIMITER //

CREATE PROCEDURE test()

BEGIN

DECLARE duplicate_entry_for_key INT DEFAULT FALSE;

BEGIN

DECLARE EXIT HANDLER FOR 1062

SET duplicate_entry_for_key = TRUE;

INSERT INTO general_ledger_accounts VALUES (130, 'Cash');

SELECT '1 row was inserted.' AS message;

END;

IF duplicate_entry_for_key = TRUE THEN

SELECT 'Row was not inserted - duplicate key encountered.' AS message;

END IF;

END//

DELIMITER ;

CALL test();

The response from the system

14

Example: a stored procedure that uses a named condition to handle all errors

USE ap;

DROP PROCEDURE IF EXISTS test;

DELIMITER //

CREATE PROCEDURE test()

BEGIN

DECLARE sql_error TINYINT DEFAULT FALSE;

BEGIN

DECLARE EXIT HANDLER FOR SQLEXCEPTION

SET sql_error = TRUE;

INSERT INTO general_ledger_accounts VALUES (130, 'Cash');

SELECT '1 row was inserted.' AS message;

END;

IF sql_error = TRUE THEN

SHOW ERRORS;

END IF;

END//

DELIMITER ;

CALL test();

The response

SHOW ERRORS statement returns a result set that contains information about the errors that resulted

from the last statement in the current session.

Multiple condition handlers can be declared for a single stored program. If you do that, the most specific

error handlers are executed first and the least specific error handlers are executed last.

The MySQL error codes and the NOT FOUND condition identify specific errors. The SQLSTATE codes

identify less specific ANSI-standard errors. And the SQLEXCEPTION and SQLWARNING conditions

identify general errors.

Example: a stored procedure that uses multiple condition handlers

15

USE ap;

DROP PROCEDURE IF EXISTS test;

DELIMITER //

CREATE PROCEDURE test()

BEGIN

DECLARE duplicate_entry_for_key INT DEFAULT FALSE;

DECLARE column_cannot_be_null INT DEFAULT FALSE;

DECLARE sql_exception INT DEFAULT FALSE;

BEGIN

DECLARE EXIT HANDLER FOR 1062

SET duplicate_entry_for_key = TRUE;

DECLARE EXIT HANDLER FOR 1048

SET column_cannot_be_null = TRUE;

DECLARE EXIT HANDLER FOR SQLEXCEPTION

SET sql_exception = TRUE;

INSERT INTO general_ledger_accounts VALUES (NULL, 'Test');

SELECT '1 row was inserted.' AS message;

END;

IF duplicate_entry_for_key = TRUE THEN

SELECT 'Row was not inserted - duplicate key encountered.' AS message;

ELSEIF column_cannot_be_null = TRUE THEN

SELECT 'Row was not inserted - column cannot be null.' AS message;

ELSEIF sql_exception = TRUE THEN

SHOW ERRORS;

END IF;

END//

DELIMITER ;

CALL test();

Transactions

A transaction is a group of SQL statements that are combined in a single logical unit of task.

By default, a MySQL session uses “autocommit” mode, which automatically automatically commits

INSERT, UPDATE, and DELETE statements.

16

When to use transactions

When you code two or more INSERT, UPDATE, or DELETE statements that affect related data.

When you move rows from one table to another table by using INSERT and DELETE statements

When data integrity is violated by INSERT, UPDATE, or DELETE statement.

To start a transaction, use the START TRANSACTION statement. This turns off autocommit mode until

the statements in the transaction are committed or rolled back. To commit the changes, code a COMMIT

statement. To roll back the change, use a ROLLBACK statement. MySQL automatically commits

changes after a DDL statement such as a CREATE TABLE statement. You should not code DDL

statement within a transaction.

Transactions are often coded within stored procedures. The following example shows a stored procedure

that runs three INSERT statements as a transaction.

How to create stored procedures that accept parameters

Syntax of Stored Procedure (SP)

CREATE PROCEDURE procedure_name

(

[parameter_name_1 data_type]

[, parameter_name_2 data_type]...

)

sql_block

Example: A SP that updates a table

USE ap;

DROP PROCEDURE IF EXISTS update_invoices_credit_total;

DELIMITER //

CREATE PROCEDURE update_invoices_credit_total

(

invoice_id_param INT,

credit_total_param DECIMAL(9,2)

)

BEGIN

DECLARE sql_error INT DEFAULT FALSE;

DECLARE CONTINUE HANDLER FOR SQLEXCEPTION

SET sql_error = TRUE;

START TRANSACTION;

UPDATE invoices

17

SET credit_total = credit_total_param

WHERE invoice_id = invoice_id_param;

IF sql_error = FALSE THEN

COMMIT;

ELSE

ROLLBACK;

END IF;

END//

DELIMITER ;

-- Use the CALL statement

CALL update_invoices_credit_total(56, 200);

SELECT invoice_id, credit_total

FROM invoices WHERE invoice_id = 56;

The system responses

credit_total was updated from 0.00 to 200.00

-- Use the CALL statement within a stored procedure

DROP PROCEDURE IF EXISTS test;

DELIMITER //

CREATE PROCEDURE test()

BEGIN

CALL update_invoices_credit_total(56, 300);

END//

DELIMITER ;

CALL test();

SELECT invoice_id, credit_total

FROM invoices WHERE invoice_id = 56;

-- Reset data to original value

CALL update_invoices_credit_total(56, 0);

18

SELECT invoice_id, credit_total

FROM invoices WHERE invoice_id = 56;

The syntax for declaring input and output parameters

Input parameters accept values that are passed from the calling program. These values cannot be changed

by the body of the stored procedure. By default, parameters are defined as input parameters. As a result,

the IN keyword is optional for identifying input parameters.

Output parameters store values that are passed back to the calling program. These values must be set by

the body of the stored procedure. To identify an output parameter, you must use the OUT keyword.

INOUT parameters can store an initial value that is passed from the calling program. The body of the SP

can change this parameter.

When you work with OUT parameters or INOUT parameters, the calling program typically passes a user

variable to the parameter list.

[IN|OUT|INOUT] parameter_name data_type

Example:

USE ap;

DROP PROCEDURE IF EXISTS update_invoices_credit_total;

DELIMITER //

CREATE PROCEDURE update_invoices_credit_total

(

IN invoice_id_param INT,

IN credit_total_param DECIMAL(9,2),

INOUT update_count INT

)

BEGIN

DECLARE sql_error INT DEFAULT FALSE;

DECLARE CONTINUE HANDLER FOR SQLEXCEPTION

SET sql_error = TRUE;

START TRANSACTION;

UPDATE invoices

SET credit_total = credit_total_param

WHERE invoice_id = invoice_id_param;

IF sql_error = FALSE THEN

SET update_count = 1;

COMMIT;

ELSE

19

SET update_count = 0;

ROLLBACK;

END IF;

END//

DELIMITER ;

set @row_count = 0;

CALL update_invoices_credit_total(56, 200, @row_count);

CALL update_invoices_credit_total(56, 0, @row_count);

SELECT CONCAT('row_count: ', @row_count) AS update_count;

The response

You can provide a default value for a parameter so that if the calling program passes a null value for the

parameter, the default value is used instead. To set a default value for a parameter, you can use an IF

statement to check if the parameter contains a null value. If it does, you can assign a default value to the

parameter. It is a good programming practice to write SP with a list of parameters that require values first

followed by parameters that allow null values.

Example:

USE ap;

DROP PROCEDURE IF EXISTS update_invoices_credit_total;

DELIMITER //

CREATE PROCEDURE update_invoices_credit_total

(

invoice_id_param INT,

credit_total_param DECIMAL(9,2)

)

BEGIN

DECLARE sql_error INT DEFAULT FALSE;

DECLARE CONTINUE HANDLER FOR SQLEXCEPTION

SET sql_error = TRUE;

-- Set default values for NULL values

20

IF credit_total_param IS NULL THEN

SET credit_total_param = 100;

END IF;

START TRANSACTION;

UPDATE invoices

SET credit_total = credit_total_param

WHERE invoice_id = invoice_id_param;

IF sql_error = FALSE THEN

COMMIT;

ELSE

ROLLBACK;

END IF;

END//

DELIMITER ;

-- call with param

CALL update_invoices_credit_total(56, 200);

SELECT invoice_id, credit_total

FROM invoices WHERE invoice_id = 56;

-- call without param

CALL update_invoices_credit_total(56, NULL);

SELECT invoice_id, credit_total

FROM invoices WHERE invoice_id = 56;

-- reset data

CALL update_invoices_credit_total(56, 0);

SELECT invoice_id, credit_total

FROM invoices WHERE invoice_id = 56;

Validate Parameters

Data validation validates the parameters in a SP before using them. The SIGNAL statement raises an

error. When an error occurs, SQLSTATE code must be signaled. You may optionally specify an error

message or MySQL error number. When an error is raised, MySQL returns the error to the caller in the

same way that it returns errors that are raised by the database engine. The calling program can handle the

error.

The syntax of the SIGNAL statement

SIGNAL SQLSTATE [VALUE] sqlstate_value

[SET MESSAGE_TEXT = message [, MYSQL_ERRNO = mysql_error_number]]

21

Example:

USE ap;

DROP PROCEDURE IF EXISTS update_invoices_credit_total;

DELIMITER //

CREATE PROCEDURE update_invoices_credit_total

(

invoice_id_param INT,

credit_total_param DECIMAL(9,2)

)

BEGIN

-- Validate paramater values

IF credit_total_param < 0 THEN

UPDATE `The credit_total column must be greater than or equal to 0.`

SET x = 'This UPDATE statement raises an error';

ELSEIF credit_total_param >= 1000 THEN

SIGNAL SQLSTATE '22003'

SET MESSAGE_TEXT =

'The credit_total column must be less than 1000.',

MYSQL_ERRNO = 1264;

END IF;

-- Set default values for parameters

IF credit_total_param IS NULL THEN

SET credit_total_param = 100;

END IF;

UPDATE invoices

SET credit_total = credit_total_param

WHERE invoice_id = invoice_id_param;

END//

DELIMITER ;

CALL update_invoices_credit_total(56, NULL);

CALL update_invoices_credit_total(56, -100);

CALL update_invoices_credit_total(56, 1000);

CALL update_invoices_credit_total(56, 0);

SELECT invoice_id, credit_total

FROM invoices WHERE invoice_id = 56;

The response

22

Example: a stored procedure that validates the data in a new invoice

If the data for each of the columns of the row is valid, the procedure executes an INSERT statement to

insert the row. Otherwise, the procedure or database engine raises an error and exits the procedure.

USE ap;

DROP PROCEDURE IF EXISTS insert_invoice;

DELIMITER //

CREATE PROCEDURE insert_invoice

(

vendor_id_param INT,

invoice_number_param VARCHAR(50),

invoice_date_param DATE,

invoice_total_param DECIMAL(9,2),

terms_id_param INT,

invoice_due_date_param DATE

)

BEGIN

DECLARE terms_id_var INT;

DECLARE invoice_due_date_var DATE;

DECLARE terms_due_days_var INT;

-- Validate paramater values

IF invoice_total_param < 0 THEN

SIGNAL SQLSTATE '22003'

SET MESSAGE_TEXT = 'The invoice_total column must be a positive number.',

MYSQL_ERRNO = 1264;

ELSEIF invoice_total_param >= 1000000 THEN

SIGNAL SQLSTATE '22003'

SET MESSAGE_TEXT = 'The invoice_total column must be less than 1,000,000.',

MYSQL_ERRNO = 1264;

END IF;

-- Set default values for parameters

IF terms_id_param IS NULL THEN

SELECT default_terms_id INTO terms_id_var

FROM vendors WHERE vendor_id = vendor_id_param;

ELSE

SET terms_id_var = terms_id_param;

23

END IF;

IF invoice_due_date_param IS NULL THEN

SELECT terms_due_days INTO terms_due_days_var

FROM terms WHERE terms_id = terms_id_var;

SELECT DATE_ADD(invoice_date_param, INTERVAL terms_due_days_var DAY)

INTO invoice_due_date_var;

ELSE

SET invoice_due_date_var = invoice_due_date_param;

END IF;

INSERT INTO invoices

(vendor_id, invoice_number, invoice_date,

invoice_total, terms_id, invoice_due_date)

VALUES (vendor_id_param, invoice_number_param, invoice_date_param,

invoice_total_param, terms_id_var, invoice_due_date_var);

END//

DELIMITER ;

-- test

CALL insert_invoice(34, 'ZXA-080', '2012-01-18', 14092.59,

3, '2012-03-18');

CALL insert_invoice(34, 'ZXA-082', '2012-01-18', 14092.59,

NULL, NULL);

-- this statement raises an error

CALL insert_invoice(34, 'ZXA-083', '2012-01-18', -14092.59,

NULL, NULL);

-- clean up

SELECT * FROM invoices WHERE invoice_id >= 115;

DELETE FROM invoices WHERE invoice_id >= 115;

The response

User variable is a special type of MySQL variable that’s globally available to the current user. A user

variable is only available to the current user and cannot be seen or accessed by other users. A user

variable is available as long as the user remains connected to the server, but it is reset when the user

disconnects. A user variable can store various types including string, numeric, and date/time types.

However, you do not need to declare a data type for a user variable. A user variable is available from

statements coded both inside and outside of stored programs.

The syntax for setting a user variable

24

SET @variable_name = expression

Example: Two SPs that work with the same user variable

USE ap;

DROP PROCEDURE IF EXISTS set_global_count;

DROP PROCEDURE IF EXISTS increment_global_count;

DELIMITER //

CREATE PROCEDURE set_global_count

(

count_var INT

)

BEGIN

SET @count = count_var;

END//

CREATE PROCEDURE increment_global_count()

BEGIN

SET @count = @count + 1;

END//

DELIMITER ;

CALL set_global_count(100);

CALL increment_global_count();

SELECT @count AS count_var;

The response

Work with dynamic SQL

Dynamic SQL is often used to build complex WHERE clauses that depende on multiple search conditions

that may or may not be specified by the user.

You can use a SP to build a string variable that contains a SQL statement. Then, you can use the

PREPARE, EXECUTE, and DEALLOCATE statements to execute the statement contained in the string.

Example: a SP that uses dynamic SQL

25

USE ap;

DROP PROCEDURE IF EXISTS select_invoices;

DELIMITER //

CREATE PROCEDURE select_invoices

(

min_invoice_date_param DATE,

min_invoice_total_param DECIMAL(9,2)

)

BEGIN

DECLARE select_clause VARCHAR(200);

DECLARE where_clause VARCHAR(200);

SET select_clause = "SELECT invoice_id, invoice_number,

invoice_date, invoice_total

FROM invoices ";

SET where_clause = "WHERE ";

IF min_invoice_date_param IS NOT NULL THEN

SET where_clause = CONCAT(where_clause,

" invoice_date > '", min_invoice_date_param, "'");

END IF;

IF min_invoice_total_param IS NOT NULL THEN

IF where_clause != "WHERE " THEN

SET where_clause = CONCAT(where_clause, "AND ");

END IF;

SET where_clause = CONCAT(where_clause,

"invoice_total > ", min_invoice_total_param);

END IF;

IF where_clause = "WHERE " THEN

SET @dynamic_sql = select_clause;

ELSE

SET @dynamic_sql = CONCAT(select_clause, where_clause);

END IF;

PREPARE select_invoices_statement

FROM @dynamic_sql;

EXECUTE select_invoices_statement;

DEALLOCATE PREPARE select_invoices_statement;

END//

DELIMITER ;

26

CALL select_invoices('2011-07-25', 100);

CALL select_invoices('2011-07-25', NULL);

CALL select_invoices(NULL, 1000);

CALL select_invoices(NULL, NULL);

A @dynamic_sql variable at runtime with parameters inserted

SELECT invoice_id, invoice_number, invoice_date, invoice_total

FROM invoices

WHERE invoice_date > '2011-08-29' AND invoice_total > 100

Drop a SP

The syntax of the DROP PROCEDURE statement

DROP PROCEDURE [IF EXISTS] procedure_name

2. Stored Functions (SFs)

A stored function (SF), which is also called a user defined function (UDF) or just a function, is an

executable database object that contains a block of procedural SQL code. With MySQL, you can only

create scalar functions which return a single value. Other DBMS SF may return a result set. To identify

the data type that is returned by a function, you use RETURNS keyword in the declaration for the

function. Then, in the body of the SF, RETURN keyword is used to specify the value that is returned.

RETURN keyword is not allowed in a SP. A function can accept input parameters that work like the input

parameters for a stored procedure. A function cannot make changes to the database such as executing an

INSERT, UPDATE, or DELETE statement. To call a stored function, you can use it in any expression.

Example:

USE ap;

DROP FUNCTION IF EXISTS get_vendor_id;

DELIMITER //

CREATE FUNCTION get_vendor_id

(

vendor_name_param VARCHAR(50)

)

RETURNS INT

BEGIN

DECLARE vendor_id_var INT;

SELECT vendor_id

INTO vendor_id_var

27

FROM vendors

WHERE vendor_name = vendor_name_param;

RETURN(vendor_id_var);

END//

DELIMITER ;

SELECT invoice_number, invoice_total

FROM invoices

WHERE vendor_id = get_vendor_id('IBM');

The response

Example: a function that calculates balance due

USE ap;

DROP FUNCTION IF EXISTS get_balance_due;

DELIMITER //

CREATE FUNCTION get_balance_due

(

invoice_id_param INT

)

RETURNS DECIMAL(9,2)

BEGIN

DECLARE balance_due_var DECIMAL(9,2);

SELECT invoice_total - payment_total - credit_total

INTO balance_due_var

FROM invoices

WHERE invoice_id = invoice_id_param;

RETURN balance_due_var;

END//

DELIMITER ;

SELECT vendor_id, invoice_number,

get_balance_due(invoice_id) AS balance_due

FROM invoices

WHERE vendor_id = 37;

28

To display SPs and SFs in MySQL Workbench

29

SHOW PROCEDURE STATUS;

Display information about the SPs.

SHOW FUNCTION STATUS;

Display information about the SFs.

3. Create Triggers

Triggers can be executed before or after an INSERT, UPDATE, or DELETE statement is executed on a

table. As a result, they provide a powerful way to enforce data consistency. A trigger is a named block of

code that executes or fires (in response to an INSERT, UPDATE, or DELETE statement). A trigger can

be fired before or after INSERT, UPDATE, or DELETE statement. You must specify a FOR EACH

ROW clause. This creates a row-level trigger that fires once for each row that is modified. You can use

the OLD and NEW keywords to get and set the values for the columns that are stored in the old row and

the new row.

The syntax of the CREATE TRIGGER statement

CREATE TRIGGER trigger_name

{BEFORE|AFTER} {INSERT|UPDATE|DELETE} ON table_name

30

FOR EACH ROW

sql_block

Example: a trigger that corrects mixed-case state names

USE ap;

DROP TRIGGER IF EXISTS vendors_before_update;

DELIMITER //

CREATE TRIGGER vendors_before_update

BEFORE UPDATE ON vendors

FOR EACH ROW

BEGIN

SET NEW.vendor_state = UPPER(NEW.vendor_state);

END//

DELIMITER ;

#An update statement that fires the trigger

UPDATE vendors

SET vendor_state = 'wi'

WHERE vendor_id = 1;

SELECT vendor_name, vendor_state

FROM vendors

WHERE vendor_id = 1;

Example: a trigger that validates line item amounts

USE ap;

DROP TRIGGER IF EXISTS invoices_before_update;

DELIMITER //

CREATE TRIGGER invoices_before_update

BEFORE UPDATE ON invoices

FOR EACH ROW

BEGIN

DECLARE sum_line_item_amount DECIMAL(9,2);

31

SELECT SUM(line_item_amount)

INTO sum_line_item_amount

FROM invoice_line_items

WHERE invoice_id = NEW.invoice_id;

IF sum_line_item_amount != NEW.invoice_total THEN

SIGNAL SQLSTATE 'HY000'

SET MESSAGE_TEXT = 'Line item total must match invoice total.';

END IF;

END//

DELIMITER ;

#Update statement that fires the trigger

UPDATE invoices

SET invoice_total = 600

WHERE invoice_id = 100;

SELECT invoice_id, invoice_total, credit_total, payment_total

FROM invoices WHERE invoice_id = 100;

The system response

Triggers can be used to enforce rules for data consistency that cannot be enforced by constraints

How to create an AFTER trigger

Triggers are commonly used to store information about actions that occur in a database so these actions

can be reviewed later. In particular, AFTER triggers are used to store information about a statement after

it executes.

Example:

-- A statement that creates an audit table

USE ap;

DROP TABLE if exists invoices_audit;

CREATE TABLE invoices_audit

(

vendor_id INT NOT NULL,

invoice_number VARCHAR(50) NOT NULL,

32

invoice_total DECIMAL(9,2) NOT NULL,

action_type VARCHAR(50) NOT NULL,

action_date DATETIME NOT NULL

);

DROP TRIGGER IF EXISTS invoices_after_insert;

DROP TRIGGER IF EXISTS invoices_after_delete;

DELIMITER //

-- two AFTER triggers that insert rows into the audit table

CREATE TRIGGER invoices_after_insert

AFTER INSERT ON invoices

FOR EACH ROW

BEGIN

INSERT INTO invoices_audit VALUES

(NEW.vendor_id, NEW.invoice_number, NEW.invoice_total,

'INSERTED', NOW());

END//

CREATE TRIGGER invoices_after_delete

AFTER DELETE ON invoices

FOR EACH ROW

BEGIN

INSERT INTO invoices_audit VALUES

(OLD.vendor_id, OLD.invoice_number, OLD.invoice_total,

'DELETED', NOW());

END//

DELIMITER ;

-- an INSERT statement that causes the first trigger to fire

INSERT INTO invoices VALUES

(115, 34, 'ZXA-080', '2011-08-30', 14092.59, 0, 0, 3, '2011-09-30', NULL);

-- a DELETE statement that causes the second trigger to fire

DELETE FROM invoices WHERE invoice_id = 115;

SELECT * FROM invoices_audit;

The first CREATE TRIGGER statement creates a trigger that executes after an INSERT statement is

executed on the Invoices table. This trigger inserts the new values for the vendor_id, invoice_number,

invoice_total, string value “INSERTED”, current date and time. The second trigger works similarly.

The response

33

View or drop triggers

List all triggers in the current database

SHOW TRIGGERS;

List all triggers in the specified database

SHOW TRIGGERS IN ap;

List all triggers that begin with ‘ven%’

SHOW TRIGGERS IN ap LIKE 'ven%';

A statement that drops a trigger only if it exists

DROP TRIGGER IF EXISTS vendors_before_update;

4. Events

An event, or scheduled event, is a named block of code that executes, or fires, according to the event

scheduler. By default the event scheduler is off. As a result, before you begin working with events, you

need to turn on the event scheduler. If you do not need to use events, you should turn the event scheduler

off to save system resources.

Check if the event scheduler is on

SHOW VARIABLES LIKE 'event_scheduler';

34

Events are procedural database objects that MySQL invokes at the scheduled time. An event can be

invoked only once. An event can be started periodically (e.g., every Sunday at 4:00 AM). MySQL keeps a

schedule that tells when events must be started.

Events are used for the following cases:

Events can close accounts: at end of each month or each year, for example, many accounting

departments need to close their accounts.

Events can turn database indicators on or off. For example, consider an airplane company

database. When a flight has departed, no more reservations can be made for that flight. The

column CLOSED in the FLIGHTS table must be set to YES. You can schedule an event to

automatically start 20 minutes after the flight’s planned departure time to complete the task.

Backup database to files

Example:

-- a statement to check if the event scheduler is on

SHOW VARIABLES LIKE 'event_scheduler';

DROP EVENT IF EXISTS one_time_delete_audit_rows;

DROP EVENT IF EXISTS monthly_delete_audit_rows;

DELIMITER //

CREATE EVENT one_time_delete_audit_rows

ON SCHEDULE AT NOW() + INTERVAL 1 MONTH

DO BEGIN

DELETE FROM invoices_audit WHERE action_date < NOW() - INTERVAL 1 MONTH LIMIT 100;

END//

CREATE EVENT monthly_delete_audit_rows

ON SCHEDULE EVERY 1 MONTH

STARTS '2013-04-28 00:00:00'

DO BEGIN

DELETE FROM invoices_audit WHERE action_date < NOW() - INTERVAL 1 MONTH LIMIT 100;

END//

DELIMITER ;

SHOW EVENTS;

SHOW EVENTS IN ap;

SHOW EVENTS IN ap LIKE 'mon%';

35

ALTER EVENT monthly_delete_audit_rows DISABLE;

ALTER EVENT monthly_delete_audit_rows ENABLE;

ALTER EVENT one_time_delete_audit_rows RENAME TO one_time_delete_audits;

DROP EVENT monthly_delete_audit_rows;

DROP EVENT IF EXISTS monthly_delete_audit_rows;