《MySQL 存储过程编程》-读书笔记

本书结构:

第一部分:存储编程基础

  第1章:存储过程程序基础

  第2章:MySQL存储编程指南

  第3章:语言基础

  第4章:语句块

  第5章:在存储程序中使用SQL

 

 

第一章:MySQL存储程序介绍

  存储程序包含存储过程,函数和触发器。正确使用存储程序也有助于加强数据库的安全性和完整性以及改善你的应用程序的性能和易维护性。

1.1 什么是存储程序

  一种被数据库服务器所存储和执行的计算机程序,存储程序的源代码可能是二进制编译版本,几乎总是占据着数据库服务器系统的表空间,程序总是位于其数据库服务器的进程或线程的内存地址中被执行。

  存储过程:是能够接受数个输入和输出参数并且能够在请求是被执行的程序单元;

  存储函数:它的执行结果会返回一个值。允许有效的扩展SQL语言的能力;

  触发器:用来相应激活或者数据库行为,事件的存储程序,常用来作为DML(数据库操纵语言)的响应而被调用,可以用来作为数据校验和自动反向格式化。

1.1.1 为什么使用存储程序

  可以使你的数据库更安全;

  提供数据访问的抽象机制,能够该少代码在底层数据结构演化过程中的易维护性;

  降低网络拥阻,因为属于数据库服务器的内部数据,相比在网上传输数据要快的多;

  可以替多种使用不同架构的外围应用实现共享的访问历程,无论这些架构是基于数据库服务器外部还是内部;

  以数据为中心的逻辑可以被独立的放置于存储程序中,可为程序员带来更高,更为独特的数据库编程体验;

  可以该少应用程序的可移植性;

1.1.2 MySQL的简史

1.1.3 MySQL 存储过程,函数和触发器

  存储程序语言包含了大量人们熟知的命令。包括变量操纵,条件语句的实现方式,迭代编程和错误处理等,很好的适应数据库编程的常规需求。

 

1.2 快速浏览

  MySQL存储程序结构和功能的基本要点的简单示例。

1.2.1 和SQL集成

  一个非常重要的方面就是和SQL的紧密集合,不需要借助于像ODBC或JDBC这样的软件粘合剂来为你的存储程序创建独立的SQL语句,只要简单的将UPDATE、INSERT和SELECT语句直接写进你的存储程序代码中。

1.2.2 控制和条件逻辑

  IF 和 CASE 语句

  完整的循环和迭代控制:包含简单循环,WHILE循环和REPEAT UNTIL循环。

1.2.3 存储函数

  存储函数是能够返回一个值的存储程序,也可以当做內建函数一样对待(调用)。

1.2.4 当发生错误时

  提供处理错误的强大机制,如果存在错误,错误将被捕获并且按照程序进行处理,如果没有错误处理,存储程序将被终止执行,并且错误将被返回给它的调用程序。

1.2.5 触发器

  用来响应数据库事件是自动回调的存储程序,触发器将在特定表的DML(数据库操纵语言)激活时被回调。

 

1.3 为开发者准备的存储过程参考资料

  MySQL5的存储程序在整个MySQL语言的演进过程中具有里程碑的意义。

  MySQL Stored Procedure Programming, by Guy Harrison with Steven Feuerstein

  MySQL in a Nutshell, by Russell Dyer

  Web Database Applications with PHP and MySQL, by Hugh Williams and David Lane

  MySQL, by Paul DuBois

  High Performance MySQL, by Jeremy Zawodny and Derek Balling

  MySQL Cookbook, by Paul DuBois

 

1.4 给开发者的建议

1.4.1 万事不能操之过急

  如果想一下子接触深度的代码结构,将需求转化为代码,很容易将被巨大的混乱所摧毁,你的程序将变得难以调试和维护,不要被紧张的开发期限所压垮,更希望你能在紧张的期限中做好周密的计划。

  强烈建议顶住时间的压力,在开始新的应用之前做好如下准备:

  在你写代码之前建立良好的测试机制和测试脚本;

  必须在动手写第一行代码之前给怎样才算一个成功的实现下一个定义。更像是在为你的程序该做什么建立一个接口,并彻底搞清楚这些功能的区别。

  为开发人员在应用程序中所写的SQL语句建立清晰的规则;

  对各种数据的查询,插入和更新操作都必须隐藏在我们预先建立并通过大量测试的存储过程函数中(这被称为数据封装),这样做你的程序就能比使用大量离散的SQL语句写出的程序更易于被优化,测试和维护。

  为开发人员在错误处理上建立清晰的规则;

  最好的方法是将错误的处理逻辑集中在一个存储集合中,这个集合中的过程专注于错误消息保存,错误的引发和传播方式的内部代码块(简言之意就是将错误处理的复杂度封装在这个过程集合中)。

  必须分配充足的时间,使用抽丝剥茧的方法,来消除你需求中的复杂度;

  把你的巨大挑战分解为一个个更小的问题,并把这些容易解决的小问题携程大小可以接受的程序,这样讲发现程序的可执行段明显的缩小,可读性也提高了,代码变得易于维护又节省时间。

1.4.2 不要害怕请教问题

  原因在于我们已经太熟悉自己写的代码了,有些时候需要的仅仅是一个新的视角,某人和善的一个建议就可能打开新的视野,这与资历,能力和经验无关。

  <原谅无知>程序开发过程中隐藏的无知是件极其危险的事情,培养能把“I don't know”说出口的分为并且鼓励问问题;

  <请求帮助>长时间不能指出代码中的Bug,请立即请教别人;

  <建立代码互查机制>不要让你的代码敲上“金牌质量”的标签或者经不起你团队中任何人的批评。

