mysql進階之存儲過程


往往看別人的代碼會有這樣的感慨:

看不懂

理還亂

是離愁

別是一番滋味在心頭

為什么要使用存儲過程?

在mysql開發中使用存儲過程的理由:

  1. 當希望在不同的應用程序或平台上執行相同的函數,或者封裝特定功能時,存儲過程是非常有用的
  2. mysql 執行語句是要先編譯,然后再執行的。這樣如果查詢並發大的時候。會浪費很多資源和時間。造成mysql進程占用資源過多,症狀就是慢。但存儲過程可以把一些特別的語句封裝成一個方法 ,再編譯好成一個可以執行的方法,對外只要接收參數就可以了。這樣就不用再編譯。執行就快了。你覺得你數據庫因為同時出現太多讀寫操作而變得慢 ,那么就要用了
  3. 使用了存過程,很多相似性的刪除,更新,新增等操作就變得輕松了,並且以后也便於管理!
  4. 存儲過程因為SQL語句已經預編繹過了,因此運行的速度比較快。
  5. 存儲過程可以接受參數、輸出參數、返回單個或多個結果集以及返回值。可以向程序返回錯誤原因。
  6. 存儲過程可以接受參數、輸出參數、返回單個或多個結果集以及返回值。可以向程序返回錯誤原因。
  7. 存儲過程運行比較穩定,不會有太多的錯誤。只要一次成功,以后都會按這個程序運行。
  8. 存儲過程主要是在服務器上運行,減少對客戶機的壓力。
  9. 存儲過程可以包含程序流、邏輯以及對數據庫的查詢。同時可以實體封裝和隱藏了數據邏輯。
  10. 存儲過程可以在單個存儲過程中執行一系列SQL語句。
  11. 存儲過程可以從自己的存儲過程內引用其它存儲過程,這可以簡化一系列復雜語句。

存儲過程案例

讓我們使用一些簡單的示例來了解什么是存儲過程,使用下邊演示程序的前提是必須正確安裝了mysql。

下邊的程序來源於[PHP和MySQL WEB開發(4th)]這本書,我們使用mysql中的books數據庫,這個數據庫中的表有一下幾個:

mysql> show tables;
+-----------------+
| Tables_in_books |
+-----------------+
| book_reviews    |
| books           |
| customers       |
| order_items     |
| orders          |
+-----------------+
5 rows in set (0.00 sec)

我們用到了orders這個表:

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)

示例1

程序basic_stored_procedure.sql

# Basic stored procedure example

delimiter //

create procedure total_orders (out total float)
begin
 select sum(amount) into total from orders;
end
//

delimiter ;

打開終端並啟動mysql,使用有權限的賬戶登錄mysql,使用某個數據庫。上邊的程序basic_stored_procedure.sql是封裝在一個后綴為sql的文件中的,因此要使用mysql調用這個文件。

使用命令source /path/basic_stored_procedure.sql,path是你保存這個文件的路徑。

然后使用命令call total_orders(@t);。可以看出,使用關鍵字call來調用該過程。call total_orders(@t);這個語句將調用total_orders過程並傳入一個用來保存結果的變量。要查看該結果,需要查看該變量。使用下邊的語句:

select @t;
結果為:

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)

現在我們逐行分析程序basic_stored_procedure.sql中的代碼:

  • elimiter // 將語句末尾的分隔符從當前值(這個分隔符通常是分號,除非以前改變了分隔符)改為雙斜杠字符。這樣做的目的是可以在存儲過程中使用分號分隔符,這樣mysql就會將分號當做是存儲過程的代碼,不會執行這些代碼

  • create procedure total_orders (out total float) 創建了實際的存儲過程,該存儲過程的名稱是total_orders。他只有一個total參數,該參數是需要計算的值。out表示該參數將被傳出或返回

    • 參數也可以聲明為in,表示該值必須傳入到存儲過程。inout表示該值必須被傳入,但可以被存儲過程修改
    • float 表示參數的類型。在這個例子中將返回所有訂單的總數。orders列的類型為float,因此該返回類型也必須是float。可接受的數據類型映射到可供使用的列類型
    • 如果希望使用多個參數,可以提供一個由逗號間隔的參數列表
  • BEGINEND就好比函數中的{ },用來標識一個語句塊

  • select sum(amount) into total from orders; 這就是我們實際中使用的查詢語句

  • delimiter ; delimiter重新把分隔符定義為分號;

示例2

