1、PL/SQL概念java
PL/SQL:Procedural language/sql,是oracle在標準sql語句上的擴展。PL/SQL不只容許嵌入sql語言,還能夠定義變量和常量,容許使用條件語句和循環語句,容許使用例外處理各類錯誤。sql
存儲過程、函數、觸發器都是存放在oracle數據庫當中,比較複雜的任務通常能夠用存儲過程來實現,而這些都是用PL/SQL來編寫的。存儲過程、函數能夠用Java程序來調用。數據庫
PL/SQL的優勢:數組
提升應用程序的運行性能:安全
傳統操做數據庫方法是:SQL語句是寫在Java程序中,而後Java程序獲得一個connection,數據庫拿到SQL語句 後須要編譯(時間),將語句翻譯成可識別的語言。而後返回結果。網絡
存儲過程:減小Java程序中的SQL語言,並且存儲過程已經在數據庫中編譯好。oracle
模塊化的設計思想:直接傳參數調用過程ide
減小網絡傳輸量:與傳整個sql語句相比,調用存儲過程只須要穿過程名,參數便可。模塊化
提升安全性:存儲過程避免了重要信息的暴露,由於直接在數據庫中。函數
PL/SQL缺點:
移植性很差:若是數據庫變換,則這些存儲過程全都要推翻掉。
PL/SQL的編寫工具:Sql plus 或 PL/SQL developer。後者是用於開發pl/sql塊的集成開發環境(IDE),這個一個獨立的產品,而不是oracle的附帶品。
一個簡單案例:編寫一個存儲過程,向某表添加記錄。
--建立一個表 create table mytest( name varchar2(20), password varchar2(20) ); --建立存儲過程 create or replace procedure pro_insertMytest is begin --執行部分 insert into mytest values ('SMITH', 'm123'); end; --最後輸入/表示執行存儲過程 --若是有錯誤,SQL> show error; 顯示錯誤信息 --如何調用該存儲過程 --方式一: exec procedureName(param1, param2...); --方式二: call procedureName(param1, param2...);
2、PL/SQL 基礎知識
PL/SQL能夠編寫什麼:
塊(block)是pl/sql的基本程序單元,編寫pl/sql程序實際上就是編寫pl/sql塊。要實現相對複雜的功能,可能須要在一個pl/sql塊中嵌套其餘的pl/sql塊。
簡單分類:
編寫規範:
註釋:
單行註釋:-- 多行註釋: /* ... */
標識符號的命名規範
1) 定義變量時,建議使用 v_ 做爲前綴 v_name
2) 定義常量時,建議使用 c_ 做爲前綴 c_rate
3) 定義遊標時,建議使用 _cursor 做爲後綴 emp_cursor
4) 定義例外時,建議使用 e_ 做爲前綴 e_error
PL/SQL 塊的結構:
由三個部分構成:定義部分、執行部分、例外處理部分,以下:
declare
/* 定義部分----定義常量、變量、遊標、例外、複雜數據類型 */
begin
/* 執行部分----要執行的pl/sql 語句和 sql 語句 */
exception
/* 例外處理部分----處理運行時的各類錯誤 */
說明:定義部分是從declare開始的,可選;執行部分從begin開始,必須;處理部分是從exception開始,可選。
PL/SQL塊的實例:
/* 實例1-最簡單的塊 說明:dbms_output 是oracle提供的包(package),相似於java的開發包,該包包含一些過程,put_line是該包的一個過程。 */ set serveroutput on; --打開輸出選項 set serveroutput off; 關閉 begin dbms_output.put_line('Hello World'); end; -------------------------------華麗的分割線------------------------------------------- /* 實例2-包含定義部分和執行部分的pl/sql塊 說明: & 表示要接收從控制檯輸入的變量 */ declare v_ename varchar2(10); -- 定義字符串變量 v_sal number(7,2); begin --執行部分 select ename, sal into v_ename, v_sal from emp where empno=&EmployeeNo; --在控制檯顯示用戶名 dbms_output.put_line('Employee Name:' || v_ename || ' Salary:' || v_sal); end; -------------------------------華麗的分割線------------------------------------------- /* 實例3-包含定義部分、執行部分和例外處理的部分的pl/sql塊 說明: 上面的實例2,若是沒有找到對應的empno,則須要進行異常處理 */ declare v_ename varchar2(10); -- 定義字符串變量 v_sal number(7,2); begin --執行部分 select ename, sal into v_ename, v_sal from emp where empno=&EmployeeNo; --在控制檯顯示用戶名 dbms_output.put_line('Employee Name:' || v_ename || ' Salary:' || v_sal); exception when no_data_found then dbms_output.put_line('Employee number not exists!'); end;
存儲過程:用於執行特定的操做。創建存儲過程時,能夠指定輸入參數(in)和指定輸出參數(out)。使用輸入參數,能夠將數據傳遞到執行部分,而使用輸出參數能夠將執行部分的數據傳遞到應用環境。
實例:編寫一個存儲過程,輸入員工名、新工資去修改該員工的工資。以及用java調用存儲過程。
/* 實例4-編寫一個存儲過程,輸入員工名、新工資去修改該員工的工資 說明: 執行帶參數的存儲過程: exec pro_updateEmp('SMITH', 4678); */ create or replace procedure pro_updateEmp(empName varchar2, newSal number) is --此處能夠定義變量 begin --執行部分 update emp set sal = newSal where ename = empName; end; -------------------------------華麗的分割線------------------------------------------- //Java 程序調用存儲過程 public class TestOracleProcedure { public static void main(String[] args) { try { //加載驅動 Class.forName("oracle.jdbc.driver.OracleDriver"); //獲取鏈接 Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:ORCL", "dog", "dog"); //建立CallableStatement //注:procedure 名大小寫要保持和數據庫一致 CallableStatement cs = conn.prepareCall("{call PRO_UPDATEEMP(?, ?)}"); cs.setString(1, "SMITH"); cs.setInt(2, 100); //執行 cs.execute(); //關閉資源 cs.close(); conn.close(); } catch (Exception e) { e.printStackTrace(); } } }
函數:用於返回特定的數據,當創建函數時,在函數頭部必須包含return子句,而在函數體內必須包含return語句返回的數據。
--函數案例 --輸入員工名,返回該員工的年薪 create or replace function fun_YearSal(EmpName varchar2) return number is YearSal number(7,2); begin --執行部分 select (sal+nvl(comm, 0))*12 into YearSal from emp where ename = EmpName; return YearSal; end; --如何調用function --定義變量abc接收返回結果 var abc number; call fun_YearSal('SMITH') into:abc;
包:用於在邏輯上組合存儲過程和函數,它由包規範和包體兩部分組成。包的規範只包含了存儲過程和函數的說明,可是沒有存儲過程和函數的實現代碼。包體用於實現包規範中的存儲過程和函數。
--包 案例 --建立包 pak_test,並聲明該包有 存儲過程 PRO_UPDATEEMP 和函數 FUN_YEARSAL create or replace package pak_test is procedure PRO_UPDATEEMP(empName varchar2, newSal number); function FUN_YEARSAL(EmpName varchar2) return number; end; --接下來要實現包體 create or replace package body pak_test is --實現包中的存儲過程 procedure pro_updateEmp(empName varchar2, newSal number) is begin update emp set sal = newSal where ename = empName; end; --實現包中的函數 function fun_YearSal(EmpName varchar2) return number is YearSal number(7,2); begin select (sal+nvl(comm, 0))*12 into YearSal from emp where ename = EmpName; return YearSal; end; end; --如何調用包的存儲過程和函數:當調用包的存儲過程和函數時,在過程和函數前須要帶有包名。若是要訪問其餘方案的包,還須要在包名前加方案名。 --包是pl/sql種很是重要的部分 call pak_test.updateEmp('SMITH', '2000');
觸發器:指隱含的執行的存儲過程,通常不會主動調用。當定義觸發器時,必需要指定觸發的事件和操做。經常使用的觸發時間包括insert, update, delete 語句,而觸發操做實際就是一個pl/sql塊。
觸發器是很是有用的,能夠維護數據庫的安全和一致性。(PL/SQL第二篇中講述)
定義並使用變量:
編寫pl/sql程序時,能夠定義變量和常量;在pl/sql程序中包括有:標量類型(scalar)、複合類型(composite)、參照類型(reference)、lob (large object)
a. 標量類型(scalar)
---經常使用類型
identifier [constant] datatype [not null] [:=| default expr]
identifier:名稱 constant:常量。須要指定初始值,並且值不能改變。 datatype:數據類型 not null:不能爲 空 :=:給變量或者常量指定初始值 default:用於指定初始值 expr:指定初始值的pl/sql表達式,能夠是文本 值、其餘變量、函數等。
案例:--定義一個變長字符串 v_ename varchar2(20)
--定義一個小數 範圍-9999.99~9999.99 v_sal number(6,2)
--定義一個小數並給一個初始值5.4 (:= 是pl/sql的賦值號 ) v_sal2 number(6,2):=5.4
--定義一個日期類型的數據 v_date date;
--定義一個布爾變量, 不能爲空,初始值爲false v_valid boolean not null default false;
---%type 類型的標量,對於上面的經常使用標量,若是實際的變量長度超過了定義的長度,則會報錯。爲了下降pl/sql 程序的維護工做量,可使用%type屬性定義變量,這樣它會按照數據庫列來肯定定義的變量的類型和長度。
標識符名 表名.列名%type
/* 案例:輸入員工號,顯示員工姓名、工資、我的所得稅(稅率0.03), 說明變量的使用*/ declare --定義標量 c_tax_rate number(3,2):=0.03; v_ename varchar2(6); v_sal number(7,2); v_tax number(7,2); begin --執行 select ename, sal into v_ename, v_sal from emp where empno=&EmployeeNum; --計算所得稅 v_tax:=v_sal*c_tax_rate; --輸出 dbms_output.put_line('Ename: '||v_ename||' Salary: '||v_sal||' Tax: '||v_tax); exception when no_data_found then dbms_output.put_line('Employee number not exists!'); end; --上述案例使用 %type 來定義變量,使用較多 declare --定義標量 c_tax_rate number(3,2):=0.03; v_ename emp.ename%type; v_sal emp.sal%type; v_tax number(7,2); begin --執行 select ename, sal into v_ename, v_sal from emp where empno=&EmployeeNum; --計算所得稅 v_tax:=v_sal*c_tax_rate; --輸出 dbms_output.put_line('Ename: '||v_ename||' Salary: '||v_sal||' Tax: '||v_tax); exception when no_data_found then dbms_output.put_line('Employee number not exists!'); end;
b. 複合類型(composite):存放多個值的變量,主要包括:pl/sql 記錄、pl/sql 表、嵌套表、varray 四種,下面將介 紹前兩種比較經常使用的。
/* 複合類型--pl/sql記錄 相似高級語言中的結構體,注意:當引用pl/sql記錄成員時,必需要加記錄變量做爲前綴(記錄變量名.記錄成員) */ declare --定義一個pl/sql記錄類型,名爲 emp_record_type,包含 ename,salary,title 三個數據 type emp_record_type is record( ename emp.ename%type, salary emp.sal%type, title emp.job%type); --定義一個emp_record_type類型的變量,emp_record emp_record emp_record_type; begin select ename, sal, job into emp_record from emp where empno = 7369; dbms_output.put_line('Ename: '||emp_record.ename||' Salary: '||emp_record.salary||' Title: '||emp_record.title); end; /* 複合類型--pl/sql表 相似高級語言中的數組,不一樣的是,pl/sql表中的元素下標能夠爲負,而且下標沒有限制 */ declare --定義一個pl/sql表類型,名爲emp_table_type,用於存放 emp.name%type 類型的數據 -- index by binary_integer 表示下標是整數 type emp_table_type is table of emp.ename%type index by binary_integer; --定義一個emp_table_type 類型的變量 emp_table emp_table emp_table_type; begin select ename into emp_table(-1) from emp where empno = 7369; dbms_output.put_line('Ename: '|| emp_table(-1)); end; --問題:若是返回多行數據,該怎麼接收呢?
c. 參照類型(reference):參照變量是指用於存放數值指針的變量。經過使用參照變量,能夠是應用程序共享相同對 象,從而下降佔用空間。在寫pl/sql程序時,可使用遊標變量(ref cursor)和對象類型變量(ref obj_type)兩種 參照變量類型。
/* 參照變量--ref cursor遊標變量 定義遊標時,不須要指定select語句,但使用遊標時(open) 須要指定select 語句,這樣遊標就和select語句結合 */ --案例:輸入部門號,顯示該部門全部員工的姓名和工資 declare --定義遊標類型 type emp_cursor_type is ref cursor; --定義遊標變量 emp_cursor emp_cursor_type; --定義兩個變量去接收結果 v_ename emp.ename%type; v_sal emp.sal%type; begin --執行 --將遊標emp_cursor 和 select語句結合,讓遊標指向結果集 open emp_cursor for select ename, sal from emp where deptno = &DeptNum; --循環取出 loop --取出遊標 fetch emp_cursor into v_ename, v_sal; --循環結束條件,emp_cursor爲空 exit when emp_cursor%notfound; dbms_output.put_line('Ename: '||v_ename||' Salary: '||v_sal); end loop; end;
pl/sql 控制結構
a. 條件分支語句
/* 案例 實例11-條件分支語句: 1.編寫一個存儲過程,輸入員工名,若是該員工工資低於2000,就給該員工工資增長10% 2.(二重條件分支) 若是補助不是0,則增長100,不然補助爲200 3.(多重條件分支) 若是該員工職位是PRESIDENT,工資加1000; 若是職位是 MANAGER,工資加500;其餘狀況工資加200 */ create or replace procedure update_sal(EmployeeName varchar2) is --定義 v_sal emp.sal%type; v_comm emp.comm%type; v_job emp.job%type; begin --執行 select sal, comm, job into v_sal, v_comm, v_job from emp where ename = EmployeeName; --判斷 1 if v_sal<2000 then update emp set sal=sal*1.1 where ename = EmployeeName; end if; --判斷 2 if v_comm <> 0 then update emp set comm=comm+100 where ename = EmployeeName; else update emp set comm=comm+200 where ename = EmployeeName; end if; --判斷3 if v_job = 'PRESIDENT' then update emp set sal=sal + 1000 where ename = EmployeeName; elsif v_job = 'MANAGER' then update emp set sal=sal + 500 where ename = EmployeeName; else update emp set sal=sal + 200 where ename = EmployeeName; end if; exception when no_data_found then dbms_output.put_line('Employee number not exists!'); end;
b. 循環語句:pl/sql中包含三種循環,loop、while、for
/* 案例 實例12-循環語句: loop 經過輸入Username 向Users表中循環添加10條數據 */ --create table sql drop table users; create table users( userid number(10) primary key, username varchar2(30) not null, password varchar2(30)); create or replace procedure loop_add_user1(Username varchar2) is --定義 v_num number:=1; begin loop insert into users values(v_num, Username, 'loop'); --判斷是否要退出循環 exit when v_num = 10; --num 自增 v_num:=v_num + 1; end loop; end; /* 案例 實例13-循環語句: while 經過輸入Username 向Users表中循環添加10條數據,從11條記錄開始添加 */ create or replace procedure loop_add_user2(Username varchar2) is --定義 v_num number:=11; begin while v_num <= 20 loop insert into users values(v_num, Username, 'whileLoop'); --判斷是否要退出循環 exit when v_num = 10; --num 自增 v_num:=v_num + 1; end loop; end; /* 案例 實例14-循環語句: for 經過輸入Username 向Users表中循環添加10條數據,從21條記錄開始添加 */ create or replace procedure loop_add_user3(Username varchar2) is begin --從21開始循環,直到30結束.能夠看到控制變量i在隱含中不停的增長 for i in reverse 21..30 loop insert into users values(i, Username, 'forLoop'); end loop; end;
pl/sql 順序控制語句 goto 、 null
/* 案例 實例15-順序控制語句: goto 、 null 說明:goto end_loop,這裏end_loop是定義的標籤 <<end_loop>> null語句不會執行任何操做,而且會直接將控制傳遞到下一條語句,通常只是爲了提升程序可讀性 */ declare i int := 1; begin loop dbms_output.put_line('Output i=' || i); if i=10 then goto end_loop; else null; end if; i := i + 1; end loop; dbms_output.put_line('Cycle Over1'); <<end_loop>> dbms_output.put_line('Cycle Over2'); end;