MySQL存儲過程與游標


  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,時間效率更高些。

 


免責聲明!

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



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