1.4.3 打破条条框框

  你只用学过的一种方法编写代码;

  你的产品的功能限制有不自觉的假定;

  你不假思索就抛弃了可能的解决方案;

  开发者对他们的程序都有自己的偏见;

  尝试各种新事物:用违背常理的方法去实验,将发现作为一个程序员或者问题解决大师你将能学到很多不可思议的东西。

 

 

第二章:MySQL存储过程变成指南

  基本的内容:

  怎样创建存储程序;

  存储程序怎样进行输入输出;

  怎样和数据库交互;

  怎样用MySQL存储变成语言创建过程,函数和触发器;

  本章内容仅仅让你对存储程序有一个大体的映像,在后面章节对本章内容进行升华。

 

2.1 所需要的工具

  MySQL 5+ server

  一个文本编辑器

  MySQL Query Browser(GUI TOOLS内的查询工具)

 

2.2 第一个存储过程

  用root账户登录localhost的3306端口,使用预安装的test数据库。

$mysql -uroot -psecret -hlocalhost

2.2.1 创建存储过程

  CREATE PROCEDURE、CREATE FUNCTION、或者CREATE TRIGGER创建存储程序。

DROP PROCEDURE IF EXISTS HelloWorld;
CREATE PROCEDURE HelloWorld()
BEGIN
    SELECT 'Hello,World!';
END;

 

2.3 变量

  本地变量可以用DECLARE语句进行声明。变量名称必须遵循MySQL的命名规则,并且可以是內建的任何数据类型。用DEFAULT子句给变量一个初始值,可以用SET语句给变两赋一个值。

DROP PROCEDURE IF EXISTS variable_demo;
CREATE PROCEDURE variable_deno()
BEGIN
    DECLARE my_integer INT;
    DECLARE my_big_integer BIGINT;
    DECLARE my_currency NUMERIC(8,2);
    DECLARE my_pi    FLOAT    DEFAULT 3.1415926;
    DECLARE my_text    TEXT;
    DECLARE my_varchar VARCHAR(30) DEFAULT 'Hello World!';
    SET my_integer=20;
    SET my_big_integer=POWER(my_integer,3);
END;

 

2.4 参数

  参数可以使存储过程更为灵活实用。把参数放置在紧随过程名的圆括号内,每一个参数都有自己的名称,数据类型还有可选的输入输出模式,有效的模式包括IN(只读模式),INOUT(可读写模式)和OUT(只写模式)。IN模式作为缺省的参数模式。

  MySQL存储程序引入了两种有关参数的不同的特性:

  DECLARE 一个用于创建存储程序内部使用的本地变量。

  SET 一个用来给变量赋值的语句。

DROP PROCEDURE IF EXISTS my_sqrt;
CREATE PROCEDURE my_sqrt(input_number INT)
BEGIN
    DECLARE l_sqrt FLOAT;
    SET l_sqrt=SQRT(input_number);
    SELECT l_sqrt;
END;

2.4.1 参数模式

  MySQL的参数模式可以被定义为IN、OUT和INOUT。

  IN:这是缺省模式,它说明参数可以被传入存储程序内部,但是任何对于该参数的修改都不会被返回给调用它的程序。

  OUT:这个模式意味着存储程序可以对参数复制(修改参数的值),并且这个被修改的值会被返回给它的调用程序。

  INOUT:这个模式意味着程序既可以读取传入的参数,而且任何对于该参数的修改对于它的调用程序而言都是可见的。

DROP PROCEDURE IF EXISTS my_sqrt;
CREATE PROCEDURE my_sqrt(input_number INT,OUT out_number FLOAT)
BEGIN
    SET out_number=SQRT(input_number);
END;

 CALL my_sqrt(10,@number);
 SELECT @Number;

 

  在客户端中,提供了一个用来保存值的OUT参数,当存储过程执行完毕,可以检验这个变量的输出情况。

 

2.5 条件执行

  用IF或者CASE语句来控制存储程序的执行流程(IF和CASE的功能是相同的)。

DROP PROCEDURE IF EXISTS discounted_price;
CREATE PROCEDURE discounted_price(normal_price NUMERIC(8,2),OUT discount_price NUMERIC(8,2))
BEGIN
    IF (normal_price>500) THEN
        SET discount_price = normal_price *0.8;
    ELSEIF (normal_price>100) THEN
        SET discount_price = normal_price *0.9;
    ELSE    
        SET discount_price = normal_price;
    END IF;
END;

CALL discounted_price(1000,@discount_price);
SELECT @discount_price as new_price;

  IF语句允许你测试表达式的真实性,并且基于表达式的结果执行一定的行为,作为一种编程语言,ELSEIF可以被用来作为IF起始循环的条件转移,ELSE子句将在IF和ELSEIF的布尔表达式为假时执行。

 

2.6 循环

  允许存储程序中重复性的执行某些行为,MySQL提供三种类型循环:

  使用LOOP和END LOOP子句的简单循环;

  当条件为真时继续执行的循环,使用WHILE和END WHILE子句;

  循环直至条件为真,使用REPEAT和UNTIL子句。

DROP PROCEDURE IF EXISTS simple_loop;
CREATE PROCEDURE simple_loop()
BEGIN
    DECLARE counter INT DEFAULT 0;
    my_simple_loop:LOOP    
        SET counter=counter+1;
        IF counter=10 THEN        
            LEAVE my_simple_loop;
        END IF;
    END LOOP my_simple_loop;
    SELECT "I can't count to 10";