示例1中是使用的procedure創建過程的方法,在示例2中,我們將使用function來創建函數。函數接受參數並返回一個唯一值。

程序basic_function.sql:

# Basic syntax to create a function

delimiter //

create function add_tax (price float) returns float
  return price*1.1;

//

delimiter ;
  • 參數不必通過in或out指定,因為所有參數都是in,或是輸入參數。
  • 在參數列表之后是returns float,它指定了返回值的類型。該值可以使任何有效的mysql類型
  • return price*1.1; 使用return可以返回一個值
  • 這里並沒有使用beginend。可以使用它們,但他們不是必須的。就像php或者c中,如果一個語句塊只含有一個語句,可以以調用內置函數的相同方式調用一個存儲函數

使用方法示例1中有所不同。

select add_tax(100);
結果如下:

mysql> select add_tax(100);
+--------------+
| add_tax(100) |
+--------------+
|          110 |
+--------------+
1 row in set (0.01 sec)

查詢或刪除存儲過程

在定義了過程和函數之后可使用下邊語句來查看過程或函數的代碼:

查詢:

show create procedure total_orders;

show create function add_tax;

刪除:

drop procedure total_orders;

drop function add_tax;

局部變量

使用declare語句,可以在begin...end語句塊之間聲明局部變量,就像函數中的局部變量一樣。例如,可以對add_tax()函數進行修改,使其使用一個局部變量來保存稅率,如下:

程序basic_function_with_variables.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 ;

游標和控制結構(一個更復雜的例子)

在下邊的這個例子中,我們將編寫一個存儲過程,該存儲過程將計算出最大金額的訂單,並且返回該訂單的orderid(很明顯一個簡單的查詢,就可以計算出該數目,但是這個簡單的例子只是說明了如何使用游標和控制結構)

程序control_structures_cursors.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 ;

在該存儲過程的開始處,聲明了一些在存儲過程中使用的局部變量:

  • this_id 保存當前行的orderid
  • this_amount 保存當前行的amount
  • l_id 保存最大金額的orderid
  • l_amount 保存最大金額的amount 默認值為0.0
  • done 用於循環中的標記,當循環結束后會被標記為1,默認為0,也就是false

declare continue handler for sqlstate '02000' set done = 1;是一個聲明句柄,它類似於存儲過程中的一個異常。

這里邊有一個關鍵字continue,這個關鍵字是和exit關鍵字相對應的。continue語句會執行完指定操作后繼續循環,而exit語句會退出將從最近的begin...end語句塊中退出。 在這里的指定的操作就是set done = 1

既然聲明了句柄,就要告訴程序句柄在什么時候調用,在這個例子中,當sqlstate '02000'語句被調用時會執行句柄。那么這個sqlstate '02000'是什么意思呢?該句柄將在無法再找到記錄行后調用。也就是說當遍歷完所有的結果集后就會調用。

因此,declare continue handler for sqlstate '02000' set done = 1;的意思就是當遍歷完結果集之后把done的值設為1。

游標(Cursor)是處理數據的一種方法,為了查看或者處理結果集中的數據,游標提供了在結果集中一次以行或者多行前進或向后瀏覽數據的能力。我們可以把游標當作一個指針,它可以指定結果中的任何位置,然后允許用戶對指定位置的數據進行處理。(關於游標的詳細信息,請看這篇博文SQLServer游標(Cursor)簡介和使用說明

注意,句柄要定義在游標之后,不然會報ERROR 1338 (42000): Cursor declaration after handler declaration錯誤。

declare c1 cursor for select orderid, amount from orders;

這個游標名稱為c1,這只是他要保存內容的定義,該查詢還不會執行。使用open c1;來真正運行這個查詢。

要獲得每一個數據行,必須運行一個fetch語句。可以在一個repeat循環中完成此操作。:

repeat
...
until done end repeat;

只有在循環的尾部才檢測done的值,除了使用repeat還可以使用while和loop循環

while condition do
...
end while

loop
...
end loop

這些循環沒有內置的循環條件,但是可以通過leave語句退出循環。請注意,存儲過程不支持for循環

fetch c1 into this_id, this_amount; 這行代碼將獲得一個數據行,並把查詢到的兩個屬性保存到this_idthis_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;

close c1; 將關閉這個游標

set largest_id=l_id; 將最大的值賦值給out參數,不能使用局部變量給外部調用。

調用方法:

call largest_order(@l);
select @l;

結果如下:

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)

說明

如有錯誤之處,請給予指出,多謝。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM