Oracle Database-PL/SQL
阿新 • • 發佈:2018-08-28
rom tput ati EDA mit inpu 工具 inf fin
因為是過程化擴展,所以
也可以在命令行中使用desc關鍵字直接查看程序包的結構
如下,這種方法相當於簡化的文檔
當然,在存儲過程或存儲函數中也可以調用存儲過程或存儲函數。
Oracle Database-PL/SQL
PL/SQL基礎
PL/SQL(Procedure Language/SQL) PL/SQL是Oracle對SQL語言的過程化擴展,指在SQL命令語言中增加了過程處理語句(如分支、循環等),使SQL語言具有過程處理能力。把SQL語言的數據操縱能力與過程語言的數據處理能力結合起來,使得 PLSQL面向過程但比過程語言簡單、高效、靈活和實用。因為是過程化擴展,所以
- PL/SQL程序內支持編寫SQL語言
- 將SQL語言的數據操縱能力與過程語言的數據處理能力結合
- PL/SQL是一門面向過程的語言
PL/SQL的官方文檔
如下,可以查到DBMS_OUTPUT程序包的內容PL/SQL的語法
*Java的語法
PL/SQL可以完成以下Java代碼(偽)完成的功能 下面是PL/SQL實現這個功能 而PL/SQL相對JDBC實現這個更能更有效率 開發模式:SpringMVC+存儲過程(PL/SQL) Hello World 這個dbms_output是一個程序包(類似API)declare-變量和常量的說明
註意:若沒有需要說明的變量或常量,可以沒有declare關鍵字說明基本數據類型變量時要說明三個部分
- 變量名
- 數據類型
- 數據大小
基本類型變量
與表的列的數據類型對應(意味著我們可以把表中的數據存到這些類型的變量中)- char
- varchar2
- date
- number
- boolean
- long
引用型變量
引用表中列的數據類型作為該變量的數據類型 示例: 賦值有兩種方法,":="和"into"關鍵字記錄型變量
記錄型變量引用表中的一行作為變量(s)的數據類型,換句話說,記錄型變量可以存放表中的一行數據,可以理解成一個數組(或者集合),數組中的每一個元素代表這一行的記錄每一列 示例:IF語句
語法
註意elsif這個寫法與Java else if不一樣 示例:-- 判斷用戶從鍵盤輸入的數字並打印相應語句 --註意,這段正確的代碼無法在PL/SQL Developer和命令行中運行,但可以在Oracle SQL Developer(自行下載)中運行接收鍵盤輸入 -- accept addr prompt ‘xxx‘ -- addr 是一個地址值,在該地址上保持了輸入的值 accept num prompt ‘請輸入一個數字‘; declare pnum number := # -- 註意:取出存在在這個地址值的值要使用‘&‘符號相當於c的指針 begin if pnum = 0 then dbms_output.put_line(‘you input 0‘); else if pnum = 1 then dbms_output.put_line(‘you input 1‘); else then dbms_output.put_line(‘you input other number‘); end if; end;
循環語句
語法
示例:輸入數字1到10Cursor-光標(遊標)
在Java語言中有集合的概念,在PL/SQL語言中也會用到多條記錄作為整體,此時我們需要用到遊標,遊標可以存儲查詢返回的多條數據 簡言之,光標/遊標類似Java中的ResultSet基本語法
說明光標
CURSOR 光標名 [(參數名數據類型[,參數名數據類型]...)] IS SELECT 語句; 如:使用步驟
fetch關鍵字的作用是將光標的一個值賦到變量中並將指針往後移動一位(遊標的指針默認在第一個值上) 示例:光標的屬性
- %isopen:光標是否已打開
- %rowcount:影響的行數(即已取出的行數)
- %found:當前指針的位置是否仍然有記錄
- %notfound:當前指針位置是否沒有記錄
帶參數的光標
語法
示例:例外
例外例外是程序設計語言提供的一種功能,用來增強程序的健壯性和容錯性 相當於Java中的異常例外的分類
- 系統定義的例外
- No_data_found(沒有找到數據)
- Too_many_rows(select …into語句匹配多個行)
- Zero_Divide(被零除)
- Value_error(算術或轉換錯誤)
- Timeout_on_resource(在等待資源時發生超時)(分布式數據庫相關)
- 用戶定義的例外
定義及處理例外
- 在declare節中定義例外
- out_of exception;
- 在可行語句中引起例外
- raise out_of;(類似Java中的throw)
- 在exception節處理例外
- when out_of then …;(類似Java中的try-catch)
*PL/SQL的應用
實例
declare --部門 cursor cdept is select deptno from dept; pdeptno dept.deptno%type; --部門中員工的薪水 cursor cemp(dno number) is select sal from emp where deptno=dno; psal emp.sal%type; --每個段的人數 count1 number; count2 number; count3 number; --部門的工資總額 salTotal number := 0; begin --部門 open cdept; loop --取一個部門 fetch cdept into pdeptno; exit when cdept%notfound; --初始化 count1:=0; count2:=0; count3:=0; --得到部門的工資總額 select sum(sal) into salTotal from emp where deptno=pdeptno; --取部門的中員工薪水 open cemp(pdeptno); loop --取一個員工的薪水 fetch cemp into psal; exit when cemp%notfound; --判斷 if psal < 3000 then count1:=count1+1; elsif psal >=3000 and psal<6000 then count2:=count2+1; else count3:=count3+1; end if; end loop; close cemp; --保存結果 insert into msg values(pdeptno,count1,count2,count3,nvl(saltotal,0)); end loop; close cdept; commit; dbms_output.put_line(‘完成‘); end;
編寫PL/SQL程序的一般步驟
- 需求分析
- SQL語句:即涉及操作數據庫的邏輯
- 獲取數據庫中的數據
- 使用什麽類型的變量來存放獲取到的數據
- 使用什麽方式來從變量中提取數據(如循環,判斷等)
- 修改數據庫中的數據
- 變量
- 變量的初始值
- 變量的獲取方式
- 滿足什麽條件就改變變量的值
PL/SQL進階內容
存儲過程和存儲函數
存儲過程
存儲過程(Stored Procedure)是在大型數據庫系統中,一組為了完成特定功能的SQL語句集經編譯後存儲在數據庫中,用戶通過指定存儲過程的名字並給出參數(如果該存儲過程帶有參數)來執行它。 存儲過程是數據庫中的一個重要對象,任何一個設計良好的數據庫應用程序都應該用到存儲過程。存儲函數
函數(Function)為一命名的存儲程序,可帶參數,並返回一計算值。函數和過程的結構類似,但必須有一個RETURN子句,用於返回函數值。函數說明要指定函數名、結果值的類型,以及參數類型等。*
簡言之,存儲在數據庫中供所有用戶程序調用的由PL/SQL編寫的子程序被稱為存儲過程或存儲函數。 在Java程序中,不能直接調用PL/SQL編寫好的程序,但是可以調用存儲過程或存儲函數。當然,在存儲過程或存儲函數中也可以調用存儲過程或存儲函數。
存儲過程和存儲函數的區別
一般來講,過程和函數的區別在於函數可以有一個返回值:而過程沒有返回值。但過程和函數都可以通過out指定一個或多個輸出參數。我們可以利用out參數,在過程和函數中實現返回多個值。 通常除去return(返回)值的不同,可以認為兩者相同。使用存儲過程的語法
創建存儲過程
調用存儲過程
示例: 示例:使用存儲函數的語法
創建存儲函數
調用存儲函數
示例存儲過程和存儲函數中的in(輸入參數)和out(輸出參數)
一般來講,過程和函數的區別在於函數可以有一個返回值,而過程沒有返回值。但過程和函數都可以通過out指定一個或多個輸出參數,我們可以利用out參數,在過程和函數中實現返回多個值。使用out參數來返回值
在out參數中返回遊標
使用存儲過程/函數的註意事項
如果只有一個返回值,用存儲函數,否則,就用存儲過程 盡量不在存儲過程或存儲函數中操作事務,因為子程序是交由其他人調用的,不應該操作事務*Java調用存儲過程/存儲函數
創建測試項目,導包 包的位置位於安裝Oracle的機器下的這個目錄 導入項目如下 編寫工具類package demo; import java.sql.*; public class JDBCUtils { private static String driver = "oracle.jdbc.OracleDriver"; private static String url = "jdbc:oracle:thin:@169.254.35.157:1521/orcl"; private static String user = "scott"; private static String password = "tiger"; static{ // 註冊驅動 try { Class.forName(driver); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() { try { return DriverManager.getConnection(url, user, password); } catch (SQLException e) { e.printStackTrace(); } return null; } public static void release(Connection conn, Statement st, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } finally{ rs = null; // 置空作為垃圾回收 } } if (st != null) { try { st.close(); } catch (SQLException e) { e.printStackTrace(); } finally{ st = null; } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } finally{ conn = null; } } } }Statement接口的子接口CallableStatement就是專門用於執行SQL存儲過程的接口 其中紅框內是調用存儲過程的標準方式 編寫測試類分別測試存儲過程和存儲函數
存儲過程
存儲過程如下 測試代碼如下/* procedure queryEmpInformation(eno in number, pename out varchar2, psal out number, pjob out varchar2) */ @Test public void testProcedure() { String sql = "{call queryEmpInformation(?,?,?,?)}"; Connection conn = null; CallableStatement call = null; try { conn = JDBCUtils.getConnection(); call = conn.prepareCall(sql); // 對於in參數,需要對其賦值 call.setInt(1, 7839); // 對於out參數,需要對其聲明 call.registerOutParameter(2, OracleTypes.VARCHAR); call.registerOutParameter(3, OracleTypes.NUMBER); call.registerOutParameter(4, OracleTypes.VARCHAR); // 執行調用存儲過程 call.execute(); // 從CallableStatement對象中取出輸出out參數 String name = call.getString(2); Double sal = call.getDouble(3); String job = call.getString(4); System.out.println("name:" + name + " sal:" + sal + " job:" + job); } catch (SQLException e) { e.printStackTrace(); } finally { JDBCUtils.release(conn, call, null); } }輸出如下
存儲函數
存儲過程如下 測試代碼如下/* function queryEmpIncome(eno in number) return number */ @Test public void testFunction() { String sql = "{?= call queryEmpIncome(?)}"; Connection conn = null; CallableStatement call = null; try { conn = JDBCUtils.getConnection(); call = conn.prepareCall(sql); // 存儲函數的輸出參數即返回值,是第一個問號,對其註冊 call.registerOutParameter(1, OracleTypes.NUMBER); // 賦值輸入參數 call.setInt(2, 7839); call.execute(); double annIncome = call.getDouble(1); System.out.println("annual income:" + annIncome); } catch (SQLException e) { e.printStackTrace(); } finally { JDBCUtils.release(conn, call, null); } }
輸出如下
調用使用光標作為out參數的存儲過程
問題:查詢某個部門中所有員工的所有信息 在編寫PL/SQL時申明包結構 創建包 在包中可以自定義一個數據類型(下面的empcursor),引用原有的數據類型(下面引用了cursor光標類型)作為這個類型 創建包體 在包體中需要實現包頭中定義的所有存儲過程和存儲函數在Java中訪問
使用ResultSet來接受光標參數 具體測試代碼如下@Test public void testCursor() { String sql = "{call mypackage.queryemplist(?,?)}"; Connection conn = null; CallableStatement call = null; try { conn = JDBCUtils.getConnection(); call = conn.prepareCall(sql); call.setInt(1, 10); call.registerOutParameter(2, OracleTypes.CURSOR); call.execute(); // CallableStatement是一個通用的接口,獲取時獲得Oracle適用的實現類對象,可以將其強制轉換成Oracle適用的接口 ResultSet cursor = ((OracleCallableStatement) call).getCursor(2); while (cursor.next()) { System.out.println("name:"+cursor.getString("ename")); System.out.println("hiredate:"+cursor.getDate("hiredate")); } } catch (SQLException e) { e.printStackTrace(); } finally { JDBCUtils.release(conn, call, cursor); // 此處關閉ResultSet時同時關閉了這個光標 } }
觸發器
數據庫觸發器是一個與表相關聯的、存儲的PL/SQL程序。每當一個特定的數據操作語句(Insert,update和delete不包括select)在指定的表上發出時,Oracle自動地執行觸發器中定義的語句序列。觸發器的作用
- 數據確認
- 如:員工漲工資後的工資不能少於漲工資後的工資
- 實施復雜的安全性檢查
- 如:禁止在非工作時間插入新員工
- 審計,跟蹤表上的數據操作(日誌)
- *Oracle中的審計有五種
- 強制審計
- 標準審計(配置)
- 基於值的審計(觸發器審計屬於這一類
- 細粒度審計
- 管理員審計
- 數據的備份和同步
觸發器的類型
- 語句級觸發器
- 在指定的操作語句操作之前或之後執行一次,不管這條語句影響了多少行
- 如:一次插入多條數據,觸發器只執行一次
- 針對的是表
- 行級觸發器
- 觸發語句作用的每一條記錄都被觸發
- 在行級觸發器中使用:old和:new偽記錄變量,識別值的狀態
- 針對的是行
創建觸發器
語法
示例: 限制非工作時間向數據庫插入數據 當非工作時間插入員工執行時會報錯 示例2: 確認數據(檢查emp表中sal 的修改值不低於原值) 運行效果查詢觸發器、過程及函數
- select * from user_triggers;
- select * from user_source;
行級觸發器中觸發語句與偽記錄變量代表的值
觸發器的實際應用
S*使用PL/SQL Developer編寫PL/SQL程序
Hello World
Oracle Database-PL/SQL