END;

CALL simple_loop()

  所有在LOOP和END LOOP之间的部分都将在LEAVE子句被执行后终止,LOOP语句带有前缀my_simple_loop的标签,LEAVE子句要求循环被标识,这样才能知道要推出哪个循环。

 

2.7 错误处理

  当存储程序发生错误时,默认的行为是终止程序的执行并把错误返回给它的调用程序,

  如下两个相关联的情景被称为错误处理的定义:

  如果你认为内嵌的SQL语句会返回空记录,或者你想用游标捕获所有SELECT语句所返回的记录,那么一个NOT FOUND错误处理可以防止存储程序过早的被终止;

  入股ONI认为SQL语句可能返回错误,你可以创建一个错误处理来阻止程序终止。这个处理将代替你的默认错误处理并继续程序的执行。

 

2.8 和数据库交互

  四种主要的交互:

  将一个SQL语句所返回的单个记录放入本地变量中;

  创建一个“游标”来迭代SQL语句所返回的结果集;

  执行一个SQL语句,将执行后的结果集返回给它的调用程序;内奸呢一个不范湖结果集的SQL语句,如INSERT、UPDATE、DELETE等。

2.8.1 对本地变量实用SELECT INTO

  当需要在单个记录数据中获取查询信息,可以使用SELECT INTO语法。可以在SELECT 语句中跟随一个INTO子句,告诉MySQL得到的查询数据返回给谁。

DROP PROCEDURE IF EXISTS customer_Sales;
CREATE PROCEDURE customer_sales(in_customer_id INT)
    READS SQL DATA
BEGIN
    DECLARE total_sales NUMERIC(8,2);
    SELECT SUM(sale_value)
        INTO total_sales
    FROM sales
    WHERE customer_id=in_customer_id;
    SELECT CONCAT('Total sales for ',in_customer_id,' is ',total_sales);
END;

CALL customer_sales(2);

2.8.2 使用游标

  SELECT INTO定义了单记录查询,很多应用程序要求查询多记录数据,可以使用MySQL游标来实现这一功能,游标允许将一个或更多的SQL结果集放进存储程序变量中,通常用来执行结果集中各个记录的处理。

DROP PROCEDURE IF EXISTS cursor_example;
CREATE PROCEDURE cursor_example()
    READS SQL DATA
BEGIN
    DECLARE employee_id INT;
    DECLARE salary NUMERIC(8,2);
    DECLARE department_id INT;
    
    DECLARE done    INT DEFAULT 0;
    
    DECLARE cur CURSOR FOR
    SELECT employee_id,salary,department_id
    FROM employees;
    
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1;
    
    OPEN cur;
    emp_loop:LOOP
        FETCH cur INTO employee_id,salary,department_id;
        IF done=1 THEN    
            LEAVE emp_loop;
        END IF;
    END LOOP emp_loop;
    CLOSE cur;
END;

 

  检测变量done的值,如果它被设置成1,那么久说明我们已经获取最后一个数据,之后就用LEAVE语句来终止循环。

2.8.3 返回结果集的存储过程

  可以在存储过程中包含一些复杂的SQL语句来返回多个结果。

  如下致命这样的存储过程,结果集将像我们执行SELECT或SHOW语句一样被返回。展示了包含沉长的SELECT语句的存储程序。

DROP PROCEDURE IF EXISTS sp_emps_in_dept;
CREATE PROCEDURE sp_emps_in_dept(in_employee_id INT)
BEGIN
    SELECT employee_id,surname,firstname,address1,address2,zipcode,date_of_birth
    FROM employees    
    WHERE department_id=in_employee_id;
END;

  存储过程调用可能返回多个结果集。

2.8.4 內建不反悔结果的SQL语句

  不返回结果集的SQL语句也可以被嵌入存储程序中,包含DML(数据操纵语言)如UPDATE、INSERT、DELETE以及DDL(数据定义语言)如CREATE TABLE等都可被包括在内。

DROP PROCEDURE IF EXISTS sp_update_salary;
CREATE PROCEDURE sp_update_salary(in_employee_id INT,in_new_salary NUMERIC(8,2))
BEGIN
    IF in_new_salary<5000 or in_new_salary>5000000 THEN
        SELECT 'Illegal salary,Salary must be between $5000 and $500000.';
    ELSE    
        UPDATE employees
        SET salary=in_new_salary
        WHERE employee_id=in_employee_id;
    END IF;
END;

 

2.9 在其他存储程序中调用存储程序

DROP PROCEDURE IF EXISTS call_example;
CREATE PROCEDURE call_example(employee_id INT,employee_type VARCHAR(20))
    NO SQL
BEGIN
    DECLARE bonux_amount NUMERIC(8,2);
    IF employee_type='MANAGER' THEN
        CALL calc_manager_bonus(employee_id,bonux_amount);
    ELSE    
        CALL calc_minion_bonux(employee_id,bonux_amount);
    END IF;
    CALL grant_bonus(employee_id,bonus_amount);
END;

 

2.10 把所有的东西组装起来  

CREATE PROCEDURE putting_it_all_together(in_department_id int)
    MODIFIES SQL DATA
BEGIN
    DECLARE employee_id INT;
    DECLARE salary NUMERIC(8,2);
    DECLARE department_id INT;
    DECLARE new_salary NUMERIC(8,2);
    DECLARE done    INT DEFAULT 0;
    
    DECLARE cur CURSOR FOR    
        select employee_id,salary,department_id
        FROM employees    
        WHERE department_id=in_department_id;
    
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1;
    
    CREATE TEMPORARY TABLE IF NOT EXISTS emp_raise
        (employee_id INT,department_id INT,new_salary NUMERIC(8,2));

