day43_Oracle學習筆記_03
十三、PL/SQL程式設計
PL/SQL(Procedure Language/Structured Query Language) 1、PL/SQL是一種高階資料庫程式設計語言,專門用於在各種環境下對Oracle資料庫進行訪問。該語言集成於資料庫伺服器中,所以PL/SQL程式碼可以對資料進行快速高效的處理。 2、PL/SQL是對SQL語言儲存過程語言的擴充套件,是Oracle系統的核心語言。 3、PL/SQL程式由三個塊組成:宣告部分、執行部分、異常處理部分。
13.1、sqldeveloper工具的使用
先去Oracle官網去下載最新版本的sqldeveloper,下載地址:https://www.oracle.com/technetwork/developer-tools/sql-developer/downloads/index.html
得到2個zip壓縮包,如下圖所示:

解壓縮後,找到sqldeveloper.exe點選開啟即可。
新建資料庫連線

就可以使用了。
如果想要連線Mysql資料庫,需要進行配置:工具 --> 首選項 --> 資料庫 --> 第三方 JDBC 驅動包 --> 新增條目,新增所需要的jar包。如下圖所示:

13.2、小案例
小案例-回顧條件表示式:
給員工漲工資:總裁漲1000元 經理漲800元 其他漲400元 寫一段java的JDBC程式,我們這裡寫的是虛擬碼,虛擬碼不能夠執行,但是可以幫助我們分析程式執行的過程和結構。 ResultSet rs = "select empno,job from emp"; while(rs.next()) { int eno = rs.getInt("empno"); String job = rs.getString("job"); if("PRESIDENT".eauals(job)) { update emp sal=sal+1000 where empno=eno; } else if ("MANAGER".eauals(job)) { update emp sal=sal+800 where empno=eno; } else { update emp sal=sal+400 where empno=eno; }
PL/SQL = Procedure Language/SQL = 過程語言/SQL
PL/SQL程式從功能上來講,與上面JDBC的程式想要完成的功能是一樣的。
學習PL/SQL程式的目的:
1、PL/SQL是Oracle對SQL語言的過程化擴充套件,操作效率更高。
2、PL/SQL在SQL命令語言中增加了過程處理語句(分支、迴圈等),使SQL語言具有過程處理能力。
我們把SQL語言的資料操縱能力與過程語言的資料處理能力結合起來,使得PL/SQL面向過程但比過程語言簡單、高效、靈活和實用。
Oracle中對SQL語言的擴充套件叫做PL/SQL。
SQL Server中對SQL語言的擴充套件叫做Transact-sql。
13.3、PL/SQL程式--列印輸出Hello World
示例程式碼如下:
SQL> --宣告部分 SQL> declare 2--說明部分 3begin 4--程式部分 5dbms_output.put_line('Hello World'); 6end; 7--退出編輯環境,並執行PL/SQL程式 8/ PL/SQL 過程已成功完成。 SQL> --預設情況下,Oracle的輸出開關是關閉的。 SQL> --如果要在螢幕上輸出資訊,需要將 serveroutput開關開啟 set serveroutput on SQL> set serveroutput on SQL> / Hello World PL/SQL 過程已成功完成。 SQL>
13.4、變數和常量說明
PL/SQL程式結構截圖如下:

PL/SQL程式結構完整截圖如下:

變數和常量說明:

引用型變數示例程式碼:
--查詢員工編號為7839的姓名和薪水 set serveroutput on declare --定義變數儲存姓名和薪水 --pename varchar2(20); --psalnumber; --定義引用型變數儲存姓名和薪水 pename emp.ename%type; psalemp.sal%type; begin --得到姓名和薪水 --在PL/SQL中,賦值方式有兩種方式,一種是 :=一種是 使用關鍵字into select ename,sal into pename,psal from emp where empno=7839; dbms_output.put_line(pename||'的薪水是'||psal); end; /
記錄型變數示例程式碼:
--查詢員工編號為7839的姓名和薪水 set serveroutput on declare --定義記錄型變數:代表一行 emp_rec emp%rowtype; begin select * into emp_rec from emp where empno=7839; dbms_output.put_line(emp_rec.ename||'的薪水是'||emp_rec.sal); end; /
如何定義常量呢?
pename emp.ename%type; psalemp.sal%type; 加一個constant,就變成常量了。 pename constant emp.ename%type; psal constant emp.sal%type;
13.5、分支

if語句示例程式碼:
set serveroutput on --判斷使用者從鍵盤輸入的數字 --接收鍵盤輸入 --num: 地址值,在該地址上儲存了輸入的值。 accept num prompt '請輸入一個數字'; declare --定義變數儲存輸入的數字 pnum number := # begin if pnum = 0 then dbms_output.put_line('您輸入的是0'); elsif pnum = 1 then dbms_output.put_line('您輸入的是1'); elsif pnum = 2 then dbms_output.put_line('您輸入的是2'); else dbms_output.put_line('其他數字'); end if; end; /
13.6、迴圈

迴圈語句示例程式碼:
--列印1~10 set serveroutput on declare pnum number := 1; begin loop --退出條件 exit when pnum > 10; dbms_output.put_line(pnum); --加一 pnum := pnum + 1; end loop; end; /
13.7、游標Cursor(遊標)== ResultSet
示例:按員工的工種長工資,總裁漲1000元,經理漲800元,其他員工漲400元。
示例程式碼截圖:

游標Cursor(遊標)詳解如下圖所示:

示例程式碼:
--查詢並列印員工的姓名和薪水 /* 1. 游標的屬性: %isopen(游標是否開啟) %rowcount(游標影響的行數) %found(游標找到內容) %notfound(游標沒有找到內容) 2. Oracle中預設,一個會話中只能開啟300個游標 SQL> --修改游標個數需要管理員許可權 SQL> show user USER 為 "SCOTT" SQL> conn sys/[email protected]:1521/orcl as sysdba 已連線。 SQL> show user USER 為 "USER" SQL> show parameter cursor NAMETYPEVALUE ------------------------------------ -------------------------------- ----------- cursor_sharingstringFORCE cursor_space_for_timebooleanFALSE open_cursorsinteger300 session_cached_cursorsinteger20 修改: alter system set open_cursors=400; 3. (思考):上面引數 cursor_sharing 什麼作用? --> 對於資料庫效能優化非常有用。 EXACT(預設值), FORCE(應急使用), SIMILAR */ --示例:使用游標查詢員工姓名和工資,並列印 set serveroutput on declare --定義一個游標 cursor cemp is select ename,sal from emp; --為這個游標定義所需要用到的對應的變數 pename emp.ename%type; psalemp.sal%type; begin --開啟游標 open cemp; loop --取一條記錄到變數中 fetch cemp into pename,psal; --退出條件 --exit when 沒有取到記錄; exit when cemp%notfound; --列印 dbms_output.put_line(pename||'的薪水是'||psal); end loop; --關閉游標 close cemp; end; /
再來給員工漲工資程式碼:
--示例:按員工的工種長工資,總裁漲1000元,經理漲800元,其他員工漲400元。 set serveroutput on declare --alter table "SCOTT"."EMP" rename column "JOB" to empjob cursor cemp is select empno,empjob from emp; --為這個游標定義所需要用到的對應的變數 pempno emp.empno%type; pjobemp.empjob%type; begin open cemp; loop --取一條記錄到變數中 fetch cemp into pempno,pjob; exit when cemp%notfound; --判斷職位 if pjob = 'PRESIDENT' then update emp set sal=sal+1000 where empno=pempno; elsif pjob = 'MANAGER' then update emp set sal=sal+800 where empno=pempno; else update emp set sal=sal+400 where empno=pempno; end if; end loop; close cemp; --Oracle是自動開啟事務的 --Oracle預設的隔離級別是:read committed --why? --> ACID commit; dbms_output.put_line('漲工資完成'); end; /
帶引數的游標
示例程式碼如下:
--查詢某個部門的員工姓名 set serveroutput on declare cursor cemp(dno number) is select ename from emp where deptno=dno; --不一樣的地方 pename emp.ename%type; begin open cemp(20); --不一樣的地方 loop fetch cemp into pename; exit when cemp%notfound; dbms_output.put_line(pename); end loop; close cemp; end; /
游標習題:請點選連結:XXX
13.8、例外
例外:是程式設計語言提供的一種功能,用來增強程式的健壯性和容錯性。
Oracle中對異常的處理
1、系統定義的例外
No_data_found (沒有找到資料)
Too_many_rows (select … into 語句中匹配多個行)
Zero_Divide (被零除)
Value_error (算術或轉換錯誤)
Timeout_on_resource (在等待資源時發生超時)
2、使用者定義的例外
演示:系統定義的例外(被0除)
--系統例外:被0除 set serveroutput on declare pnum number; begin pnum := 1/0; exception when zero_divide then dbms_output.put_line('1:0不能做分母'); dbms_output.put_line('2:0不能做分母'); when value_error then dbms_output.put_line('算術或轉換錯誤'); when others then dbms_output.put_line('其他例外'); end; /
演示:使用者定義的例外以及處理例外
--查詢50號部門的員工姓名 set serveroutput on declare cursor cemp is select ename from emp where deptno=50; pename emp.ename%type; --自定義例外 no_emp_found exception; begin open cemp; --取第一條記錄 fetch cemp into pename; if cemp%notfound then --丟擲例外 raise no_emp_found; end if; --回顧 --Java中是通過IO流來操作硬碟中的檔案, --Java中IO最終是通過什麼方式操作硬碟上的檔案呢?答:通過作業系統的程序。 --Oracle中通過記憶體中的例項操作硬碟中的檔案, --而記憶體中例項最終是怎麼操作硬碟上的檔案呢?答:也是通過作業系統的程序。 --這句執行不到,Oracle中怎麼辦呢?答:通過程序監視器 --pmon: process monitor 程序監視器 close cemp; exception when no_emp_found then dbms_output.put_line('沒有找到員工'); when others then dbms_output.put_line('其他例外'); end; /
13.9、例項
瀑布模型圖解:

/* SQL語句: select to_char(hiredate,'yyyy') from emp; --> 集合 --> 游標 --> 迴圈 --> 退出條件:notfound 變數: 1. 初始值 2. 最終怎麼得到 每年入職的員工人數: count80 number := 0; count81 number := 0; count82 number := 0; count87 number := 0; */ set serveroutput on declare cursor cemp is select to_char(hiredate,'yyyy') from emp; phiredate varchar2(4); --每年入職的員工人數: count80 number := 0; count81 number := 0; count82 number := 0; count87 number := 0; begin open cemp; loop --取一個員工的入職年份到變數中 fetch cemp into phiredate; --退出條件:notfound exit when cemp%notfound; --判斷年份 if phiredate = '1980' then count80:=count80+1; elsif phiredate = '1981' then count81:=count81+1; elsif phiredate = '1982' then count82:=count82+1; else count87:=count87+1; end if; end loop; close cemp; dbms_output.put_line('Total:'||(count80+count81+count82+count87)); dbms_output.put_line('1980年入職的有:'||count80); dbms_output.put_line('1981年入職的有:'||count81); dbms_output.put_line('1982年入職的有:'||count82); dbms_output.put_line('1987年入職的有:'||count87); end; /
例項2:為員工漲工資,從最低工資調起每人漲10%,但工資總額不能超過5萬元,請計算漲工資的人數和漲工資後的工資總額,並輸出漲工資人數及工資總額。
/* SQL語句: select empno,sal from emp order by sal; --> 游標 --> 退出條件:1. 工資總額 > 5w2. notfound 變數: 1. 初始值 2. 最終得到 漲工資的人數: countEmp number := 0; 漲後的工資總額: salTotal number; 方式1. select sum(sal) into salTotal from emp; 方式2. 漲後=漲前 + sal * 0.1 寫程式的原則:能不操作資料庫就不要操作資料庫。 練習:人數:7總額:50205.325 */ set serveroutput on declare cursor cemp is select empno,sal from emp order by sal; pempno emp.empno%type; psalemp.sal%type; --漲工資的人數: countEmp number := 0; --漲後的工資總額: salTotal number; begin --得到初始的工資總額 select sum(sal) into salTotal from emp; open cemp; loop --取一個員工出來到變數中 fetch cemp into pempno,psal; --1. 工資總額 > 5w exit when salTotal > 50000; --2. notfound exit when cemp%notfound; --漲工資操作 update emp set sal=sal*1.1 where empno=pempno; --人數+1 countEmp := countEmp + 1; --2. 漲後工資總額=漲前工資總額 + sal * 0.1 salTotal := salTotal + psal * 0.1; end loop; close cemp; commit; dbms_output.put_line('人數:'||countEmp||'總額:'||salTotal); end; /
例項3:用PL/SQL語言編寫一程式,實現按部門分段(6000以上、(6000,3000)、3000元以下)統計各工資段的職工人數、以及各部門的工資總額(工資總額中不包括獎金)
/* SQL語句: 部門: select deptno from dept; 部門中員工的薪水:select sal from emp where deptno=???;問號是部門編號 變數: 1. 初始值 2. 最終得到 每個段的人數: count1 number; count2 number; count3 number; 部門的工資總額: salTotal number := 0; 得到部門的工資總額的方式: 1.select sum(sal) into salTotal from emp where deptno=???; 2.累加 */ set serveroutput on 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; /
13.10、筆試題2道
筆試1指令碼.txt
create table test1 (id int primary key, name varchar(20), money int); insert into test1 values(1,'Tom',1000); insert into test1 values(2,'Mary',2000); insert into test1 values(3,'Mike',3000); insert into test1 values(4,'Jeff',4000); commit;
示例程式碼如下:
SQL> select * from test1; ID NAMEMONEY ---------- -------------------- ---------- 1 Tom1000 2 Mary2000 3 Mike3000 4 Jeff4000 SQL> select id,name,money,(select money from test1 where id=t.id-1) money1 from test1 t; ID NAMEMONEYMONEY1 ---------- -------------------- ---------- ---------- 1 Tom1000 2 Mary20001000 3 Mike30002000 4 Jeff40003000 SQL>
筆試2指令碼.txt
create table pm_ci (ci_id varchar(20) primary key, stu_ids varchar(100)); insert into pm_ci values('1','1,2,3,4'); insert into pm_ci values('2','1,4'); create table pm_stu (stu_id varchar(20) primary key, stu_name varchar(20)); insert into pm_stu values('1','張三'); insert into pm_stu values('2','李四'); insert into pm_stu values('3','王五'); insert into pm_stu values('4','趙六'); commit;
示例程式碼如下:
SQL> select * from pm_ci; CI_IDSTU_IDS -------------------- ---------------------------------------------------------------------------------------------------- 11,2,3,4 21,4 SQL> select * from pm_stu; STU_IDSTU_NAME -------------------- -------------------- 1張三 2李四 3王五 4趙六 SQL> select c.ci_id,s.stu_name 2from pm_ci c,pm_stu s 3where instr(c.stu_ids,s.stu_id)>0; CI_IDSTU_NAME -------------------- -------------------- 1張三 1李四 1王五 1趙六 2張三 2趙六 已選擇 6 行。 SQL> select ci_id,wm_concat(stu_name) namelist 2from(select c.ci_id,s.stu_name 3from pm_ci c,pm_stu s 4where instr(c.stu_ids,s.stu_id)>0) 5group by ci_id; CI_ID -------------------- NAMELIST ------------------------------------------------------------------------------------------------------------------------------------------------------ 1 張三,李四,王五,趙六 2 張三,趙六 SQL> --設定列的寬度 SQL> col namelist for a50 SQL> select ci_id,wm_concat(stu_name) namelist 2from(select c.ci_id,s.stu_name 3from pm_ci c,pm_stu s 4where instr(c.stu_ids,s.stu_id)>0) 5group by ci_id; CI_IDNAMELIST -------------------- -------------------------------------------------- 1張三,李四,王五,趙六 2張三,趙六 SQL>
十四、儲存過程和儲存函式
14.1、儲存過程
詳解如下:
儲存在資料庫中供所有使用者程式呼叫的子程式(用PL/SQL寫的)叫儲存過程、儲存函式。 建立儲存過程的語法: create [or replace] PROCEDURE 過程名(引數列表) as PL/SQL子程式體;
示例程式碼1:
--列印Hello World,不傳遞引數 /* 呼叫儲存過程的方式: 1. exec sayHelloWorld(); 2. begin sayHelloWorld(); sayHelloWorld(); sayHelloWorld(); end; / */ create or replace procedure sayHelloWorld--注意Oracle中的命名規範,但是這裡為了簡便,我們使用java的命名規範 as --說明部分 begin dbms_output.put_line('Hello World'); end; /
示例程式碼2:
--給指定的員工漲100,並且列印漲前和漲後的工資,傳遞單個引數 create or replace procedure raisesalary(eno in number)--注意:需要指明引數是輸入引數,還是輸出引數 as --定義變數儲存漲前的薪水 psal emp.sal%type; begin --得到漲前的薪水 select sal into psal from emp where empno=eno; --漲100 update emp set sal=sal+100 where empno=eno; --要不要commit呢?答:不要。 --原則:一般情況下,我們不在儲存過程和儲存函式中commit和rollback資料,應該交由呼叫者去做。 dbms_output.put_line('漲前:'||psal||'漲後:'||(psal+100)); end; /
示例程式碼3:
--給指定的員工漲指定額度的工資,傳遞多個引數 create or replace procedure raiseSalary(eno in number,rate in number) as psal emp.sal%type; begin --得到漲前的薪水 select sal into psal from emp where empno=eno; --漲指定額度的工資 update emp set sal=sal*rate where empno=eno; dbms_output.put_line('漲前:'||psal||'漲後:'||(psal*rate)); end;
14.2、儲存函式
詳解如下:
儲存函式和儲存過程的結構類似,但必須有一個return子句,用於返回函式值。 函式說明要指定函式名、結果值的型別,以及引數型別等。 建立儲存函式的語法: create [or replace] FUNCTION 函式名(引數列表) return 函式返回值型別 as PL/SQL子程式體;
示例程式碼1:
--查詢某個員工的年收入 create or replace function queryempincome(eno in number) return number as --定義變數儲存月薪和獎金 psal emp.sal%type; pcomm emp.comm%type; begin select sal,comm into psal,pcomm from emp where empno=eno; --返回年收入 return psal*12+nvl(pcomm,0); end; /
14.3、儲存過程和儲存函式中的in和out引數
詳解如下:
一般來講,儲存過程和儲存函式區別在於儲存函式可以有一個返回值,而儲存過程沒有返回值。 但儲存過程和儲存函式都可以通過out指定一個或多個輸出引數。我們可以利用out引數,在儲存過程和儲存函式中實現返回多個值。 這時儲存函式的功能就被儲存過程取代了,那為什麼還要保留儲存函式呢?答:為了版本的向下相容。 什麼時候使用儲存過程/儲存函式呢? 原則: 一般而言,如果只有一個返回值,就用儲存函式;否則,就用儲存過程。
示例程式碼如下:
--查詢某個員工的姓名 月薪 職位 create or replace procedure queryempinfo(eno in number, pename out varchar2, psalout number, pjobout varchar2) as begin select ename,sal,empjob into pename,psal,pjob from emp where empno=eno; end; /
思考:
1. 查詢某個員工的所有信息 --> 問題:out引數太多
2. 查詢某個部門中的所有員工資訊 --> 問題:返回的是集合
14.4、在Java中呼叫儲存過程和儲存函式
- 在java中想要訪問資料庫,首先要得到Connection物件,通過該物件得到Statement物件(介面),我們使用Statement的子介面CallableStatement。
在Java中呼叫儲存過程和儲存函式 的示例程式碼:
/* create or replace procedure queryempinfo(eno in number, pename out varchar2, psalout number, pjobout varchar2) as begin select ename,sal,empjob into pename,psal,pjob from emp where empno=eno; end; */ @Test public void testProcedure() { // {call <procedure-name>[(<arg1>,<arg2>, ...)]} String sql = "{call queryempinfo(?,?,?,?)}"; 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(); // 取出結果 String name = call.getString(2); double sal = call.getDouble(3); String job = call.getString(4); System.out.println(name + "\t" + sal + "\t" + job); } catch (Exception e) { e.printStackTrace(); }finally{ JDBCUtils.release(conn, call, null); } } /* create or replace function queryempincome(eno in number) return number as --定義變數儲存月薪和獎金 psal emp.sal%type; pcomm emp.comm%type; begin select sal,comm into psal,pcomm from emp where empno=eno; --返回年收入 return psal*12+nvl(pcomm,0); end; */ @Test public void testFunction(){ // {?= call <procedure-name>[(<arg1>,<arg2>, ...)]} String sql = "{?=call queryempincome(?)}"; Connection conn = null; CallableStatement call = null; try { conn = JDBCUtils.getConnection(); call = conn.prepareCall(sql); // 第一個是out引數,需要宣告 call.registerOutParameter(1, OracleTypes.NUMBER); // 第二個是in引數,需要賦值 call.setInt(2, 7839); // 執行儲存函式 call.execute(); // 取出年收入 double income = call.getDouble(1); System.out.println(income); } catch (Exception e) { e.printStackTrace(); }finally{ JDBCUtils.release(conn, call, null); } }
14.5、在out引數中使用游標
查詢某個部門中所有員工的所有資訊,返回的是集合。
我們需要宣告包結構和建立包體,其中包和包體也是資料庫的物件。
示例程式碼如下:
/* 1. 查詢某個員工的所有資訊 --> 問題:out引數太多 2. 查詢某個部門中的所有員工資訊 --> 問題:返回的是集合 */ // 在out引數中使用游標 // 查詢某個部門中所有員工的所有資訊 /* --宣告包結構 create or replace package myPackage as type empcursor is ref cursor; procedure queryEmpList(dno in number,emplist out empcursor); end myPackage; --建立包體 create or replace package body myPackage as procedure queryEmpList(dno in number,emplist out empcursor) as begin open emplist for select * from emp where deptno=dno; end queryEmpList; end myPackage; */ @Test public void testCursor(){ String sql = "{call myPackage.queryEmpList(?,?)}"; Connection conn = null; CallableStatement call = null; ResultSet rs = null; try { conn = JDBCUtils.getConnection(); call = conn.prepareCall(sql); // 對於in引數,需要賦值 call.setInt(1, 20); // 對於out引數 ,需要宣告 call.registerOutParameter(2, OracleTypes.CURSOR); // 執行儲存函式 call.execute(); // 取出結果 rs = ((OracleCallableStatement)call).getCursor(2); while (rs.next()) { // 取出一個員工,示例只取出了兩列 String name = rs.getString("ename"); double sal = rs.getDouble("sal"); System.out.println(name + "\t" + sal); } } catch (Exception e) { e.printStackTrace(); }finally{ JDBCUtils.release(conn, call, rs); } }
十五、觸發器
詳解如下:
資料庫觸發器是一個與表相關聯的、儲存的PL/SQL程式。 每當一個特定的資料操作語句(insert、update、delete)在指定的表上發出時,Oracle自動地執行觸發器中定義的語句序列。 觸發器的型別: 語句級(表級)觸發器:在指定的操作語句操作之前或之後執行一次,不管這條語句影響了多上行。 行級觸發器(for each row):觸發語句作用的每一條記錄都被觸發。在行級觸發器中使用 :old 和 :new 偽記錄變數來識別值的狀態。 建立觸發器的語法: create or replace trigger 觸發器名 before | after insert | update | delete [of 列名] on 表名 [for each row [when(條件)]]--觸發器的型別 declare begin ...... end; 觸發器的用途: 1. 資料確認 2. 實施複雜的安全性檢查 3. 做審計,跟蹤表上所做的資料操作等(想要做什麼事,不被查到,需要關閉資料庫的審計功能) 4. 資料的備份和同步
示例1:
--每當成功插入新員工後,自動列印“成功插入了新員工” create or replace trigger abcd after insert on emp declare begin dbms_output.put_line('成功插入了新員工'); end;
15.1、觸發器應用一:實施複雜的安全性檢查
禁止在非工作時間向資料庫中插入資料 週末:to_char(sysdate,'day') in ('星期六','星期日') 上班前 下班後:to_number(tochar(sysdate,'hh24')) not between 9 and 17 ------------------------------------------------------------------ create or replace trigger securityemp before insert on emp declare begin if to_char(sysdate,'day') in ('星期六','星期日') or to_number(to_char(sysdate,'hh24')) not between 9 and 17 then --禁止insert raise_application_error(-20002,'禁止在非工作時間向資料庫中插入資料');-- -20000到-20999之間 end if; end; ------------------------------------------------------------------ SQL> insert into emp(empno,ename,sal,deptno) values(1001, 'tom',3000, 20); insert into emp(empno,ename,sal,deptno) values(1001, 'tom',3000, 20) * 第 1 行出現錯誤: ORA-20002: 禁止在非工作時間向資料庫中插入資料 ORA-06512: 在 "SCOTT.SECURITYEMP", line 6 ORA-04088: 觸發器 'SCOTT.SECURITYEMP' 執行過程中出錯 SQL>
15.2、觸發器應用二:資料確認
檢查emp表中的sal的修改值不低於原值 ------------------------------------------------------------------ create or replace trigger checksalary before update on emp for each row declare begin if :new.sal<:old.sal then raise_application_error(-20001,'漲後的工資不能少於漲前的工資。漲前:'||:old.sal||'漲後:'||:new.sal);-- -20000到-20999之間 end if; end; ------------------------------------------------------------------ 測試程式碼: SQL> update emp set sal=sal+1 where empno=7839; 已更新 1 行。 SQL> update emp set sal=sal-1 where empno=7839; update emp set sal=sal-1 where empno=7839 * 第 1 行出現錯誤: ORA-20001: 漲後的工資不能少於漲前的工資。漲前:7987漲後:7986 ORA-06512: 在 "SCOTT.CHECKSALARY", line 4 ORA-04088: 觸發器 'SCOTT.CHECKSALARY' 執行過程中出錯 SQL>
15.3、練習:限制每個部門只招聘10名員工,超過計劃則報出錯誤資訊
限制每個部門只招聘10名員工,超過計劃則報出錯誤資訊 ------------------------------------------------------------------ create or replace trigger limitEmpCount before insert on emp declare count10 number := 0; count20 number := 0; count30 number := 0; begin select count(*) into count10 from emp where deptno=10; select count(*) into count20 from emp where deptno=20; select count(*) into count30 from emp where deptno=30; if count10>=10 then raise_application_error(-20005,'部門:10,員工已有'||count10||'人'); elsif count20>=10 then raise_application_error(-20005,'部門:20,員工已有'||count20||'人'); elsif count30>=10 then raise_application_error(-20005,'部門:30,員工已有'||count30||'人'); end if; end; ------------------------------------------------------------------ 測試程式碼: SQL> insert into emp(empno,ename,sal,deptno) values(1030,'tom',3000, 30); insert into emp(empno,ename,sal,deptno) values(1030,'tom',3000, 30) * 第 1 行出現錯誤: ORA-20005: 部門:30,員工已有10人 ORA-06512: 在 "SCOTT.LIMITEMPCOUNT", line 12 ORA-04088: 觸發器 'SCOTT.LIMITEMPCOUNT' 執行過程中出錯 SQL>