1、存儲過程簡介
(1)存儲過程:是為以后使用而保存的一條或多條SQL語句或函數。可以將它視為批文件,不過它的作用不僅僅限於批處理;通常被稱為函數或子程序。
(2)支持存儲過程的DBMS:Oracle、SQL Server、MySQL 5及更高版本;而Microsoft Access和SQLite不支持存儲過程。
(3)存儲過程的優點:簡單、安全、高性能
- 把處理封裝在一個易用的單元中,簡化了復雜的操作,實現過程化編程
- 不用反復建立一系列處理步驟,保證數據的一致性,防止錯誤
- 簡化對變動的管理,以達到安全性;通過存儲過程限制對基礎數據的訪問,減少了數據訛誤的機會
- 存儲過程通常以編譯過的形式存儲,所以DBMS處理命令所需的工作量少,提高了性能;也即是說存儲過程的語句已經保存在數據庫里了,語句已經被解析過了,以可執行格式存在。
- 存儲過程可以利用SQL元素和特性來編寫功能更強大更靈活的代碼
(4)存儲過程的缺陷:
- 不同DBMS中的存儲過程語法有所不同,編寫真正的可移植存儲過程幾乎是不可能的,不過存儲過程的自我調用可以相對保持可移植
- 編寫存儲過程比編寫基本SQL語句復雜,需要更高的技能,更豐富的經驗
(5)存儲過程的創建
create procedure procedure_name(in arg1 type1, out argN typeN) begin ... end
存儲過程可以定義輸入參數,用in關鍵字來表示;也可以定義輸出參數,用out關鍵字來表示。存儲過程的業務代碼都放置在begin和end語句中,並且每天語句的結束法默認是分號;通過declare來聲明變量,並且所有的變量聲明都要放在代碼塊中的開頭;通過set或者select...into來給變量賦值;此外,也可以通過select語句顯示返回的值。整體來說,重要掌握了存儲過程的相關語法(可以參考該鏈接),撰寫業務邏輯代碼還是不難的。
(6)存儲過程的執行
-- MySQL 執行存儲過程 CALL procedure_name([param1 [, ...]]) -- SQL Server 執行存儲過程 EXECUTE [ @RETURN STATUS =] procedure_name [[[@param1_name = ] VALUE | [@param2_name = ] @VARIABLE [ OUTPUT ]] [WITH RECOMPILE] -- Oracle 執行存儲過程 EXECUTE [ @RETURN STATUS =] procedure_name [[[@param1_name = ] VALUE | [@param2_name = ] @VARIABLE [ OUTPUT ]] [WITH RECOMPILE]
在MySQL中執行存儲過程的方式是:call database_name.procedure_name(arg1,...,argN),其實也可以不用數據庫名,直接用存儲過程名,即call procedure_name(arg1,...,argN)
(7)代碼注釋
在寫存儲過程中應該添加適當的注釋,這樣更容易地理解和更安全地修改代碼;增加注釋不影響性能,也不存在缺陷。對代碼行進行注釋的標准方式是在之前放置兩個連字符(--),注意了連字符和注釋內容之間至少要隔一個空格;所有的DBMS都支持--連字符進行注釋。MySQL還支持井號(#)進行注釋。
(8)觸發器
觸發器是特殊的存儲過程,它在特定的數據庫活動發生時自動執行。觸發器可以與特定表上的insert、update和delete操作(或組合)相關聯。在大多數情況下,觸發器是很不錯的函數,但是它會導致更多的I/O開銷。
- 存儲過程只是簡單的存儲SQL語句,觸發器與單個的表相關聯。
- 觸發器的內容不能修改,只能替換或者重新創建它。
- 不同的DBMS,它們的觸發器操作時機可能不同,有的是在特定操作執行之前執行,有的是在特定操作執行之后執行。
- 觸發器內的代碼數據訪問權:
- insert操作中的所有新數據
- update操作中的所有新數據和舊數據
- delete操作中刪除的數據
- 觸發器的用途
- 保證數據一致。
- 基於某個表的變動在其他表上執行活動。
- 進行額外的驗證並根據需要回退數據。
- 計算列的值或更新時間戳。
- 一般來說,約束的處理比觸發器快,應盡量使用約束。
2、游標簡介
(1)游標:也稱為光標,是一個存儲在DBMS服務器上的數據庫查詢,它不是一條select語句,而是被該語句檢索出來的結果集。
(2)用途:對檢索出來的數據進行行前進或者后退操作,主要用於交互式應用,如用戶滾動屏幕上的數據
(3)特性:
- 能夠標記游標為只讀,使數據能讀取,但不能更新和刪除
- 能控制可以執行的定向操作(向前、向后、第一、最后、絕對位置、相對位置等)
- 能標記某些列為可編輯的,某些列為不可編輯的
- 規定范圍,使游標對創建它的特定請求(如存儲過程)或對所有請求可訪問
- 只是DBMS對檢索出的數據(而不是指出表中活動數據)進行復制,使數據在游標打開和訪問期間不變化
(4)支持游標的DBMS:DB2、MariaDB、MySQL 5、SQL Server、SQLite、Oracle和PostgreSQL,而Microsoft Access不支持
(5)游標對基於Web的應用用處不大(ASP、ASP.NET、ColdFusion、PHP、Python、Ruby、JSP等),大多數Web應用開發人員不使用游標
(6)使用:
- 聲明游標: DECLARE cursor_name CURSOR FOR SELECT * FROM table_name; // 還沒有檢索數據
-- MySQL游標的聲明 DECLARE cursor_name CURSOR FOR select_statement -- SQL Server游標的聲明 DECLARE cursor_name CURSOR FOR select_statement [FOR [READ ONLY | UPDATE {[co lumn_list]}]] -- Oracle游標的聲明 DECLARE CORSOR cursor_name IS {select_statement}
- 打開游標:OPEN cursor_name; // 開始檢索數據,即指定游標的SELECT語句被執行,並且查詢的結果集被保存在內存里的特定區域。
-- MySQL打開游標 OPEN cursor_name -- SQL Server打開游標 OPEN cursor_name -- Oracle打開游標 OPEN cursor_name [param1 [, param2]]
- 獲取數據:FETCH cursor_name into var1,var2,...,varn; // 當游標cursor_name檢索完數據后,只有等到下一次fetch時才會觸發結束的標志
-- MySQL游標獲取數據 FETCH cursor_name INTO var1_name [, var2_name] ... -- SQL Server游標獲取數據 FETCH NEXT FROM cursor_name [INTO fetch_list] -- Oracle游標獲取數據 FETCH cursor_name {INTO : host_var1 [[INDICATOR] : indicator_var1] [, : host_var2 [[INDICATOR] : indicator_var2]] | USING DESCRIPTOR DESCRIPTOR}
- 關閉游標:CLOSE cursor_name;
-- MySQL關閉游標,會主動釋放資源,所以不需要DEALLOCATE語句 CLOSE cursor_name -- SQL Server關閉游標和釋放資源 CLOSE cursor_name DEALLOCATE cursor_name -- Oracle關閉游標,會主動釋放資源,所以不需要DEALLOCATE語句 CLOSE cursor_name
3、存儲過程代碼示例
(1)主存儲過程:該存儲過程的名稱為main_procedure_name,並且在該存儲過程中調用另外一個存儲過程,其名稱為child_procedure_name
CREATE PROCEDURE `database_name`.`main_procedure_name`() begin declare tmp_id varchar(15); declare done int default false; declare tmp_cursor cursor for select distinct pk_id from database_name.table_name where `some_field` = 'some_value'; // 根據實際情況添加限定條件 declare continue handler for not found set done = true; open tmp_cursor; pk_id_loop:loop fetch tmp_cursor into tmp_id; if done then leave pk_id_loop; end if; call database_name.child_procedure_name(tmp_id); end loop pk_id_loop; close account_cursor; end
(2)子存儲過程:該存儲過程的名稱為child_procedure_name,其偽代碼流程如下所示
CREATE PROCEDURE `database_name`.`child_procedure_name`(in input_param varchar(15)) begin declare counts int default 0; declare cmp_result int default 0; declare cur_id varchar(20); declare cur_value varchar(30); declare pre_id varchar(20); declare pre_value varchar(30); declare next_id varchar(20); declare next_value varchar(30); declare done int default false; declare cursor_name cursor for select id, value from database_name.table_name where some_field = input_param order by another_field desc; declare continue handler for not found set done = true; select count(*) into counts from database_name.table_name where some_field = input_param; if counts = 1 then open cursor_name; fetch cursor_name into cur_id, cur_value; ... close cursor_name; elseif credit_count = 2 then open cursor_name; fetch cursor_name into cur_id, cur_value; fetch cursor_name into pr_id, pre_value; ... close cursor_name; elseif credit_count > 2 then open cursor_name; info_loop:loop fetch cursor_name into cur_id, cur_value; fetch cursor_name into pre_id, pre_value; ... set next_id = pre_id; set next_value = pre_value; leave info_loop; end loop info_loop; close cursor_name;; end if; end
子存儲過程的改造,不用區分總記錄數為1、2和更多條的情況,以下是改造后的偽代碼流程:
CREATE PROCEDURE `database_name`.`child_procedure_name`(in input_param varchar(15)) begin declare counts int default 0; declare cmp_result int default 0; declare cur_id varchar(20); declare cur_value varchar(30); declare pre_id varchar(20); declare pre_value varchar(30); declare next_id varchar(20); declare next_value varchar(30); declare flag int default 0; declare done int default false; declare cursor_name cursor for select id, value from database_name.table_name where some_field = input_param order by another_field desc; declare continue handler for not found set done = true; select count(*) into counts from database_name.table_name where some_field = input_param; set flag = counts % 2; open cursor_name; info_loop:loop fetch cursor_name into cur_id, cur_value; fetch cursor_name into pre_id, pre_value; if done then # 只有一條記錄數據 # 記錄數據為偶數條 # 記錄數據為奇數條 leave info_loop; end if; ... set next_id = pre_id; set next_value = pre_value; leave info_loop; end loop info_loop; close cursor_name;; end
-- 注意了,在跳出循環的地方需要進行commit提交操作
子存儲過程再次改造,每次循環只獲取一條數據,跟上一次保存的數據進行比較,偽代碼如下所示:
CREATE PROCEDURE `database_name`.`child_procedure_name`(in input_param varchar(15)) begin declare cmp_result int default 0; declare cur_id varchar(20); declare cur_value varchar(30); declare pre_id varchar(20); declare pre_value varchar(30); declare next_id varchar(20); declare next_value varchar(30); declare done int default false; declare cursor_name cursor for select id, value from database_name.table_name where some_field = input_param order by another_field desc; declare continue handler for not found set done = true; open cursor_name; info_loop:loop fetch cursor_name into cur_id, cur_value; if done then ... leave info_loop; end if; ... set next_id = pre_id; set next_value = pre_value; leave info_loop; end loop info_loop; close cursor_name;; end
上述的存儲過程代碼只是一個業務邏輯思路過程,可供參考,比如可以用來循環處理或者比較相鄰兩條記錄的數據。在業務處理模塊中,每次循環取一條數據的邏輯代碼要簡單些,但是循環的次數較多,具體的循環次數為總記錄數+1;而每次循環取兩條數據的邏輯代碼要復雜些,不過循環的次數是單條數據循環次數的一般,具體的循環次數為總記錄數 / 2 + 1,時間效率更高些。