OPEN cur;

    emp_loop:LOOP
        FETCH cur INTO employee_id,salary,department_id;
        IF done=1 THEN
            LEAVE emp_loop;
        END IF;
        CALL new_salary(employee_id,new_salary);
        IF (new_salary<>salary) THEN
            UPDATE employees
            SET salary=new_salary
            WHERE employee_id=employee_id;
        
        INSERT INTO emp_raises(employee_id,department_id,new_salary)
        VALUES (employee_id,department_id,new_salary);
        END IF;
    END LOOP emp_loop;
CLOSE cur;
    
    SELECT employee_id,department_id,new_salary from emp_raise order by employee_id;
END;    

 

2.11 存储函数

  存储函数与存储过程很相似:都是包含一个或多个语句的被命名程序单元。不同的地方有:

    函数的参数列表模式只能为IN。OUT和INOUT不被允许。IN关键字是被允许也是默认的;

    函数必须返回一个值,它的类型被定义于函数的头部;

    函数能被SQL语句所调用;

    函数可能不反悔任何结果集。

DROP FUNCTION IF EXISTS discount_price;
CREATE FUNCTION discounted_price
    (normal_price NUMERIC(8,2))
    RETURNS NUMERIC(8,2)
    DETERMINISTIC
BEGIN
    DECLARE discount_price NUMERIC(8,2);
    IF (normal_price>500) THEN
        SET discount_price = normal_price *0.8;
    ELSEIF (normal_price>100) THEN
        SET discount_price = normal_price *0.9;
    ELSE    
        SET discount_price = normal_price;
    END IF;
    RETURN(discounted_price);
END;

  作为函数的定义,指定RETURNS子句,定义了函数的返回类型。函数必须声明不修改SQL(使用NO SQL或者READS SQL DATA子句)或者声明为DETERMINISTIC(如果服务器被允许开启二进制日志)。这个限制为了防止当函数返回不确定值时,数据库同步复制的不一致性。用RETURN语句返回IF语句计算出的结果。

 

2.12 触发器

  是在数据库表被INSERT、UPDATE或DELETE等DML语句所作用时激活的特殊的存储程序。只要表发生改变就会激活触发器的功能,因为触发器直接依附于表,所以程序代码无法绕过数据库触发器(没有办法让触发器失效)。触发器被用来实现严格的商业逻辑,高效的反向格式化数据和审核表的更改状况。触发器可以被定义出发于特定的DML语句执行前或之后。

 

  

 第三章:语言基础

  MySQL存储程序语言是一种块结构语言,包含用来控制变量,实现条件执行,进行迭代处理和错误处理的语句。

  本章关注基本要素:存储程序语言的块,字面变量,参数,注释,操作符,表达式和数据类型。

 

3.1 变量,字面量,参数和注释

3.1.1 变量

  变量是一个值可以在程序执行过程中被改变的命名数据项。字面量是一个可以被复制给变量的未命名数据项。字面量是程序中的硬代码,并通常拿来复制给变量,传递给参数,或者作为SELECT语句的参数。

  DECLARE语句创建变量。DECLARE语法如下:

 

DECLARE variable_name [,variable_name...] data_type [DEFAULT value];

 

  多个变量可以在一个DECLARE语句中被声明,而且变量可以给出一个默认值,如果不给出DEFAULT子句,那么这个变量将会被赋予空值。否则任何依赖于这个变量的后续操作都将在赋值之前返回NULL。datatype是CREATE TABLE语句中使用的有效MySQL数据类型。

 

   常用MySQL数据类型:

  INT,INTEGER  32位整数。取值范围-21亿到+21亿,如果是非符号数,值可以达到21亿,但这样做不能包括负数;

  BIGINT  64位整数。取值范围-9万亿到+9万亿或者非负的0到18万亿;

  FLOAT  32位浮点数。取值范围1.7e38到-1.7e38或者非负的0到3.4e38;

  DOUBLE  64位浮点数。取值范围接近无限;

  DECIMAL(precision,scale)  

  NUMERIC(precision,scale)  定点数。存储情况取决于precsion,能保存可能出现的数字范围。NUMERIC通常用来保存重要的十进制数,例如货币数字;

  DATE  日期类型,没有详述时间;

  DATETIME  日期和时间,时间精确到秒;

  CHAR(length)  定长字符串,值会被空白填充至定长度,最大长度为255字节;

  VARCHAR(length)  边长字符串。最大长度为64k的可变字符串;

  BLOB,TEXT  最大64K长度,BLOB用来保存2进制数据,TEXT用来保存文本数据;

  LONGBLOB,LONGTEXT  BLOB和TEXT的加长版本,存储能力达4GB。

