12 MySQL存储过程与函数

存储过程和函数
    存储过程和函数是事先经过编译并存储在数据库中的一段SQL语句的集合.
    调用存储过程和函数可以简化应用开发人员的工作,减少数据在数据库和应用服务器之间的传输,提高数据处理的效率.
 
    存储过程和函数的区别在于:
        函数必须有返回值,而存储过程没有.
        存储过程的参数可以使用 IN,OUT,INOUT 类型;而函数的参数只能是IN类型.
     
    如果有函数从其他类型数据库迁移到MySQL就可能需要把函数改造成存储过程.
 
 
存储过程和函数的相关操作
    首先确认是否有相应的权限.
        创建存储过程/函数 需要CREATE ROUTINE权限.
        修改/删除 存储过程/函数 需要ALTER ROUTINE权限.
        执行存储过程/函数 需要EXECUTE权限.
 
    1).创建/修改 存储过程或函数
        CREATE PROCEDURE sp_name ([proc_param[,...]])
            [characteristic …] 
            routine_body
 
        CREATE FUNCTION sp_name ([func_param[,...])
            RETURNS     type
            [characteristic …] 
            routine_body
 
 
            其中,
            proc_param :
                [IN|OUT|INOUT] param_name type
 
            func_param :
                param_name type
 
            type : any valid MySQL data type .
    
            characteristic :
                LANGUAGE SQL
                | [NOT] DETERMINISTIC
                | {CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA}
                | SQL SECURITY {DEFINER | INVOKER}
                | COMMENT 'string'
 
            routine_body :
                valid SQL procedure statement or statements .
 
        
 
        ALTER {PROCEDURE | FUNCTION} sp_name [characteristic ...]
 
        characteristic :
            {CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA}
            |SQL SECURITY {DEFINER|INVOKER}
            |COMMENT 'string'
 
        调用存储过程/函数,使用关键字CALL :
            CALL sp_name ([[parameter [,...]])
 
 
      注意: 存储过程/函数 中允许包含DDL ,也可以在存储过程中执行COMMIT/ROLLBACK 操作,还可以调用其他的过程/函数;
                但是存储过程/函数 中不允许使用 LOAD DATA INFILE 语句.
 
 
           Demo :
                DELIMITER $$
        
                CREATE PROCEDURE film_in_stock (IN p_film_id INT , IN p_store_id INT , OUT p_film_count INT)
                READS SQL DATA
                BEGIN
                        SELECT inventory_id
                        FROM inventory
                        WHERE film_id = p_film_id
                        AND store_id = p_store_id
                        AND inventory_in_stock(inventory_id);
        
                        SELECT FOUND_ROWS() INTO p_film_count;
                END $$
 
                DELIMITER ;
 
                CALL film_in_stock(2,2,@a);
        
                SELECT @a ;
 
    注意 : 与视图的创建语法不同,存储过程/函数 的CREATE语法不支持使用 CREATE OR REPLACE对存储过程/函数进行修改.如需修改,可以执行ALTER语法.
 
 
 
    characteristic特征值的简单说明:
 
        1).LANGUAGE SQL
            说明下面过程的BODY是使用SQL语句编写(系统默认的),以后MySQL可能支持其它语言.
 
        2).[NOT] DETERMINISTIC:
                目前还未被优化程序使用
                DETERMINISTIC 每次输入一样输出也一样的程序
                NOT DETERMINISTIC(系统默认).
 
        3).{CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA}
            目前这些特征值只是提供给服务器,并未用来约束存储过程实际使用数据的情况.
                CONTAINS SQL 表示子程序不包含读或者写数据的语句
                NO SQL 表示子程序不包含SQL语句
                READS SQL DATA 表示子程序包含读数据的语句
                MODIFIES SQL DATA 表示子程序包含写数据的语句
                默认值是 CONTAINS SQL
 
        4).SQL SECURITY {DEFINER | INVOKER}
            指定子程序 该用创建子程序者的权限来执行,还是使用调用者的权限来执行.默认值是DEFINER , 即使用创建者的权限执行.
 
        5).COMMENT 'string'
            存储过程/函数 的注释信息.
 
 
    
    2.删除存储过程/函数
        一次性只能删除一个存储过程/函数.需要有ALTER ROUTINE 权限.
        DROP {PROCEDURE | FUNCTION} [IF EXISTS] sp_name
 
 
    3.查看存储过程/函数
        1).查看存储过程/函数 的状态
            SHOW {PROCEDURE | FUNCTION} STATUS [LIKE 'pattern']
 
        2).查看存储过程/函数 的定义
            SHOW CREATE {PROCEDURE|FUNCTION} sp_name
 
        3).通过查看information_schema.Routines 了解存储过程/函数 的信息
            包含名称,类型,语法,创建人等信息.
            demo:SELECT * FROM routines WHERE ROUTINE_NAME = 'film_in_stock' G
 
    
    4.变量的使用
 
        1).变量的定义
            DECLARE var_name [,...] type [DEFAULT value]
 
 
        2).变量的赋值
            SET var_name = expr [,var_name = expr] ...
 
            其中,expr 可以是字面量,函数返回值,SELECT 语句(要求结果有且只有一行)等.
            或者使用SELECT .. INTO .. 语句
            SELECT col_name[,..] INTO var_name[,..] table_expr
 
             Demo :
 
                DECLARE $$
 
        CREATE FUNCTION get_customer_balance( p_customer_id INT , p_effective_date DATETIME )
            RETURNS DECIMAL(5,2)
            DETERMINISTIC
            READS SQL DATA
        BEGIN
                ...
                DECLARE v_payments DECIMAL(5,2) ;
                ...
                SELECT IFNULL (SUM(payment,amount),0) INTO v_payments
                FROM payment
                WHERE payment.payment_date <= p_effective_date
                AND payment.customer_id = p_customer_id ;
                ...        
                RETURN v_rentfees + v_overfees - v_payments ;
        END $$
        DECLARE ;
 
        CALL get_customer_balance(1,'2018-03-02 09:11:15') ;
            
 
 
    5.定义条件和处理
        1).条件的定义
            DECLARE condition_name CONDITION FOR condition_value
 
            其中,condition_value :
                SQLSTATE [VALUE] sqlstate_value | mysql_error_code
 
        2).条件的处理
            DECLARE handler_type HANDLER FOR condition_value[,..] sp_statement
 
            handler_type :
                CONTINUE | EXIT | UNDO(暂不支持)
 
            condition_value:
                SQLSTATE [VALUE] sqlstate_value
                |condition_name
                |SQLWARNING
                |NOT FOUND
                |SQLEXCEPTION
                |mysql_error_code
 
 
        demo:todo
 
 
 
    6.光标的使用 --就是游标
        可以使用游标对结果集进行循环处理
        1).声明光标
            DECLARE cursor_name CURSOR FOR select_statement
                select_statement 中的SELECT语句 不能包含 INTO , 即不可以是SELECT .. INTO ..语句
                SELECT 语句查询出来的列数 , 必须与FETCH游标中的接收数据的变量数 一致.     
 
        2).OPEN光标
            OPEN cursor_name
 
        3).FETCH光标
            FETCH cursor_name INTO var_name[,var_name,..]
 
        4).CLOSE光标
            CLOSE cursor_name
 
        Demo :  —— 需要注意 DECLARE 声明的顺序 : 变量 -> 游标 -> 条件处理 .
            DELIMITER $$
        
            CREATE PROCEDURE payment_stat()
            BEGIN
                DECLARE i_staff_id INT ;
                DECLARE d_amount DECIMAL(5,2);
                DECLARE cur_payment CURSOR FOR SELECT staff_id,amount FROM payment ;
                DECLARE EXIT HANDLER FOR NOT FOUND CLOSE cur_payment ;
 
                SET @x1 = 0 ;
                SET @x2 = 0 ;
 
                OPEN cur_payment ;
                
                REPEAT
                    FETCH cur_payment INTO i_staff_id,d_amount ;
                        IF i_staff_id = 2
                        THEN
                            SET @x1 = @x1 + d_amount ;
                        ELSE
                            SET @x2 = @x2 + d_amount ;
                        END IF ;
                UNTIL 0 END REPEAT ;
 
                CLOSE cur_payment ;
 
            END ;
            $$
 
            DELIMITER ;
 
            CALL payment_stat();
 
        注意:变量,条件,处理程序,游标都是通过DECLARE定义的,但是顺序是由先后要求的.
            变量和条件 必须声明在最前面,然后是游标的声明,最后才是处理程序的声明.
 
 
 
    7.流程控制
        1).IF语句
 
            IF search_condition
                THEN statement_list
            [
            ELSEIF search_condition 
                THEN statement_list
            ]
            [
            ELSE statement_list
            ]
            END IF
 
        2).CASE语句
            CASE case_value
                WHEN when_value THEN
                     statement_list
                [
                WHEN when_value THEN
                     statement_list
                ]
                    ...
                [
                ELSE 
                    statement_list
                ]
            END CASE
 
           或者
            CASE WHEN search_condition THEN 
                    statement_list
                [
                WHEN search_condition THEN
                     statement_list
                ]
                    ...
                [
                ELSE 
                    statement_list
                ]
            END CASE
 
        Demo : 改写游标demo中IF语句
            
            CASE
                WHEN i_staff_id = 2
                 THEN
                    SET @x1=@x1+d_amount ;
                ELSE
                    SET @x2= @x2+d_amount ;
            END CASE ;
 
           或
            CASE i_staff_id
                WHEN 2
                THEN
                    SET @x1 = @x1 + d_amount ;
                ELSE
                    SET @x2 = @x2 + d_amount ;
            END CASE ;
 
 
        3).LOOP 语句
            简单的循环,需要配合其他的语句定义来实现退出循环,通常使用LEAVE语句实现.
 
            [begin_label:] LOOP
                statement_list
            END LOOP [end_label]
 
            如果没有退出循环语句,就是个死循环.
 
 
        4).LEAVE 语句
            从标注的流程中退出,通常和BEGIN..END / 循环 一起使用.
            Demo :
 
                DELIMITER $$
 
                CREATE PROCEDURE actor_insert()
                BEGIN
                    SET @x = 0 ;
                    ins : LOOP
                        SET @x = @x+1;
                        IF @x=100
                        THEN
                            LEAVE ins ;
                        END IF ;
                        INSERT INTO actor(first_name,last_name)
                        VALUES('Test','201');
                    END LOOP ins ;
                END ;
                $$
 
                DELIMITER ;
 
                CALL actor_insert();
 
 
        5).ITERATE 语句
            必须用在循环中,表示跳过当前循环的剩下语句,进入下次循环.作用相当于Java中的continue.
 
            Demo:
            
                DELIMITER $$
                CREATE PROCEDURE actor_insert()
                BEGIN
                    SET @x = 0 ;
                    ins : LOOP
                        SET @x = @x + 1;
                        IF @x = 10
                        THEN
                            LEAVE ins ;
                        ELSEIF mod(@x,2) = 0
                        THEN
                            ITERATE ins ;
                        END IF ;
                        INSERT INTO actor (actor_id , first_name,last_name)
                        VALUES(@x*10,'Test',@x);
                    END LOOP ins ;
                END ;
                $$
 
                DELIMITER ;
            
                CALL actor_insert() ;
 
        6).REPEAT 语句
            有条件的循环语句,相当于Java中的do_while
            不同的是REPEAT是满足条件时就退出循环,while是满足就执行.Repeat 和 do..while 一样,至少执行一次.
 
                Demo参考游标的demo
 
 
        
        7).WHILE 语句
            [begin_label:] WHILE search_condition
                DO statement_list
            END WHILE [end_label]
 
    
    总结:存储过程/函数的优势是可以将数据的处理放在数据库服务器上进行,避免将大量的结果集传输给客户端,减少数据传输,
            但是在数据库服务器上进行大量的复杂运算会占用服务器的cpu,造成数据库服务器的压力,
            所以存储过程/函数中不要进行大量的复杂运算,应该将这些运算操作分摊到应用服务器上执行.
     
原文地址:https://www.cnblogs.com/lmxxlm-123/p/11132056.html