mysql进阶之存储过程

lichh321 8年前
   <p><img src="https://simg.open-open.com/show/c842727c21a9008267f69cd250a50e1f.jpg"></p>    <p>往往看别人的代码会有这样的感慨:</p>    <p>看不懂</p>    <p>理还乱</p>    <p>是离愁</p>    <p>别是一番滋味在心头</p>    <h2><strong>为什么要使用存储过程?</strong></h2>    <p>在mysql开发中使用存储过程的理由:</p>    <ol>     <li> <p>当希望在不同的应用程序或平台上执行相同的函数,或者封装特定功能时,存储过程是非常有用的</p> </li>     <li> <p>mysql 执行语句是要先编译,然后再执行的。这样如果查询并发大的时候。会浪费很多资源和时间。造成mysql进程占用资源过多,症状就是慢。但存储过程可以把一些特别的语句封装成一个方法 ,再编译好成一个可以执行的方法,对外只要接收参数就可以了。这样就不用再编译。执行就快了。你觉得你数据库因为同时出现太多读写操作而变得慢 ,那么就要用了</p> </li>     <li> <p>使用了存过程,很多相似性的删除,更新,新增等操作就变得轻松了,并且以后也便于管理!</p> </li>     <li> <p>存储过程因为SQL语句已经预编绎过了,因此运行的速度比较快。</p> </li>     <li> <p>存储过程可以接受参数、输出参数、返回单个或多个结果集以及返回值。可以向程序返回错误原因。</p> </li>     <li> <p>存储过程可以接受参数、输出参数、返回单个或多个结果集以及返回值。可以向程序返回错误原因。</p> </li>     <li> <p>存储过程运行比较稳定,不会有太多的错误。只要一次成功,以后都会按这个程序运行。</p> </li>     <li> <p>存储过程主要是在服务器上运行,减少对客户机的压力。</p> </li>     <li> <p>存储过程可以包含程序流、逻辑以及对数据库的查询。同时可以实体封装和隐藏了数据逻辑。</p> </li>     <li> <p>存储过程可以在单个存储过程中执行一系列SQL语句。</p> </li>     <li> <p>存储过程可以从自己的存储过程内引用其它存储过程,这可以简化一系列复杂语句。</p> </li>    </ol>    <h2><strong>存储过程案例</strong></h2>    <p>让我们使用一些简单的示例来了解什么是存储过程, <strong>使用下边演示程序的前提是必须正确安装了mysql。</strong></p>    <p>下边的程序来源于 <strong>[PHP和MySQL WEB开发(4th)]</strong> 这本书,我们使用mysql中的books数据库,这个数据库中的表有一下几个:</p>    <pre>  <code class="language-sql">mysql> show tables;  +-----------------+  | Tables_in_books |  +-----------------+  | book_reviews    |  | books           |  | customers       |  | order_items     |  | orders          |  +-----------------+  5 rows in set (0.00 sec)</code></pre>    <p>我们用到了orders这个表:</p>    <pre>  <code class="language-sql">mysql> select * from orders;  +---------+------------+--------+------------+  | orderid | customerid | amount | date       |  +---------+------------+--------+------------+  |       1 |          3 |  69.98 | 2007-04-02 |  |       2 |          1 |  49.99 | 2007-04-15 |  |       3 |          2 |  74.98 | 2007-04-19 |  |       4 |          3 |  24.99 | 2007-05-01 |  |       5 |          3 |  69.98 | 2007-04-02 |  |       6 |          1 |  49.99 | 2007-04-15 |  |       7 |          2 |  74.98 | 2007-04-19 |  |       8 |          3 |  24.99 | 2007-05-01 |  |       9 |          2 |  69.98 | 2008-09-02 |  |      10 |          3 |  45.90 | 2009-09-09 |  +---------+------------+--------+------------+  10 rows in set (0.00 sec)</code></pre>    <h3><strong>示例1</strong></h3>    <p>程序 basic_stored_procedure.sql :</p>    <pre>  <code class="language-sql"># Basic stored procedure example    delimiter //    create procedure total_orders (out total float)  begin   select sum(amount) into total from orders;  end  //    delimiter ;</code></pre>    <p>打开终端并启动mysql,使用有权限的账户登录mysql,使用某个数据库。上边的程序 basic_stored_procedure.sql 是封装在一个后缀为sql的文件中的,因此要使用mysql调用这个文件。</p>    <p>使用命令 source /path/basic_stored_procedure.sql ,path是你保存这个文件的路径。</p>    <p>然后使用命令 call total_orders(@t); 。可以看出,使用关键字call来调用该过程。 call total_orders(@t); 这个语句将调用total_orders过程并传入一个用来保存结果的变量。要查看该结果,需要查看该变量。使用下边的语句:</p>    <p>select @t;<br> 结果为:</p>    <pre>  <code class="language-sql">mysql> call total_orders(@t);  Query OK, 1 row affected (0.01 sec)    mysql> select @t;  +------------------+  | @t               |  +------------------+  | 555.760009765625 |  +------------------+  1 row in set (0.00 sec)</code></pre>    <p>现在我们逐行分析程序 basic_stored_procedure.sql 中的代码:</p>    <ul>     <li>elimiter // 将语句末尾的分隔符从当前值(这个分隔符通常是分号,除非以前改变了分隔符)改为双斜杠字符。 <strong>这样做的目的是可以在存储过程中使用分号分隔符,</strong> 这样mysql就会将分号当做是存储过程的代码,不会执行这些代码</li>     <li> <p>create procedure total_orders (out total float) 创建了实际的存储过程,该存储过程的名称是total_orders。他只有一个total参数,该参数是需要计算的值。out表示该参数将被传出或返回</p>      <ul>       <li>参数也可以声明为in,表示该值必须传入到存储过程。inout表示该值必须被传入,但可以被存储过程修改</li>       <li>float 表示参数的类型。在这个例子中将返回所有订单的总数。orders列的类型为float,因此该返回类型也必须是float。可接受的数据类型映射到可供使用的列类型</li>       <li>如果希望使用多个参数,可以提供一个由逗号间隔的参数列表</li>      </ul> </li>     <li> <p>BEGIN 和 END 就好比函数中的 { } ,用来标识一个语句块</p> </li>     <li>select sum(amount) into total from orders; 这就是我们实际中使用的查询语句</li>     <li>delimiter ; delimiter重新把分隔符定义为分号 ;</li>    </ul>    <h3><strong>示例2</strong></h3>    <p>示例1中是使用的 procedure 创建过程的方法,在示例2中,我们将使用 function 来创建函数。函数接受参数并返回一个唯一值。</p>    <p>程序 basic_function.sql :</p>    <pre>  <code class="language-sql"># Basic syntax to create a function    delimiter //    create function add_tax (price float) returns float    return price*1.1;    //    delimiter ;</code></pre>    <ul>     <li>参数不必通过in或out指定,因为所有参数都是in,或是输入参数。</li>     <li>在参数列表之后是 returns float ,它指定了返回值的类型。 <strong>该值可以使任何有效的mysql类型</strong></li>     <li>return price*1.1; 使用 return 可以返回一个值</li>     <li>这里并没有使用 begin 和 end 。可以使用它们,但他们不是必须的。就像php或者c中,如果一个语句块只含有一个语句,可以以调用内置函数的相同方式调用一个存储函数</li>    </ul>    <p>使用方法示例1中有所不同。</p>    <p>select add_tax(100);<br> 结果如下:</p>    <pre>  <code class="language-sql">mysql> select add_tax(100);  +--------------+  | add_tax(100) |  +--------------+  |          110 |  +--------------+  1 row in set (0.01 sec)</code></pre>    <h2><strong>查询或删除存储过程</strong></h2>    <p>在定义了过程和函数之后可使用下边语句来查看过程或函数的代码:</p>    <p>查询:</p>    <p>show create procedure total_orders;</p>    <p>show create function add_tax;</p>    <p>删除:</p>    <p>drop procedure total_orders;</p>    <p>drop function add_tax;</p>    <h2><strong>局部变量</strong></h2>    <p>使用 <strong>declare</strong> 语句,可以在begin...end语句块之间声明局部变量,就像函数中的局部变量一样。例如,可以对 add_tax() 函数进行修改,使其使用一个局部变量来保存税率,如下:</p>    <p>程序 basic_function_with_variables.sql :</p>    <pre>  <code class="language-sql"># Basic syntax to create a function    delimiter //    create function add_tax (price float) returns float  begin    declare tax float default 0.10;    return price*(1+tax);  end  //    delimiter ;</code></pre>    <h2><strong>游标和控制结构(一个更复杂的例子)</strong></h2>    <p>在下边的这个例子中,我们将编写一个存储过程,该存储过程将计算出最大金额的订单,并且返回该订单的orderid(很明显一个简单的查询,就可以计算出该数目,但是这个简单的例子只是说明了如何使用游标和控制结构)</p>    <p>程序 control_structures_cursors.sql :</p>    <pre>  <code class="language-sql"># Procedure to find the orderid with the largest amount  # could be done with max, but just to illustrate stored procedure principles    delimiter //    create procedure largest_order(out largest_id int)  begin    declare this_id int;    declare this_amount float;    declare l_amount float default 0.0;    declare l_id int;      declare done int default 0;    declare c1 cursor for select orderid, amount from orders;    declare continue handler for sqlstate '02000' set done = 1;      open c1;    repeat      fetch c1 into this_id, this_amount;      if not done then        if this_amount > l_amount then          set l_amount=this_amount;          set l_id=this_id;        end if;      end if;     until done end repeat;    close c1;        set largest_id=l_id;    end  //    delimiter ;</code></pre>    <p>在该存储过程的开始处,声明了一些在存储过程中使用的局部变量:</p>    <ul>     <li>this_id 保存当前行的orderid</li>     <li>this_amount 保存当前行的amount</li>     <li>l_id 保存最大金额的orderid</li>     <li>l_amount 保存最大金额的amount 默认值为0.0</li>     <li>done 用于循环中的标记,当循环结束后会被标记为1,默认为0,也就是false</li>    </ul>    <p>declare continue handler for sqlstate '02000' set done = 1; 是一个声明句柄,它类似于存储过程中的一个异常。</p>    <p>这里边有一个关键字 continue ,这个关键字是和 exit 关键字相对应的。 continue 语句会执行完指定操作后继续循环,而 exit 语句会退出将从最近的begin...end语句块中退出。 在这里的指定的操作就是 set done = 1 。</p>    <p>既然声明了句柄,就要告诉程序句柄在什么时候调用,在这个例子中,当 sqlstate '02000' 语句被调用时会执行句柄。那么这个 sqlstate '02000' 是什么意思呢? <strong>该句柄将在无法再找到记录行后调用</strong> 。也就是说当遍历完所有的结果集后就会调用。</p>    <p>因此, declare continue handler for sqlstate '02000' set done = 1; 的意思就是当遍历完结果集之后把done的值设为1。</p>    <p>游标(Cursor)是处理数据的一种方法,为了查看或者处理结果集中的数据,游标提供了在结果集中一次以行或者多行前进或向后浏览数据的能力。我们可以把游标当作一个指针,它可以指定结果中的任何位置,然后允许用户对指定位置的数据进行处理。(关于游标的详细信息,请看这篇博文 <a href="/misc/goto?guid=4959717940109238872" rel="nofollow,noindex">SQLServer游标(Cursor)简介和使用说明</a> )</p>    <p>注意,句柄要定义在游标之后,不然会报ERROR 1338 (42000): Cursor declaration after handler declaration错误。</p>    <p>declare c1 cursor for select orderid, amount from orders;</p>    <p>这个游标名称为c1,这只是他要保存内容的定义,该查询还不会执行。使用 open c1; 来真正运行这个查询。</p>    <p>要获得每一个数据行,必须运行一个fetch语句。可以在一个repeat循环中完成此操作。:</p>    <pre>  <code class="language-sql">repeat  ...  until done end repeat;</code></pre>    <p>只有在循环的尾部才检测done的值,除了使用repeat还可以使用while和loop循环</p>    <pre>  <code class="language-sql">while condition do  ...  end while    loop  ...  end loop</code></pre>    <p>这些循环没有内置的循环条件,但是可以通过leave语句退出循环。 <strong>请注意,存储过程不支持for循环</strong></p>    <p>fetch c1 into this_id, this_amount; 这行代码将获得一个数据行,并把查询到的两个属性保存到 this_id 和 this_amount 中。</p>    <pre>  <code class="language-sql">if not done then    if this_amount > l_amount then      set l_amount=this_amount;      set l_id=this_id;    end if;  end if;</code></pre>    <p>close c1; 将关闭这个游标</p>    <p>set largest_id=l_id; 将最大的值赋值给out参数,不能使用局部变量给外部调用。</p>    <p>调用方法:</p>    <pre>  <code class="language-sql">call largest_order(@l);  select @l;</code></pre>    <p>结果如下:</p>    <pre>  <code class="language-sql">mysql> call largest_order(@l);  Query OK, 0 rows affected (0.00 sec)    mysql> select @l;  +------+  | @l   |  +------+  |    3 |  +------+  1 row in set (0.01 sec)</code></pre>    <p> </p>    <p> </p>    <p>来自:http://www.jianshu.com/p/53a12af46836</p>    <p> </p>