3.1.2 字面常量

  字面常量是程序中的硬代码。通常可以将字面常量用于赋值语句和条件比对(比如IF),存储过程,函数的参数或者SQL语句中。

  三大基本字面量类型:

  数字字面常量。

  日期字面常量。

  字符字面常量,是任何被简单的包含在单引号中的值。转义序列字符(\'表示单引号,\t表示tab,\n表示换行,\\表示反斜杠等)。

3.1.3 变量命名规则

  MySQL命名灵活,区别其他大多数变成语言。但还是建议使用通用明智的命名习惯,避免使用过长的变量名。

3.1.4 变量赋值

  可用SET语句操作变量赋值,如下语法:

 

SET variable_name=expression [,variable_name=expression...]

 

  SET语句可完成多次赋值。经常用于变量的初始化,很容易造成不适用具体的SET来的对变量赋值时造成错误。

3.1.5 参数

  参数是可以被主叫程序传入或传出于存储程序的变量。参数被函数或过程创建时定义于CREATE语句内,如下所示;

CREATE PROCEDURE | FUNCTION (
    [[ IN
| OUT
|INOUT
] parameter_name date_type...])

  参数可以附加上IN,OUT,INOUT属性:

  IN:除非被具体定义,否则参数都假定IN属性。意味着它们的值必须被主叫程序所指定,并且任何在存储程序内部对该参数的修改都不能再主叫程序中起作用。

  OUT:一个OUT参数可以被存储程序所修改,并且这个被修改的值可以在主叫程序中生效。主叫程序必须提供一个变量来接受由OUT参数输出的内容。但是存储程序本身并没有对这个可能已经初始化的变量的操作权限,当存储程序开始时,任何OUT变量的值都被复制为NULL,不管这个值在主叫程序中是否被赋予其他值。

  INOUT:INOUT参数同时扮演着IN和OUT参数的角色,意味着,主叫程序可以提供一个值,而被叫程序自身可以修改这个参数的值,在存储函数中所有的参数都被视为IN参数。

  首先,MySQL语序修改In参数,但这种修改在主叫程序中并不可见,下例打印并修改了参数的值,单号存储程序内部对于输入参数的修改被允许时,原本的变量(@p_in)并没有改变:

CREATE PROCEDURE sp_demo_in_parameter(in p_in int)
BEGIN
    /* We can see the value of the IN parameter */
    SELECT p_in;
    /* We can modify it */
    SET p_in=2;
    /* Show that the modification took effect */
    SELECT p_in;
END;
DROP PROCEDURE IF EXISTS sp_demo_in_parameter;

SET @p_in=1;
CALL sp_demo_in_parameter(@p_in);

  接下来,验证OUT参数的行为。虽然主叫程序已经初始化了OUT参数的值,但是被叫程序无法看到这个值。无论如何,主叫程序只有在被叫程序执行完成后才能看到参数的改变。

CREATE PROCEDURE sp_demo_out_parameter(OUT p_out INT)
BEGIN
    /* We can't see the value of the OUT parameter */
    SELECT p_out,'we can''t see the value of the out parameter';
    /* We can modify it */
    SET p_out=2;
    SELECT p_out,'OUT parameter value has been changed';
END;
DROP PROCEDURE IF EXISTS sp_demo_out_parameter;

SET @p_out=1;
CALL sp_demo_out_parameter(@p_out);
SELECT @p_out,"calling program can see the value of the changed OUT parameter";

  最后,演示INOUT参数的值,可以为我们的被叫程序锁见,所修改并范湖给它的主叫程序。

CREATE PROCEDURE sp_demo_inout_parameter(INOUT p_inout INT)
BEGIN
    select p_inout,'We can see the value of the INOUT parameter in the stored program';
    SET p_inout=2;
    SELECT p_inout,'INOUT parameter value has been changed';
END;
DROP PROCEDURE IF EXISTS sp_demo_inout_parameter;

SET @p_inout=1;
CALL sp_demo_inout_parameter(@p_inout);
SELECT @p_inout,"calling program can see the value of the changed INOUT parameter";

3.1.6 用户变量

  用户变量是在MySQL中定义并且可以在存储程序中或存储程序之外被操作的变量。两种方法来使用用户变量:

  因为用户变量具有独立于存储程序个体的作用域,可以用来描述那些能够被任何存储程序锁读写的会话。有些接近其他编程语言中全局变量的原理。

  用户变量可以给方法传递参数以第二种选择,存储程序对用户变量具有读写权限,这样可以避免使用参数传值的必要。

SELECT 'Hello World!' INTO @x;
SELECT @x;
-- 
SET @y='GoodBye Cruel World1';
SELECT @y;

  可以在当前会话里从存储程序中使用任何用户变量,如下展示如何不适用过程参数向存储过程传递信息,使用用户变量在主叫程序和被叫程序之间传递信息。

CREATE PROCEDURE greetworld()
SELECT concat(@greeting,'World!');

SET @greeting='hello';
CALL greetworld();

  也可以用一个存储程序创建用户变量。会使该变量在其他存储程序中都可用。举例来说,在过程p1创建一个用户变量,对过程p2也可见:

CREATE PROCEDURE p1()
SET @last_procdure='p1';
CREATE PROCEDURE p2()
SELECT CONCAT('Last prcedure was ',@last_procdure);
CALL p1();
CALL p2();

  用户变量是一种可变数据类型。用户变量存在于一个持续的MySQL会话中,在此会话中任何程序和语句都可以访问该用户变量。当然,别的会话则无法访问它。

  使用用户变量去实现跨越读个存储程序的变量在某些场合会非常有用,但是,必须明确这样做的目的,并谨慎使用。过度的使用作用域超越单个程序的全局变量会让你的代码不易读且难于维护。使用这些变量的例程会变得高耦合并难以维护,测试和理解。

3.1.7 注释

  -- 两个连字符跟上一个空格穿件一个到当前行末的注释。

  C语言风格的注释,用/*开始,用*/结束。称为多行注释。

/*
| Program:
| Purpose:
| Author:
| Change History:
*/

 

3.2 操作符

  操作符经常是和SET语句一起来改变变量的值,和比较语句如IF或者CASE,和循环控制表达式使用。

3.2.1 数学操作符

  + - * /

  DIV  整除,返回会发操作的整数部分;

  %  模,返回整除后的余数的部分。

3.2.2 比较操作符

  比较操作符比较两个值并返回TRUE、FALSE、UNKNOWN(通常如果一个值被比较后返回NULL或者UNKNOWN)。

  <   >  <=  >=  BETWEEN   NOT BETWEEN  IN   NOT IN  =  <>  !=  LIKE  REGEXP  IS NULL   IS NOT NULL  

  <=>  NULL安全等于(如果两个值均为NULL,则返回TRUE)。

SELECT NULL=NULL,NULL<=>NULL;

3.2.3 逻辑操作符

  AND操作符比较两个逻辑表达式,并且只有在两个表达式都为真时才返回TRUE。

  OR操作符比较两个逻辑表达式,并且只要其中一个表达式为真即范湖TRUE。

  XOR操作符只有在两个值不完全为真时才返回TRUE。  

  对于大多逻辑操作符而言,如果其中任何值被比较得出为NULL,那么最终的结果就位NULL,这个事实很重要,否则,你的代码可能隐含一些微小的错误。

3.2.4 位操作符

 

3.3 表达式

  表达式是字面量,变量和操作符的集合。

 

3.4 內建函数

  MySQL函数被归类为几个类型:字符串函数、数学函数、日期和时间函数、其他函数(如类型转换、流程控制、信息反馈和加密函数)。

  ABS()

  CEILING()

  CONCAT()

  CURDATE()

  DATE_ADD()

  DATE_SUB()

  FORMAT()

  GREATEST()

  IF()

  IFNULL()

  INSERT()

  INSTR()

  ISNULL()

  LEAST()

  LEFT()

  LENGTH()

  LOCATE()

  LOWER()

  LPAD()

  LTRIM()

  MOD()

  NOW

  POWER()

  RAND()

  REPEAT()

  REPLACE()

  ROUND()

  RPAD()

  RTRIM()

  SIGN()

  SQRT(number)  返回number的平方根

  STRCMP(string1,string2)  如果两个值相同则返回0,若根据当前分类次序,第一个参数小于第二个,则返回-1,其他情况返回1

  SUBSTRING(string,position,length)  从字符串指定位置开始返回length个字符  

  UPPER(string)  将指定字符串转换为大写

  VERSION  返回服务器当前版本号字符串

 

3.5 数据类型

  在MySQL存储程序中的所有变量都是单纯的标量,也就是变量存储的只是单纯的个体,存储程序中没有数组等。

3.5.1 字符串数据类型  

  CHAR和VARCHAR。CHAR存储定长字符串,VARCHAR存储可变长度字符串。如果CHAR变量被赋予小煜其生命长度的值,那么将空白填充至声明长度。

  在MySQL表中,CHAR和VARCHAR的选择非常重要,直接关系到磁盘存储空间需求。在存储程序中,额外的内存需求将会最小化。

  CAHR数据类型最大可以存放255个字节的数据,而VARCHAR最大可以存放65532字节的数据。

3.5.1.1 枚举数据类型

  存放一系列被允许的值,这些值可以用他们的字符串或者他们在这一列数据中的索引值进行访问。

 

CREATE PROCEDURE sp_enums(in_option ENUM('Yes','No','Maybe')
BEGIN
    DECLARE position INTEGER;
    SET position=in_option;
    SELECT in_option,position;
END;

 

3.5.1.2 SET数据类型

  SET类似枚举,但可在SET中插入多个列表中的值。

3.5.2 数字数据类型

  MySQL支持两种族系的数字类型:

  精确数据类型比如INT和DECIMAL类型;近似数字类型比如FLOAT。

3.5.3 日期和时间数据类型

3.5.4 TEXT和BLOB数据类型

 

 

 

第四章:程序块,条件表达式和迭代编程

  介绍MySQL语言程序创建过程中作用域的空值和流程控制。

  迭代控制结构或者说是循环体,提供三种不同的循环控制:

  简单循环;REPEAT UNTIL循环;WHILE循环。

 

4.1 存储程序的块结构

  每个程序块都由BEGIN语句开始,END语句结束。如CREATE PROCEDURE、FUNCTION、TRIGGER。

  CREATE {PROCEDURE|FUNCTION|TRIGGER} program_name

  BEGIN

    program_statements

  END;

  使用程序块有两个原因:

  将逻辑相关的代码部分放在一起;控制变量和作用域。(可以在一个块中定义一个变量,这样在块的外部就无法看到这个变量。其次,可以在一个块的内部顶一个一个覆盖块概不同名变量的变量)。

4.1.1 块的结构

  一个块由多种不同的声明和程序代码组成,之间的顺U型如下:

  1.  变量和条件声明,

  2.  游标声明

  3.  处理单元声明

  4.  程序代码

  在快中语句的顺序必须是变量和条件,接下来是游标,然后是一场处理,最后是其他语句。也可以给块命名一个标签label。标签必须同时出现在BEGIN语句之前和END语句之后,第一有助于改善diamante的可读性,快速找到代码块;允许使用LEAVE语句终止程序块的执行。

[lavel:]BEGIN    
    variable and condition declarations;
    cursor declarations;
    handler declarations;
    program code;
END[label];

4.1.2 嵌套块

  在块中声明的变量在块的外部不可用,但是对于此块中定义的嵌套块却是可见的。可以在块中覆盖定义“外部”变量,并且可以在不影响外部变量的情况下操作这个内部变量。这种情况下降低代码的可读性,一般来说不要使用这种覆盖变量的定义。请避免在内部覆盖定义外部快中以存在的变量。

 

4.2 条件控制

4.2.1 IF语句

  语法是:

IF expression THEN commands
    [ELSEIF expression THEN commands ...] 
    [ELSE commands]
END IF;

  不要假设测试表达式的结果不是TRUE就是FALSE。在表达式中的任何一个变量为NULL时也可以返回NULL(NUKNOWN)。

4.2.1.2 简单的IF-THEN组合

4.2.1.3 IF-THEN-ELSE语句

4.2.1.4 IF-THEN-ELSEIF-ELSE语句

 

IF expression THEN 
    statements that execute if the expression is TRUE 
ELSEIF expression THEN 
    statements that execute if expression1 is TRUE 
ELSE statements that execute if all the preceding expressions are FALSE or NULL 
END IF;

 

4.2.2 CASE语句

  CASE语句通常更可读并且在处理多个测试条件时更有效,特别是当所有的条件都输出比对同一个表达式时。

4.2.2.1 简单CASE语句

CASE expression 
    WHEN value THEN statements 
    [WHEN value THEN statements ...] 
    [ELSE statements] 
END CASE;

4.2.2.2 查询CASE语句

CASE
WHEN condition THEN statements 
    [WHEN condition THEN statements...] 
    [ELSE statements] 
END CASE;

4.2.3 IF和CASE的比较

  如果CASE或IF结构中的条件得到满足,则别的条件将不再得到测试,这意味着你的条件排放顺序需要相当的严格;

  MySQL存储程序语言使用三值逻辑,因此若是一个语句是非真则并不意味着他必定为FALSE,也可能是NULL;

  考虑代码的可读性。避免嵌套很深的代码。

 

4.3 循环中迭代处理

  基于许多原因促使程序需要迭代:

  程序可以提供一个可以进行主循环的借口进行等待,然后处理,接受用户输入;

  数学算法只有使用计算机程序中的循环来实现;

  当处理一个文件时,程序应该在文件中的每一条记录间循环并进行处理;

  一个数据库程序应该再记录间循环并使用SELECT语句进行返回。

4.3.1 循环语句

[label:] LOOP 
    statements 
END LOOP [label];

  在LOOP和END LOOP之间的语句会无限循环,可以使用LEAVE语句来终止LOOP。存储程序是在数据库服务器内部运行的,所以使用Ctrl+C或者其他例如KILL命令来终止MySQL会话进程的方法来将其结束,或者关闭数据库服务器。同时循环会占用大量的CPU资源。

4.3.2 LEAVE语句

  语法:LEAVE label;

  LEAVE会使当前循环终止,标签匹配了要终止的循环。

4.3.3 ITERATE语句

4.3.4 REPAET...UNTIL循环

  创建一直重复直到遇到某些逻辑条件才终止的循环,语法如下:

 

[label:] REPEAT 
    statements 
    UNTIL expression 
END REPEAT [label];

 

  UNTIL语句总是伴随着END REPEAT子句出现在循环的最低端。

4.3.5 WHILE循环

  WHILE循环只有在条件为真时才执行循环,如果条件不为真,那么循环体将永远得不到执行。语法如下:

[label:] WHILE expression DO 
    statements 
END WHILE [label];

4.3.6 嵌套循环

DECLARE i,j INT DEFAULT 1; 
outer_loop: LOOP 
    SET j=1; 
    inner_loop: LOOP 
        SELECT concat(i," times ", j," is ",i*j); 
        SET j=j+1; 
            IF j>12 THEN 
                LEAVE inner_loop; 
            END IF; 
    END LOOP inner_loop; 
    SET i=i+1; 
    IF i>12 THEN 
        LEAVE outer_loop; 
    END IF; 
END LOOP outer_loop;

4.3.7 对循环的部分注释

 

 

 

 

第五章:在存储变成中使用SQL

  在存储程序中使用SQL的方法:

  简单SQL语句是一种可以被轻松嵌入存储程序中的不反悔结果集的语句;

  一个SELECT语句可以将返回的单个记录使用INTO传入本地变量;

  一个SELECT语句返回多个记录时,可以使用游标在各个记录之间循环;

  任何SELECT语句都可以使用INTO子句和CURSOR语句被包含在存储过程中,

  SQL语句可以在服务端动态的被使用。

 

5.1 在存储程序中使用非SELECT SQL语句

CREATE PROCEDURE simple_sqls( ) 
BEGIN 
DECLARE i INT DEFAULT 1; 
/* Example of a utility statement */ 
SET autocommit=0; 
/* Example of DDL statements */ 
DROP TABLE IF EXISTS test_table ; 
CREATE TABLE test_table (id INT PRIMARY KEY, some_data VARCHAR(30)) ENGINE=innodb; 
/* Example of an INSERT using a procedure variable */ 
    WHILE (i<=10) DO 
    INSERT INTO TEST_TABLE VALUES(i,CONCAT("record ",i)); 
    SET i=i+1; 
    END WHILE; 
/* Example of an UPDATE using procedure variables*/ 
SET i=5; 
UPDATE test_table 
SET some_data=CONCAT("I updated row ",i) 
WHERE id=i; 
/* DELETE with a procedure variable */ 
DELETE FROM test_table WHERE id>i; 
END;

 

5.2 在SELECT语句中使用INTO子句

  可在SELECT语句中使用INTO语句将返回值传递给存储程序的变量,格式:

SELECT expression1 [, expression2 ....] 
INTO variable1 [, variable2 ...] 
other SELECT statement clauses;

CREATE PROCEDURE get_customer_details(in_customer_id INT) 
BEGIN 
    DECLARE l_customer_name VARCHAR(30); 
    DECLARE l_contact_surname VARCHAR(30); 
    DECLARE l_contact_firstname VARCHAR(30); 
    
    SELECT customer_name, contact_surname,contact_firstname 
    INTO l_customer_name,l_contact_surname,l_contact_firstname 
    FROM customers WHERE customer_id=in_customer_id; 
    /* Do something with the customer record */ 
END;

 

5.3 创建和使用游标

  当处理一个返回多个记录的SELECT语句时,必须为其创建和操纵一个游标。使用游标对结果集中的每条记录进行迭代并且赋予他们各个结果的不同行为。

5.3.1 定义游标

  使用DECLARE语句来定义游标,语法如下(游标的声明必须在我们所有的变量声明之后):

DECLARE cursor_name CURSOR FOR 
    SELECT_statement;

5.3.2 游标语句

  MySQL存储程序支持三种对游标的操作语句:

  OPEN:初始化游标的结果集,必须在从游标中获取结果之前打开游标,语法很简单,OPEN cursor_name;

  FETCH:获取游标中的下一个记录并把游标在结果集中的“指针”下移。语法如下:FETCH cursor_name INTO variable list;

  CLOSE:解除游标并释放游标所占用的内存。语法如下:CLOSE cursor_name;

5.3.3 从游标中获取单条记录

5.3.4 获取所有记录集

  声明一个错误处理单元来捕获“no data to fetch”

  DECLARE CONTINUE HANDLER FOR NOT FOUND SET L_last_row_fetched=1;

  这个处理单元会促使MySQL遇到“No data to fetch”时做两件事情:

    1.设定变量“last row variable”的值为1;2.允许程序继续执行。

  可以检测变量L_last_row_fetched值是否为1,就知道最后一行记录是否已被获取,于是就可以终止循环关闭游标。当关闭游标后把“end of result set”指示器复位,否则下一次尝试时从游标获取数据时,程序将立即终止执行。这就是这段SQL代码的用意。

 

DECLARE CONTINUE HANDLER FOR NOT FOUND SET l_last_row_fetched=1; 
SET l_last_row_fetched=0; 
OPEN cursor1; 
cursor_loop:LOOP 
    FETCH cursor1 INTO l_customer_name,l_contact_surname,l_contact_firstname; 
    IF l_last_row_fetched=1 THEN 
        LEAVE cursor_loop; 
    END IF; 
/*Do something with the row fetched*/ 
END LOOP cursor_loop; 
CLOSE cursor1; 
SET l_last_row_fetched=0;

 

5.3.6 嵌套游标循环

5.3.8 游标错误条件

  游标语句必须依循OPEN-FETCH-CLOSE的顺序。

 

5.4 使用非受限SELECT语句

  存储程序可以将结果集返回给调用程序。这些包括SELECT、SHOW、EXPLAIN、DESC等SQL语句在内。

  在某些方面,使用存储过程来返回结果集的功能和特定的查询创建视图有些相似。和视图一样,存储过程时一个封装复杂的SQL操作,这样就是用户不需要懂得复杂的结构设计就可以获得简单的数据。将SQL封装进存储过程同样可以改善安全性。和视图不同的是,存储过程可以返回多个结果集。

5.4.2 向另一个存储过程返回结果集

  要将结果集从一个存储过程传递给另一个的唯一方法就是将其传递给一个临时表。

  临时表的作用域仅与创建该表的会话具有相同的作用域,并且它会在会话借宿时自动被清除。

 

5.5 使用预处理语句执行动态SQL

  支持一项功能名为“服务器端预处理语句”,被用于提供独立于API的,在反复执行过程中具备高效性和安全性的SQL语句。从编程的角度看,预处理语句具备很大的优势,因为它允许你创建动态SQL调用。

  使用PREPARE语句来创建预处理语句:

PREPARE statement_name FROM sql_text;

  当SQL执行前必须在SQL文本中包含数据值的占位符。占位符用字符?表示。预处理语句使用EXECUTE语句进行执行:

EXECUTE statemnt_name [USING variable,[,variable...]]

  USING子句可以为PREPARE语句中的占位符提供指定的值。这些值必须是用户变量(以@为前缀字符),最后可以用DEALLOCATE语句撤销预处理语句

DEALLOCATE PREPARE statement_name;
PREPARE prod_insert_stmt FROM "INSERT INTO product_codes VALUES(?,?)";
SET @code='QB';
SET @name='MySQL Query Browser';
EXECUTE prod_insert_stmt USING @code,@name;
DEALLOCATE PREPARE prod_insert_stmt;

  预处理语句减少了SQL语句中少数数据值改变时重新解析(预处理)的开销,并且因为允许使用SQL语句参数,从而防止了SQL注入。存储过程并不需要预处理语句,因为在存储过程中的SQL语句在执行前已经被“预处理”。

  预处理语句在存储过程中还是炙手可热,因为它允许你在过程中执行动态SQL。

 

5.6 SQL错误处理:预览

  通常,如果一个存储程序中的SQL语句发生了错误,那么存储程序会停止并把是错误返回给它的调用程序。如果不希望发生这种情况,必须用如下语法指定一个错误处理单元:

DECLARE {CONTINUE | EXIT} HANDLER FOR 
    {SQLSTATE sqlstate_code| MySQL error code| condition_name} 
    stored_program_statement

 

  

 

 

  

 

  

  

 

  

 

  

  

 

  

 

 

 

 

  

  

  

 

 

  

 

 

 

 

 

 

 

转载于:https://www.cnblogs.com/pythonMLer/p/5943351.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值