《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

 

  

 

 

  

 

  

  

 

  

 

  

  

 

  

 

 

 

 

  

  

  

 

 

  

 

 

 

 

 

 

 


免責聲明!

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



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