PLSQL編程詳解

1.1 基本結(jié)構(gòu)

PL/SQL程序由三個(gè)塊組成漓库,即聲明部分恶座、執(zhí)行部分搬瑰、異常處理部分臣咖。

DECLARE 
   --聲明部分: 在此聲明PL/SQL用到的變量,類(lèi)型及游標(biāo)涩金,以及局部的存儲(chǔ)過(guò)程和函數(shù)
BEGIN
   -- 執(zhí)行部分:  過(guò)程及SQL 語(yǔ)句  , 即程序的主要部分
EXCEPTION
   -- 執(zhí)行異常部分: 錯(cuò)誤處理
END;

1.2 命名規(guī)則

標(biāo)識(shí)符 命名規(guī)則 例子
程序變量 V_name V_name
程序常量 C_Name C_company_name
游標(biāo)變量 Cursor_Name Cursor_Emp
異常標(biāo)識(shí) E_name E_too_many
表類(lèi)型 Name_table_type Emp_record_type
Name_table Emp
記錄類(lèi)型 Name_record Emp_record
SQL*Plus 替代變量 P_name P_sa-
綁定變量 G_name G_year_sa-

1.3 記錄類(lèi)型

TYPE record_name IS RECORD(
   v1 data_type1  [NOT NULL]  [:= default_value ],
   v2 data_type2  [NOT NULL]  [:= default_value ],
   ......
   vn data_typen  [NOT NULL]  [:= default_value ] );

范例如下:

DECLARE
--定義與hr.employees表中的這幾個(gè)列相同的記錄數(shù)據(jù)類(lèi)型
   TYPE RECORD_TYPE_EMPLOYEES IS RECORD(
        f_name   hr.employees.first_name%TYPE,
        h_date   hr.employees.hire_date%TYPE,
        j_id     hr.employees.job_id%TYPE);
--聲明一個(gè)該記錄數(shù)據(jù)類(lèi)型的記錄變量
   v_emp_record RECORD_TYPE_EMPLOYEES;
 
BEGIN
   SELECT first_name, hire_date, job_id INTO v_emp_record
   FROM employees
   WHERE employee_id = &emp_id;
 
   DBMS_OUTPUT.PUT_LINE('雇員名稱(chēng):'||v_emp_record.f_name
             ||'  雇傭日期:'||v_emp_record.h_date
             ||'  崗位:'||v_emp_record.j_id);
END;

1.4 數(shù)組類(lèi)型

TYPE varray_name IS VARRAY(size) OF element_type [NOT NULL];

范例

DECLARE
--定義一個(gè)最多保存5個(gè)VARCHAR(25)數(shù)據(jù)類(lèi)型成員的VARRAY數(shù)據(jù)類(lèi)型
   TYPE reg_varray_type IS VARRAY(5) OF VARCHAR(25);
--聲明一個(gè)該VARRAY數(shù)據(jù)類(lèi)型的變量
   v_reg_varray REG_VARRAY_TYPE;
 
BEGIN
--用構(gòu)造函數(shù)語(yǔ)法賦予初值
   v_reg_varray := reg_varray_type
         ('中國(guó)', '美國(guó)', '英國(guó)', '日本', '法國(guó)');
 
   DBMS_OUTPUT.PUT_LINE('地區(qū)名稱(chēng):'||v_reg_varray(1)||'谱醇、'
                                    ||v_reg_varray(2)||'、'
                                    ||v_reg_varray(3)||'步做、'
                                    ||v_reg_varray(4));
   DBMS_OUTPUT.PUT_LINE('賦予初值NULL的第5個(gè)成員的值:'||v_reg_varray(5));
--用構(gòu)造函數(shù)語(yǔ)法賦予初值后就可以這樣對(duì)成員賦值
   v_reg_varray(5) := '法國(guó)';
   DBMS_OUTPUT.PUT_LINE('第5個(gè)成員的值:'||v_reg_varray(5));
END;

1.5 %TYPE

使用%TYPE定義一個(gè)變量副渴,其數(shù)據(jù)類(lèi)型與已經(jīng)定義的某個(gè)數(shù)據(jù)變量(尤其是表的某一列)的數(shù)據(jù)類(lèi)型相一致

DECLARE
   v_empno emp.empno%TYPE :=&no;
   Type t_record is record (
        v_name   emp.ename%TYPE,
        v_sa-   emp.sal%TYPE,
        v_date   emp.hiredate%TYPE);
   Rec t_record;
BEGIN
   SELECT ename, sal, hiredate INTO Rec FROM emp WHERE empno=v_empno;
   DBMS_OUTPUT.PUT_LINE(Rec.v_name||'---'||Rec.v_sal||'--'||Rec.v_date);
END;

1.6 %ROWTYPE

使用%ROWTYPE操作符, 返回一個(gè)記錄類(lèi)型, 其數(shù)據(jù)類(lèi)型和數(shù)據(jù)庫(kù)表的數(shù)據(jù)結(jié)構(gòu)相一致。

DECLARE
    v_empno emp.empno%TYPE :=&no;
    rec emp%ROWTYPE;
BEGIN
    SELECT * INTO rec FROM emp WHERE empno=v_empno;
    DBMS_OUTPUT.PUT_LINE('姓名:'||rec.ename||'工資:'||rec.sal||'工作時(shí)間:'||rec.hiredate);
END;

1.7 TABLE

使用TABLE定義記錄表數(shù)據(jù)類(lèi)型全度。它與記錄類(lèi)型相似煮剧,它可以處理多行記錄,類(lèi)似于二維數(shù)組來(lái)模仿數(shù)據(jù)庫(kù)中的表将鸵。

TYPE table_name IS TABLE OF element_type [NOT NULL]
INDEX BY [BINARY_INTEGER | PLS_INTEGER | VARRAY2];
方法 描述
EXISTS(n) 如果集合的第n個(gè)成員存在勉盅,則返回true
COUNT 返回已經(jīng)分配了存儲(chǔ)空間即賦值了的成員數(shù)量
FIRSTLAST FIRST:返回成員的最低下標(biāo)值LAST:返回成員的最高下標(biāo)值
PRIOR(n) 返回下標(biāo)為n的成員的前一個(gè)成員的下標(biāo)。如果沒(méi)有則返回NUL-
NEXT(N) 返回下標(biāo)為n的成員的后一個(gè)成員的下標(biāo)咨堤。如果沒(méi)有則返回NUL-
TRIM TRIM:刪除末尾一個(gè)成員TRIM(n) :刪除末尾n個(gè)成員
DELETE DELETE:刪除所有成員DELETE(n) :刪除第n個(gè)成員DELETE(m, n) :刪除從n到m的成員
EXTEND EXTEND:添加一個(gè)null成員EXTEND(n):添加n個(gè)null成員EXTEND(n,i):添加n個(gè)成員菇篡,其值與第i個(gè)成員相同
LIMIT 返回在varray類(lèi)型變量中出現(xiàn)的最高下標(biāo)值

范例如下

DECLARE
  TYPE dept_table_type IS TABLE OF
       dept%ROWTYPE INDEX BY BINARY_INTEGER;
  my_dname_table dept_table_type;
  v_count number(2) :=4;
BEGIN
  FOR int IN 1 .. v_count LOOP
    SELECT * INTO my_dname_table(int) FROM dept WHERE deptno=int*10;
  END LOOP;
  FOR int IN my_dname_table.FIRST .. my_dname_table.LAST LOOP
  DBMS_OUTPUT.PUT_LINE('Department number: '||my_dname_table(int).deptno);
  DBMS_OUTPUT.PUT_LINE('Department name: '|| my_dname_table(int).dname);
  END LOOP;
END;

1.8 運(yùn)算符

-- 變量賦值
variable  := expression ;

-- 空值加數(shù)字仍是空值
NULL + <數(shù)字> = NULL 

-- 空值加(連接)字符,結(jié)果為字符
NULL || <字符串> = < 字符串> 

-- CHAR 轉(zhuǎn)換為 NUMBER
v_total := TO_NUMBER('100.0') + sal;

--  NUMBER 轉(zhuǎn)換為CHAR
v_comm := TO_CHAR('123.45') || '元' ;

-- 字符轉(zhuǎn)換為日期
v_date := TO_DATE('2001.07.03','yyyy.mm.dd');

-- 日期轉(zhuǎn)換為字符
v_to_day := TO_CHAR(SYSDATE, 'yyyy.mm.dd hh24:mi:ss') ;

1.9 注釋

  • 使用雙 ‘-‘ ( 減號(hào)) 加注釋
  • 使用 /* */ 來(lái)加一行或多行注釋

條件語(yǔ)句IF

IF <布爾表達(dá)式> THEN
  PL/SQL 和 SQL語(yǔ)句
END IF;
-----------------------
IF <布爾表達(dá)式> THEN
  PL/SQL 和 SQL語(yǔ)句
ELSE
  其它語(yǔ)句
END IF;
-----------------------
IF <布爾表達(dá)式> THEN
  PL/SQL 和 SQL語(yǔ)句
ELSIF < 其它布爾表達(dá)式> THEN
  其它語(yǔ)句
ELSIF < 其它布爾表達(dá)式> THEN
  其它語(yǔ)句
ELSE
  其它語(yǔ)句
END IF;

范例如下

DECLARE
    v_empno  employees.employee_id%TYPE :=&empno;
    V_salary employees.salary%TYPE;
    V_comment VARCHAR2(35);
BEGIN
   SELECT salary INTO v_salary FROM employees 
   WHERE employee_id = v_empno;
   IF v_salary < 1500 THEN
       V_comment:= '太少了,加點(diǎn)吧~!';
   ELSIF v_salary <3000 THEN
      V_comment:= '多了點(diǎn),少點(diǎn)吧~!';
   ELSE
      V_comment:= '沒(méi)有薪水~!';
   END IF;
   DBMS_OUTPUT.PUT_LINE(V_comment);
   exception
     when no_data_found then
        DBMS_OUTPUT.PUT_LINE('沒(méi)有數(shù)據(jù)~!');
     when others then
        DBMS_OUTPUT.PUT_LINE(sqlcode || '---' || sqlerrm);        
END;

2.1 條件語(yǔ)句CASE

CASE 條件表達(dá)式
  WHEN 條件表達(dá)式結(jié)果1 THEN 
     語(yǔ)句段1
  WHEN 條件表達(dá)式結(jié)果2 THEN
     語(yǔ)句段2
  ......
  WHEN 條件表達(dá)式結(jié)果n THEN
     語(yǔ)句段n
  [ELSE 條件表達(dá)式結(jié)果]
END;

------------------

CASE 
  WHEN 條件表達(dá)式1 THEN
     語(yǔ)句段1
  WHEN 條件表達(dá)式2 THEN
     語(yǔ)句段2
  ......
  WHEN 條件表達(dá)式n THEN 
     語(yǔ)句段n
  [ELSE 語(yǔ)句段]
END;

范例如下

DECLARE
  V_grade char(1) := UPPER('&p_grade');
  V_appraisal VARCHAR2(20);
BEGIN
  V_appraisal :=
  CASE v_grade
    WHEN 'A' THEN 'Excellent'
    WHEN 'B' THEN 'Very Good'
    WHEN 'C' THEN 'Good'
    ELSE 'No such grade'
  END;
  DBMS_OUTPUT.PUT_LINE('Grade:'||v_grade||'  Appraisal: '|| v_appraisal);
END;

DECLARE
   v_first_name employees.first_name%TYPE;
   v_job_id employees.job_id%TYPE;
   v_salary employees.salary%TYPE;
   v_sal_raise NUMBER(3,2);
BEGIN
   SELECT first_name,   job_id,   salary INTO
          v_first_name, v_job_id, v_salary
   FROM employees WHERE employee_id = &emp_id;
   CASE
      WHEN v_job_id = 'PU_CLERK' THEN
         IF v_salary < 3000 THEN v_sal_raise := .08;
         ELSE v_sal_raise := .07;
         END IF;
      WHEN v_job_id = 'SH_CLERK' THEN
         IF v_salary < 4000 THEN v_sal_raise := .06;
         ELSE v_sal_raise := .05;
         END IF;
      WHEN v_job_id = 'ST_CLERK' THEN
         IF v_salary < 3500 THEN v_sal_raise := .04;
         ELSE v_sal_raise := .03;
         END IF;
      ELSE
         DBMS_OUTPUT.PUT_LINE('該崗位不漲工資: '||v_job_id);
   END CASE;
   DBMS_OUTPUT.PUT_LINE(v_first_name||'的崗位是'||v_job_id
                                    ||'一喘、的工資是'||v_salary
                                    ||'驱还、工資漲幅是'||v_sal_raise);
END;

2.2 循環(huán)語(yǔ)句LOOP

 LOOP
      要執(zhí)行的語(yǔ)句;
      EXIT WHEN <條件語(yǔ)句> --條件滿(mǎn)足,退出循環(huán)語(yǔ)句
  END LOOP;

范例

DECLARE
    int NUMBER(2) :=0;
BEGIN
   LOOP
      int := int + 1;
      DBMS_OUTPUT.PUT_LINE('int 的當(dāng)前值為:'||int);
      EXIT WHEN int =10;
   END LOOP;
END;

2.3 循環(huán)語(yǔ)句While

WHILE <布爾表達(dá)式> LOOP
    要執(zhí)行的語(yǔ)句;
END LOOP;

范例

DECLARE 
  x NUMBER :=1;
BEGIN
   WHILE x<=10 LOOP
      DBMS_OUTPUT.PUT_LINE('X的當(dāng)前值為:'||x);
       x:= x+1;
   END LOOP;
END;

2.4 循環(huán)語(yǔ)句For

[<<循環(huán)標(biāo)簽>>]
FOR 循環(huán)計(jì)數(shù)器 IN [ REVERSE ] 下限 .. 上限 LOOP
  要執(zhí)行的語(yǔ)句;
END LOOP [循環(huán)標(biāo)簽];

范例

CREATE TABLE temp_table(num_col NUMBER);

DECLARE
    V_counter NUMBER := 10;
BEGIN
   INSERT INTO temp_table(num_col) VALUES (v_counter );
   FOR v_counter IN 20 .. 25 LOOP
      INSERT INTO temp_table (num_col ) VALUES ( v_counter );
   END LOOP;
   INSERT INTO temp_table(num_col) VALUES (v_counter );
   FOR v_counter IN REVERSE 20 .. 25 LOOP
      INSERT INTO temp_table (num_col ) VALUES ( v_counter );
   END LOOP;
END ;

DROP TABLE temp_table;

3.1 顯式游標(biāo)

顯式游標(biāo)處理需四個(gè) PL/SQL步驟:

  1. 定義/聲明游標(biāo):就是定義一個(gè)游標(biāo)名凸克,以及與其相對(duì)應(yīng)的SELECT 語(yǔ)句
  • 打開(kāi)游標(biāo):就是執(zhí)行游標(biāo)所對(duì)應(yīng)的SELECT 語(yǔ)句议蟆,將其查詢(xún)結(jié)果放入工作區(qū),并且指針指向工作區(qū)的首部萎战,標(biāo)識(shí)游標(biāo)結(jié)果集合咐容。如果游標(biāo)查詢(xún)語(yǔ)句中帶有FOR UPDATE選項(xiàng),OPEN 語(yǔ)句還將鎖定數(shù)據(jù)庫(kù)表中游標(biāo)結(jié)果集合對(duì)應(yīng)的數(shù)據(jù)行
  • 提取游標(biāo)數(shù)據(jù):就是檢索結(jié)果集合中的數(shù)據(jù)行蚂维,放入指定的輸出變量中
  • 關(guān)閉游標(biāo):當(dāng)提取和處理完游標(biāo)結(jié)果集合數(shù)據(jù)后戳粒,應(yīng)及時(shí)關(guān)閉游標(biāo)路狮,以釋放該游標(biāo)所占用的系統(tǒng)資源,并使該游標(biāo)的工作區(qū)變成無(wú)效蔚约,不能再使用FETCH 語(yǔ)句取其中數(shù)據(jù)奄妨。關(guān)閉后的游標(biāo)可以使用OPEN 語(yǔ)句重新打開(kāi)
-- define cursor
CURSOR cursor_name[(parameter[, parameter]…)] 
    [RETURN datatype]
IS 
    select_statement;

-- open cursor
OPEN cursor_name[([parameter =>] value[, [parameter =>] value]…)];

-- fetch cursor
FETCH cursor_name INTO {variable_list | record_variable };

--close cursor
CLOSE cursor_name;

范例

--ex1
DECLARE
   CURSOR c_cursor 
   IS SELECT first_name || last_name, Salary 
   FROM EMPLOYEES 
   WHERE rownum<11;   
   v_ename  EMPLOYEES.first_name%TYPE;
   v_sa-   EMPLOYEES.Salary%TYPE;   
BEGIN
  OPEN c_cursor;
  FETCH c_cursor INTO v_ename, v_sal;
  WHILE c_cursor%FOUND LOOP
     DBMS_OUTPUT.PUT_LINE(v_ename||'---'||to_char(v_sal) );
     FETCH c_cursor INTO v_ename, v_sal;
  END LOOP;
  CLOSE c_cursor;
END;

-- ex2
DECLARE
  DeptRec    DEPARTMENTS%ROWTYPE;
  Dept_name  DEPARTMENTS.DEPARTMENT_NAME%TYPE;
  Dept_loc   DEPARTMENTS.LOCATION_ID%TYPE;
  CURSOR c1 IS 
  SELECT DEPARTMENT_NAME, LOCATION_ID FROM DEPARTMENTS 
  WHERE DEPARTMENT_ID <= 30;
  
  CURSOR c2(dept_no NUMBER DEFAULT 10) IS
    SELECT DEPARTMENT_NAME, LOCATION_ID FROM DEPARTMENTS 
    WHERE DEPARTMENT_ID <= dept_no;
  CURSOR c3(dept_no NUMBER DEFAULT 10) IS 
    SELECT * FROM DEPARTMENTS 
    WHERE DEPARTMENTS.DEPARTMENT_ID <=dept_no;
BEGIN
  OPEN c1;
  LOOP
    FETCH c1 INTO dept_name, dept_loc;
    EXIT WHEN c1%NOTFOUND;
        DBMS_OUTPUT.PUT_LINE(dept_name||'---'||dept_loc);
    END LOOP;
    CLOSE c1;

    OPEN c2;
    LOOP
        FETCH c2 INTO dept_name, dept_loc;
        EXIT WHEN c2%NOTFOUND;
        DBMS_OUTPUT.PUT_LINE(dept_name||'---'||dept_loc);
    END LOOP;
    CLOSE c2;

    OPEN c3(dept_no =>20);
    LOOP
        FETCH c3 INTO deptrec;
        EXIT WHEN c3%NOTFOUND;
        DBMS_OUTPUT.PUT_LINE(deptrec.DEPARTMENT_ID||'---'||deptrec.DEPARTMENT_NAME||'---'||deptrec.LOCATION_ID);
    END LOOP;
    CLOSE c3;
END;

3.2 游標(biāo)屬性

  • Cursor_name%FOUND:布爾型屬性,當(dāng)最近一次提取游標(biāo)操作FETCH成功則為 TRUE,否則為FALSE苹祟;
  • Cursor_name%NOTFOUND:布爾型屬性砸抛,與%FOUND相反;
  • Cursor_name%ISOPEN:布爾型屬性树枫,當(dāng)游標(biāo)已打開(kāi)時(shí)返回 TRUE直焙;
  • Cursor_name%ROWCOUNT:數(shù)字型屬性,返回已從游標(biāo)中讀取的記錄數(shù)砂轻。

范例

-- 給工資低于1200 的員工增加工資50
DECLARE
   v_empno  EMPLOYEES.EMPLOYEE_ID%TYPE;
   v_sa-     EMPLOYEES.Salary%TYPE;
   CURSOR c_cursor IS SELECT EMPLOYEE_ID, Salary FROM EMPLOYEES; 
BEGIN
   OPEN c_cursor;
   LOOP
      FETCH c_cursor INTO v_empno, v_sal;
      EXIT WHEN c_cursor%NOTFOUND; 
      IF v_sal<=1200 THEN
            UPDATE EMPLOYEES SET Salary=Salary+50 WHERE EMPLOYEE_ID=v_empno;
            DBMS_OUTPUT.PUT_LINE('編碼為'||v_empno||'工資已更新!');
      END IF;
   DBMS_OUTPUT.PUT_LINE('記錄數(shù):'|| c_cursor %ROWCOUNT);
   END LOOP;
   CLOSE c_cursor;
END; 

-- 沒(méi)有參數(shù)且沒(méi)有返回值的游標(biāo)
DECLARE
   v_f_name employees.first_name%TYPE;
   v_j_id   employees.job_id%TYPE;
   CURSOR c1       --聲明游標(biāo),沒(méi)有參數(shù)沒(méi)有返回值
   IS
      SELECT first_name, job_id FROM employees 
      WHERE department_id = 20;
BEGIN
   OPEN c1;        --打開(kāi)游標(biāo)
   LOOP
      FETCH c1 INTO v_f_name, v_j_id;    --提取游標(biāo)
      IF c1%FOUND THEN
         DBMS_OUTPUT.PUT_LINE(v_f_name||'的崗位是'||v_j_id);
      ELSE
         DBMS_OUTPUT.PUT_LINE('已經(jīng)處理完結(jié)果集了');
         EXIT;
      END IF;
   END LOOP;
   CLOSE c1;   --關(guān)閉游標(biāo)
END;

-- 有參數(shù)且沒(méi)有返回值的游標(biāo)
DECLARE
   v_f_name employees.first_name%TYPE;
   v_h_date employees.hire_date%TYPE;
   CURSOR c2(dept_id NUMBER, j_id VARCHAR2) --聲明游標(biāo),有參數(shù)沒(méi)有返回值
   IS
      SELECT first_name, hire_date FROM employees
      WHERE department_id = dept_id AND job_id = j_id;
BEGIN
   OPEN c2(90, 'AD_VP');  --打開(kāi)游標(biāo),傳遞參數(shù)值
   LOOP
      FETCH c2 INTO v_f_name, v_h_date;    --提取游標(biāo)
      IF c2%FOUND THEN
         DBMS_OUTPUT.PUT_LINE(v_f_name||'的雇傭日期是'||v_h_date);
      ELSE
         DBMS_OUTPUT.PUT_LINE('已經(jīng)處理完結(jié)果集了');
         EXIT;
      END IF;
   END LOOP;
   CLOSE c2;   --關(guān)閉游標(biāo)
END;

-- 有參數(shù)且有返回值的游標(biāo)
DECLARE
   TYPE emp_record_type IS RECORD(
        f_name   employees.first_name%TYPE,
        h_date   employees.hire_date%TYPE);
   v_emp_record EMP_RECORD_TYPE;

   CURSOR c3(dept_id NUMBER, j_id VARCHAR2) --聲明游標(biāo),有參數(shù)有返回值
          RETURN EMP_RECORD_TYPE
   IS
      SELECT first_name, hire_date FROM employees
      WHERE department_id = dept_id AND job_id = j_id;
BEGIN
   OPEN c3(j_id => 'AD_VP', dept_id => 90);  --打開(kāi)游標(biāo),傳遞參數(shù)值
   LOOP
      FETCH c3 INTO v_emp_record;    --提取游標(biāo)
      IF c3%FOUND THEN
         DBMS_OUTPUT.PUT_LINE(v_emp_record.f_name||'的雇傭日期是'
                            ||v_emp_record.h_date);
      ELSE
         DBMS_OUTPUT.PUT_LINE('已經(jīng)處理完結(jié)果集了');
         EXIT;
      END IF;
   END LOOP;
   CLOSE c3;   --關(guān)閉游標(biāo)
END;

-- 基于游標(biāo)定義記錄變量
DECLARE
   CURSOR c4(dept_id NUMBER, j_id VARCHAR2) --聲明游標(biāo),有參數(shù)沒(méi)有返回值
   IS
      SELECT first_name f_name, hire_date FROM employees
      WHERE department_id = dept_id AND job_id = j_id;
    --基于游標(biāo)定義記錄變量奔誓,比聲明記錄類(lèi)型變量要方便,不容易出錯(cuò)
    v_emp_record c4%ROWTYPE;
BEGIN
   OPEN c4(90, 'AD_VP');  --打開(kāi)游標(biāo),傳遞參數(shù)值
   LOOP
      FETCH c4 INTO v_emp_record;    --提取游標(biāo)
      IF c4%FOUND THEN
         DBMS_OUTPUT.PUT_LINE(v_emp_record.f_name||'的雇傭日期是'
                            ||v_emp_record.hire_date);
      ELSE
         DBMS_OUTPUT.PUT_LINE('已經(jīng)處理完結(jié)果集了');
         EXIT;
      END IF;
   END LOOP;
   CLOSE c4;   --關(guān)閉游標(biāo)
END;

3.3 游標(biāo)FOR循環(huán)

游標(biāo)FOR循環(huán)語(yǔ)句舔清,自動(dòng)執(zhí)行游標(biāo)的OPEN丝里、FETCH、CLOSE語(yǔ)句和循環(huán)語(yǔ)句的功能体谒;當(dāng)進(jìn)入循環(huán)時(shí)杯聚,游標(biāo)FOR循環(huán)語(yǔ)句自動(dòng)打開(kāi)游標(biāo),并提取第一行游標(biāo)數(shù)據(jù)抒痒,當(dāng)程序處理完當(dāng)前所提取的數(shù)據(jù)而進(jìn)入下一次循環(huán)時(shí)幌绍,游標(biāo)FOR循環(huán)語(yǔ)句自動(dòng)提取下一行數(shù)據(jù)供程序處理,當(dāng)提取完結(jié)果集合中的所有數(shù)據(jù)行后結(jié)束循環(huán)故响,并自動(dòng)關(guān)閉游標(biāo)傀广。

FOR index_variable IN cursor_name[(value[, value]…)] LOOP
    -- 游標(biāo)數(shù)據(jù)處理代碼
END LOOP;

index_variable為游標(biāo)FOR 循環(huán)語(yǔ)句隱含聲明的索引變量,該變量為記錄變量彩届,其結(jié)構(gòu)與游標(biāo)查詢(xún)語(yǔ)句返回的結(jié)構(gòu)集合的結(jié)構(gòu)相同伪冰。在程序中可以通過(guò)引用該索引記錄變量元素來(lái)讀取所提取的游標(biāo)數(shù)據(jù),index_variable中各元素的名稱(chēng)與游標(biāo)查詢(xún)語(yǔ)句選擇列表中所制定的列名相同樟蠕。如果在游標(biāo)查詢(xún)語(yǔ)句的選擇列表中存在計(jì)算列贮聂,則必須為這些計(jì)算列指定別名后才能通過(guò)游標(biāo)FOR 循環(huán)語(yǔ)句中的索引變量來(lái)訪問(wèn)這些列數(shù)據(jù)。

范例

-- ex1
DECLARE
   CURSOR c_sal IS SELECT employee_id, first_name || last_name ename, salary
   FROM employees ;
BEGIN
   --隱含打開(kāi)游標(biāo)
   FOR v_sal IN c_sal LOOP
   --隱含執(zhí)行一個(gè)FETCH語(yǔ)句
      DBMS_OUTPUT.PUT_LINE(to_char(v_sal.employee_id)||'---'|| v_sal.ename||'---'||to_char(v_sal.salary)) ;
   --隱含監(jiān)測(cè)c_sal%NOTFOUND
   END LOOP;
--隱含關(guān)閉游標(biāo)
END;

-- ex2 當(dāng)所聲明的游標(biāo)帶有參數(shù)時(shí)寨辩,通過(guò)游標(biāo)FOR 循環(huán)語(yǔ)句為游標(biāo)傳遞參數(shù)
DECLARE
  CURSOR c_cursor(dept_no NUMBER DEFAULT 10) 
  IS
    SELECT department_name, location_id FROM departments WHERE department_id <= dept_no;
BEGIN
    DBMS_OUTPUT.PUT_LINE('當(dāng)dept_no參數(shù)值為30:');
    FOR c1_rec IN c_cursor(30) LOOP        DBMS_OUTPUT.PUT_LINE(c1_rec.department_name||'---'||c1_rec.location_id);
    END LOOP;
    DBMS_OUTPUT.PUT_LINE(CHR(10)||'使用默認(rèn)的dept_no參數(shù)值10:');
    FOR c1_rec IN c_cursor LOOP        DBMS_OUTPUT.PUT_LINE(c1_rec.department_name||'---'||c1_rec.location_id);
    END LOOP;
END;

不要在程序中對(duì)游標(biāo)進(jìn)行人工操作吓懈;不要在程序中定義用于控制FOR循環(huán)的記錄

3.4 處理隱式游標(biāo)

對(duì)于非查詢(xún)語(yǔ)句,如修改靡狞、刪除操作耻警,則由ORACLE 系統(tǒng)自動(dòng)地為這些操作設(shè)置游標(biāo)并創(chuàng)建其工作區(qū),這些由系統(tǒng)隱含創(chuàng)建的游標(biāo)稱(chēng)為隱式游標(biāo),隱式游標(biāo)的名字為SQL甘穿,這是由ORACLE 系統(tǒng)定義的腮恩。對(duì)于隱式游標(biāo)的操作,如定義扒磁、打開(kāi)庆揪、取值及關(guān)閉操作,都由ORACLE 系統(tǒng)自動(dòng)地完成妨托,無(wú)需用戶(hù)進(jìn)行處理。用戶(hù)只能通過(guò)隱式游標(biāo)的相關(guān)屬性吝羞,來(lái)完成相應(yīng)的操作兰伤。在隱式游標(biāo)的工作區(qū)中,所存放的數(shù)據(jù)是與用戶(hù)自定義的顯示游標(biāo)無(wú)關(guān)的钧排、最新處理的一條SQL 語(yǔ)句所包含的數(shù)據(jù)敦腔。

格式調(diào)用為: SQL%

范例

--  刪除EMPLOYEES表中某部門(mén)的所有員工,如果該部門(mén)中已沒(méi)有員工恨溜,則在DEPARTMENT表中刪除該部門(mén)
DECLARE
    V_deptno department_id%TYPE :=&p_deptno;
BEGIN
    DELETE FROM employees WHERE department_id=v_deptno;
    IF SQL%NOTFOUND THEN
        DELETE FROM departments WHERE department_id=v_deptno;
    END IF;
END;

-- 通過(guò)隱式游標(biāo)SQL的%ROWCOUNT屬性來(lái)了解修改了多少行
DECLARE
   v_rows NUMBER;
BEGIN
--更新數(shù)據(jù)
   UPDATE employees SET salary = 30000
   WHERE department_id = 90 AND job_id = 'AD_VP';
--獲取默認(rèn)游標(biāo)的屬性值
   v_rows := SQL%ROWCOUNT;
   DBMS_OUTPUT.PUT_LINE('更新了'||v_rows||'個(gè)雇員的工資');
--回退更新符衔,以便使數(shù)據(jù)庫(kù)的數(shù)據(jù)保持原樣
   ROLLBACK;
END;

3.5 NO_DATA_FOUND 和 %NOTFOUND的區(qū)別

  • SELECT … INTO 語(yǔ)句觸發(fā) NO_DATA_FOUND
  • 當(dāng)一個(gè)顯式游標(biāo)的WHERE子句未找到時(shí)觸發(fā)%NOTFOUND糟袁;
  • 當(dāng)UPDATEDELETE 語(yǔ)句的WHERE 子句未找到時(shí)觸發(fā) SQL%NOTFOUND判族;
  • 在提取循環(huán)中要用 %NOTFOUND%FOUND來(lái)確定循環(huán)的退出條件,不要用 NO_DATA_FOUND

3.6 使用游標(biāo)更新和刪除數(shù)據(jù)

為了對(duì)正在處理(查詢(xún))的行不被另外的用戶(hù)改動(dòng)项戴,ORACLE 提供一個(gè) FOR UPDATE 子句來(lái)對(duì)所選擇的行進(jìn)行鎖住形帮。該需求迫使ORACLE鎖定游標(biāo)結(jié)果集合的行,可以防止其他事務(wù)處理更新或刪除相同的行周叮,直到您的事務(wù)處理提交或回退為止辩撑。

SELECT column_list FROM table_list FOR UPDATE [OF column[, column]…] [NOWAIT]

范例

-- 從EMPLOYEES表中查詢(xún)某部門(mén)的員工情況,將其工資最低定為 1500仿耽;
DECLARE 
    V_deptno employees.department_id%TYPE :=&p_deptno;
    CURSOR emp_cursor 
  IS 
  SELECT employees.employee_id, employees.salary 
    FROM employees WHERE employees.department_id=v_deptno
  FOR UPDATE NOWAIT;
BEGIN
    FOR emp_record IN emp_cursor LOOP
    IF emp_record.salary < 1500 THEN
        UPDATE employees SET salary=1500
    WHERE CURRENT OF emp_cursor;
    END IF;
    END LOOP;
--    COMMIT;
END; 
 
-- 將EMPLOYEES表中部門(mén)編碼為90合冀、崗位為AD_VP的雇員的工資都更新為2000元;
DECLARE
   v_emp_record employees%ROWTYPE;
   CURSOR c1
   IS
      SELECT * FROM employees FOR UPDATE;
BEGIN
   OPEN c1;
   LOOP
      FETCH c1 INTO v_emp_record;
      EXIT WHEN c1%NOTFOUND;
      IF v_emp_record.department_id = 90 AND
         v_emp_record.job_id = 'AD_VP'
      THEN
         UPDATE employees SET salary = 20000
         WHERE CURRENT OF c1;  --更新當(dāng)前游標(biāo)行對(duì)應(yīng)的數(shù)據(jù)行
      END IF;
   END LOOP;
   COMMIT;   --提交已經(jīng)修改的數(shù)據(jù)
   CLOSE c1;
END;

3.7 游標(biāo)變量

游標(biāo)變量操作也包括打開(kāi)项贺、提取和關(guān)閉三個(gè)步驟君躺。

1. 打開(kāi)游標(biāo)變量
打開(kāi)游標(biāo)變量時(shí)使用的是OPEN…FOR 語(yǔ)句。格式為:

OPEN {cursor_variable_name | :host_cursor_variable_name}
FOR select_statement;

cursor_variable_name 為游標(biāo)變量敬扛,host_cursor_variable_name 為PL/SQL主機(jī)環(huán)境(如OCI: ORACLE Call Interface晰洒,Pro*c 程序等)中聲明的游標(biāo)變量。
OPEN…FOR 語(yǔ)句可以在關(guān)閉當(dāng)前的游標(biāo)變量之前重新打開(kāi)游標(biāo)變量啥箭,而不會(huì)導(dǎo)致 CURSOR_ALREAD_OPEN 異常錯(cuò)誤谍珊。新打開(kāi)游標(biāo)變量時(shí),前一個(gè)查詢(xún)的內(nèi)存處理區(qū)將被釋放急侥。

2 . 提取游標(biāo)變量數(shù)據(jù)
使用FETCH語(yǔ)句提取游標(biāo)變量結(jié)果集合中的數(shù)據(jù)砌滞。格式為:

FETCH {cursor_variable_name | :host_cursor_variable_name}
INTO {variable [, variable]…| record_variable};

cursor_variable_namehost_cursor_variable_name 分別為游標(biāo)變量和宿主游標(biāo)變量名稱(chēng)侮邀; variablerecord_variable 分別為普通變量和記錄變量名稱(chēng)。

3. 關(guān)閉游標(biāo)變量
CLOSE語(yǔ)句關(guān)閉游標(biāo)變量贝润,格式為:

CLOSE {cursor_variable_name | :host_cursor_variable_name}

cursor_variable_namehost_cursor_variable_name 分別為游標(biāo)變量和宿主游標(biāo)變量名稱(chēng)绊茧,如果應(yīng)用程序試圖關(guān)閉一個(gè)未打開(kāi)的游標(biāo)變量,則將導(dǎo)致 INVALID_CURSOR 異常錯(cuò)誤

-- 強(qiáng)類(lèi)型參照游標(biāo)變量類(lèi)型
DECLARE
    TYPE emp_job_rec IS RECORD(
        Employee_id employees.employee_id%TYPE,
        Employee_name employees.first_name%TYPE,
        Job_title employees.job_id%TYPE
    );
    TYPE emp_job_refcur_type IS REF CURSOR RETURN emp_job_rec;
    Emp_refcur emp_job_refcur_type ;
    Emp_job emp_job_rec;
BEGIN
    OPEN emp_refcur FOR 
    SELECT employees.employee_id, employees.first_name||employees.last_name, employees.job_id 
  FROM employees 
  ORDER BY employees.department_id;
  
    FETCH emp_refcur INTO emp_job;
    WHILE emp_refcur%FOUND LOOP
       DBMS_OUTPUT.PUT_LINE(emp_job.employee_id||': '||emp_job.employee_name||' is a '||emp_job.job_title);
    FETCH emp_refcur INTO emp_job;
    END LOOP;
END;

-- 弱類(lèi)型參照游標(biāo)變量類(lèi)型
DECLARE
    Type refcur_t IS REF CURSOR;
    Refcur refcur_t;
    TYPE sample_rec_type IS RECORD (
        Id number,
        Description VARCHAR2 (30)
    );
    sample sample_rec_type;
    selection varchar2(1) := UPPER (SUBSTR ('&tab', 1, 1));
BEGIN
    IF selection='D' THEN
        OPEN refcur FOR 
    SELECT departments.department_id, departments.department_name FROM departments;
        DBMS_OUTPUT.PUT_LINE('Department data');
    ELSIF selection='E' THEN
        OPEN refcur FOR 
    SELECT employees.employee_id, employees.first_name||' is a '||employees.job_id FROM employees;
        DBMS_OUTPUT.PUT_LINE('Employee data');
    ELSE
        DBMS_OUTPUT.PUT_LINE('Please enter ''D'' or ''E''');
        RETURN;
    END IF;
    DBMS_OUTPUT.PUT_LINE('----------------------');
    FETCH refcur INTO sample;
    WHILE refcur%FOUND LOOP
        DBMS_OUTPUT.PUT_LINE(sample.id||': '||sample.description);
        FETCH refcur INTO sample;
    END LOOP;
    CLOSE refcur;
END;

-- 使用游標(biāo)變量(沒(méi)有RETURN子句)
DECLARE
--定義一個(gè)游標(biāo)數(shù)據(jù)類(lèi)型
   TYPE emp_cursor_type IS REF CURSOR;
--聲明一個(gè)游標(biāo)變量
   c1 EMP_CURSOR_TYPE;
--聲明兩個(gè)記錄變量
   v_emp_record employees%ROWTYPE;
   v_reg_record regions%ROWTYPE;

BEGIN
   OPEN c1 FOR SELECT * FROM employees WHERE department_id = 20;
   LOOP
      FETCH c1 INTO v_emp_record;
      EXIT WHEN c1%NOTFOUND;
      DBMS_OUTPUT.PUT_LINE(v_emp_record.first_name||'的雇傭日期是'
                            ||v_emp_record.hire_date);
   END LOOP;
--將同一個(gè)游標(biāo)變量對(duì)應(yīng)到另一個(gè)SELECT語(yǔ)句
   OPEN c1 FOR SELECT * FROM regions WHERE region_id IN(1打掘,2);
   LOOP
      FETCH c1 INTO v_reg_record;
      EXIT WHEN c1%NOTFOUND;
      DBMS_OUTPUT.PUT_LINE(v_reg_record.region_id||'表示'
                            ||v_reg_record.region_name);
   END LOOP;
   CLOSE c1;
END;

-- 使用游標(biāo)變量(有RETURN子句)
DECLARE
--定義一個(gè)與employees表中的這幾個(gè)列相同的記錄數(shù)據(jù)類(lèi)型
   TYPE emp_record_type IS RECORD(
        f_name   employees.first_name%TYPE,
        h_date   employees.hire_date%TYPE,
        j_id     employees.job_id%TYPE);
--聲明一個(gè)該記錄數(shù)據(jù)類(lèi)型的記錄變量
   v_emp_record EMP_RECORD_TYPE;
--定義一個(gè)游標(biāo)數(shù)據(jù)類(lèi)型
   TYPE emp_cursor_type IS REF CURSOR
        RETURN EMP_RECORD_TYPE;
--聲明一個(gè)游標(biāo)變量
   c1 EMP_CURSOR_TYPE;
BEGIN
   OPEN c1 FOR SELECT first_name, hire_date, job_id
               FROM employees WHERE department_id = 20;
   LOOP
      FETCH c1 INTO v_emp_record;
      EXIT WHEN c1%NOTFOUND;
      DBMS_OUTPUT.PUT_LINE('雇員名稱(chēng):'||v_emp_record.f_name
                ||'  雇傭日期:'||v_emp_record.h_date
                ||'  崗位:'||v_emp_record.j_id);
   END LOOP;
   CLOSE c1;
END;

4.1 異常處理概念

有三種類(lèi)型的異常錯(cuò)誤:

1.預(yù)定義錯(cuò)誤:ORACLE預(yù)定義的異常情況大約有24個(gè)华畏。對(duì)這種異常情況的處理,無(wú)需在程序中定義尊蚁,由ORACLE自動(dòng)將其引發(fā)亡笑。
2. 非預(yù)定義錯(cuò)誤:即其他標(biāo)準(zhǔn)的ORACLE錯(cuò)誤。對(duì)這種異常情況的處理横朋,需要用戶(hù)在程序中定義仑乌,然后由ORACLE自動(dòng)將其引發(fā)。
3. 用戶(hù)定義錯(cuò)誤:程序執(zhí)行過(guò)程中琴锭,出現(xiàn)編程人員認(rèn)為的非正常情況晰甚。對(duì)這種異常情況的處理,需要用戶(hù)在程序中定義决帖,然后顯式地在程序中將其引發(fā)厕九。

EXCEPTION
   WHEN first_exception THEN  <code to handle first exception >
   WHEN second_exception THEN  <code to handle second exception >
   WHEN OTHERS THEN  <code to handle others exception >
END;

4.2 預(yù)定義的異常處理

只需在PL/SQL塊的異常處理部分,直接引用相應(yīng)的異常情況名古瓤,并對(duì)其完成相應(yīng)的異常錯(cuò)誤處理即可止剖。

錯(cuò)誤號(hào) 異常錯(cuò)誤信息名稱(chēng) 說(shuō)明
ORA-0001 Dup_val_on_index 違反了唯一性限制
ORA-0051 Timeout-on-resource 在等待資源時(shí)發(fā)生超時(shí)
ORA-0061 Transaction-backed-out 由于發(fā)生死鎖事務(wù)被撤消
ORA-1001 Invalid-CURSOR 試圖使用一個(gè)無(wú)效的游標(biāo)
ORA-1012 Not-logged-on 沒(méi)有連接到ORACLE
ORA-1017 Login-denied 無(wú)效的用戶(hù)名/口令
ORA-1403 No_data_found SELECT INTO沒(méi)有找到數(shù)據(jù)
ORA-1422 Too_many_rows SELECT INTO 返回多行
ORA-1476 Zero-divide 試圖被零除
ORA-1722 Invalid-NUMBER 轉(zhuǎn)換一個(gè)數(shù)字失敗
ORA-6500 Storage-error 內(nèi)存不夠引發(fā)的內(nèi)部錯(cuò)誤
ORA-6501 Program-error 內(nèi)部錯(cuò)誤
ORA-6502 Value-error 轉(zhuǎn)換或截?cái)噱e(cuò)誤
ORA-6504 Rowtype-mismatch 宿主游標(biāo)變量與 PL/SQL變量有不兼容行類(lèi)型
ORA-6511 CURSOR-already-OPEN 試圖打開(kāi)一個(gè)已處于打開(kāi)狀態(tài)的游標(biāo)
ORA-6530 Access-INTO-nul- 試圖為null 對(duì)象的屬性賦值
ORA-6531 Collection-is-nul- 試圖將Exists 以外的集合( collection)方法應(yīng)用于一個(gè)null pl/sql 表上或varray上
ORA-6532 Subscript-outside-limit 對(duì)嵌套或varray索引得引用超出聲明范圍以外
ORA-6533 Subscript-beyond-count 對(duì)嵌套或varray 索引得引用大于集合中元素的個(gè)數(shù).
-- 更新指定員工工資,如工資小于1500落君,則加100穿香;
DECLARE
   v_empno employees.employee_id%TYPE := &empno;
   v_sa-  employees.salary%TYPE;
BEGIN
   SELECT salary INTO v_sal FROM employees WHERE employee_id = v_empno;
   IF v_sal<=1500 THEN 
        UPDATE employees SET salary = salary + 100 WHERE employee_id=v_empno; 
        DBMS_OUTPUT.PUT_LINE('編碼為'||v_empno||'員工工資已更新!');     
   ELSE
        DBMS_OUTPUT.PUT_LINE('編碼為'||v_empno||'員工工資已經(jīng)超過(guò)規(guī)定值!');
   END IF;
EXCEPTION
   WHEN NO_DATA_FOUND THEN  
      DBMS_OUTPUT.PUT_LINE('數(shù)據(jù)庫(kù)中沒(méi)有編碼為'||v_empno||'的員工');
   WHEN TOO_MANY_ROWS THEN
      DBMS_OUTPUT.PUT_LINE('程序運(yùn)行錯(cuò)誤!請(qǐng)使用游標(biāo)');
   WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE(SQLCODE||'---'||SQLERRM);
END; 

4.3 非預(yù)定義的異常處理

必須對(duì)非定義的ORACLE錯(cuò)誤進(jìn)行定義。步驟如下:

1 . 定義異常情況 <異常情況> EXCEPTION;
2 . 將定義異常情況绎速,與標(biāo)準(zhǔn)的ORACLE錯(cuò)誤聯(lián)系起來(lái): PRAGMA EXCEPTION_INIT(<異常情況>, <錯(cuò)誤代碼>);
3 . 在異常情況處理部分對(duì)異常情況做出相應(yīng)的處理皮获。

-- 刪除指定部門(mén)的記錄信息,以確保該部門(mén)沒(méi)有員工纹冤。
INSERT INTO departments VALUES(50, 'FINANCE', 'CHICAGO');

DECLARE
   v_deptno departments.department_id%TYPE := &deptno;
   deptno_remaining EXCEPTION;
   PRAGMA EXCEPTION_INIT(deptno_remaining, -2292);
   /* -2292 是違反一致性約束的錯(cuò)誤代碼 */
BEGIN
   DELETE FROM departments WHERE department_id = v_deptno;
EXCEPTION
   WHEN deptno_remaining THEN 
      DBMS_OUTPUT.PUT_LINE('違反數(shù)據(jù)完整性約束!');
   WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE(SQLCODE||'---'||SQLERRM);
END;

4.4 用戶(hù)自定義的異常處理

用戶(hù)定義的異常錯(cuò)誤是通過(guò)顯式使用 RAISE 語(yǔ)句來(lái)觸發(fā)洒宝。當(dāng)引發(fā)一個(gè)異常錯(cuò)誤時(shí),控制就轉(zhuǎn)向到 EXCEPTION塊異常錯(cuò)誤部分萌京,執(zhí)行錯(cuò)誤處理代碼雁歌。對(duì)于這類(lèi)異常情況的處理,步驟如下:

1 . 定義異常情況 <異常情況> EXCEPTION;
2 . RAISE <異常情況>知残;
3 . 在異常情況處理部分對(duì)異常情況做出相應(yīng)的處理

-- 更新指定員工工資靠瞎,增加100;
DECLARE
   v_empno employees.employee_id%TYPE :=&empno;
   no_result  EXCEPTION;
BEGIN
   UPDATE employees SET salary = salary+100 WHERE employee_id = v_empno;
   IF SQL%NOTFOUND THEN
      RAISE no_result;
   END IF;
EXCEPTION
   WHEN no_result THEN 
      DBMS_OUTPUT.PUT_LINE('你的數(shù)據(jù)更新語(yǔ)句失敗了!');
   WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE(SQLCODE||'---'||SQLERRM);
END;

4.5 自定義異常代碼

調(diào)用DBMS_STANDARD(ORACLE提供的包)包所定義的RAISE_APPLICATION_ERROR過(guò)程,可以重新定義異常錯(cuò)誤消息乏盐,它為應(yīng)用程序提供了一種與ORACLE交互的方法佳窑。
RAISE_APPLICATION_ERROR 的語(yǔ)法如下:

RAISE_APPLICATION_ERROR(error_number,error_message,[keep_errors] );

這里的 error_number 是從 –20,000–20,999 之間的參數(shù),
error_message 是相應(yīng)的提示信息(< 2048 字節(jié))父能,
keep_errors 為可選神凑,如果 keep_errors =TRUE ,則新錯(cuò)誤將被添加到已經(jīng)引發(fā)的錯(cuò)誤列表中罕模。如果 keep_errors=FALSE(缺省), 則新錯(cuò)誤將替換當(dāng)前的錯(cuò)誤列表窖铡。

-- 創(chuàng)建一個(gè)函數(shù)get_salary, 該函數(shù)檢索指定部門(mén)的工資總和,其中定義了-20991和-20992號(hào)錯(cuò)誤烈菌,分別處理參數(shù)為空和非法部門(mén)代碼兩種錯(cuò)誤:
 
CREATE TABLE errlog(
  Errcode NUMBER,
  Errtext CHAR(40));

CREATE OR REPLACE FUNCTION get_salary(p_deptno NUMBER)
RETURN NUMBER 
AS
  v_sal NUMBER;
BEGIN
  IF p_deptno IS NULL THEN
    RAISE_APPLICATION_ERROR(-20991, ’部門(mén)代碼為空’);
  ELSIF p_deptno<0 THEN
    RAISE_APPLICATION_ERROR(-20992, ’無(wú)效的部門(mén)代碼’);
  ELSE
    SELECT SUM(employees.salary) INTO v_sal FROM employees 
    WHERE employees.department_id=p_deptno;
    RETURN v_sal;
  END IF;
END;

DECLARE 
  V_salary NUMBER(7,2);
  V_sqlcode NUMBER;
  V_sqlerr VARCHAR2(512);
  Null_deptno EXCEPTION;
  Invalid_deptno EXCEPTION;
  PRAGMA EXCEPTION_INIT(null_deptno,-20991);
  PRAGMA EXCEPTION_INIT(invalid_deptno, -20992);
BEGIN
  V_salary :=get_salary(10);
  DBMS_OUTPUT.PUT_LINE('10號(hào)部門(mén)工資:' || TO_CHAR(V_salary));

  BEGIN
    V_salary :=get_salary(-10);
  EXCEPTION
    WHEN invalid_deptno THEN
      V_sqlcode :=SQLCODE;
      V_sqlerr  :=SQLERRM;
      INSERT INTO errlog(errcode, errtext) 
      VALUES(v_sqlcode, v_sqlerr);
      COMMIT;
  END inner1;

  V_salary :=get_salary(20);
  DBMS_OUTPUT.PUT_LINE('部門(mén)號(hào)為20的工資為:'||TO_CHAR(V_salary));

  BEGIN
    V_salary :=get_salary(NULL);
  END inner2;

  V_salary := get_salary(30);
  DBMS_OUTPUT.PUT_LINE('部門(mén)號(hào)為30的工資為:'||TO_CHAR(V_salary));

  EXCEPTION
    WHEN null_deptno THEN
      V_sqlcode :=SQLCODE;
      V_sqlerr  :=SQLERRM;
      INSERT INTO errlog(errcode, errtext) VALUES(v_sqlcode, v_sqlerr);
      COMMIT;
    WHEN OTHERS THEN
         DBMS_OUTPUT.PUT_LINE(SQLCODE||'---'||SQLERRM);
END outer;


-- 定義觸發(fā)器岔霸,使用RAISE_APPLICATION_ERROR阻止沒(méi)有員工姓名的新員式記錄插入:
CREATE OR REPLACE TRIGGER tr_insert_emp
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
  IF :new.first_name IS NULL OR :new.last_name is null THEN
    RAISE_APPLICATION_ERROR(-20000,'Employee must have a name.');
  END IF;
END;

4.6 異常錯(cuò)誤處理編程

由于ORACLE 的錯(cuò)信息最大長(zhǎng)度是512字節(jié)薛躬,為了得到完整的錯(cuò)誤提示信息,我們可用 SQLERRMSUBSTR 函數(shù)一起得到錯(cuò)誤提示信息呆细,方便進(jìn)行錯(cuò)誤,特別是如果WHEN OTHERS異常處理器時(shí)更為方便八匠。

SQLCODE  返回遇到的Oracle錯(cuò)誤號(hào),
SQLERRM  返回遇到的Oracle錯(cuò)誤信息.
 
如:  SQLCODE=-100   è SQLERRM=’no_data_found ‘
 SQLCODE=0      è SQLERRM=’normal, successfual completion’

范例

-- 將ORACLE錯(cuò)誤代碼及其信息存入錯(cuò)誤代碼表
CREATE TABLE errors (errnum NUMBER(4), errmsg VARCHAR2(100));

DECLARE
   err_msg  VARCHAR2(100);
BEGIN
   /*  得到所有 ORACLE 錯(cuò)誤信息  */
   FOR err_num IN -100 .. 0 LOOP
      err_msg := SQLERRM(err_num);
      INSERT INTO errors VALUES(err_num, err_msg);
   END LOOP;
END;
DROP TABLE errors;

 
 
-- 查詢(xún)ORACLE錯(cuò)誤代碼絮爷;
BEGIN
   INSERT INTO employees(employee_id, first_name,last_name,hire_date,department_id)
   VALUES(2222, 'Eric','Hu', SYSDATE, 20);
   DBMS_OUTPUT.PUT_LINE('插入數(shù)據(jù)記錄成功!');
   
   INSERT INTO employees(employee_id, first_name,last_name,hire_date,department_id)
   VALUES(2222, '胡','勇', SYSDATE, 20);
   DBMS_OUTPUT.PUT_LINE('插入數(shù)據(jù)記錄成功!');
EXCEPTION
   WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE(SQLCODE||'---'||SQLERRM);
END;

 
-- 利用ORACLE錯(cuò)誤代碼,編寫(xiě)異常錯(cuò)誤處理代碼梨树;
DECLARE
   empno_remaining EXCEPTION;
   PRAGMA EXCEPTION_INIT(empno_remaining, -1);
   /* -1 是違反唯一約束條件的錯(cuò)誤代碼 */
BEGIN
   INSERT INTO employees(employee_id, first_name,last_name,hire_date,department_id)
   VALUES(3333, 'Eric','Hu', SYSDATE, 20);
   DBMS_OUTPUT.PUT_LINE('插入數(shù)據(jù)記錄成功!');
   
   INSERT INTO employees(employee_id, first_name,last_name,hire_date,department_id)
   VALUES(3333, '胡','勇',SYSDATE, 20);
   DBMS_OUTPUT.PUT_LINE('插入數(shù)據(jù)記錄成功!');
EXCEPTION
   WHEN empno_remaining THEN 
      DBMS_OUTPUT.PUT_LINE('違反數(shù)據(jù)完整性約束!');
   WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE(SQLCODE||'---'||SQLERRM);
END;

5.1 過(guò)程與函數(shù)

過(guò)程和函數(shù)統(tǒng)稱(chēng)為PL/SQL子程序坑夯,他們是被命名的PL/SQL塊,均存儲(chǔ)在數(shù)據(jù)庫(kù)中抡四,并通過(guò)輸入柜蜈、輸出參數(shù)或輸入/輸出參數(shù)與其調(diào)用者交換信息。過(guò)程和函數(shù)的唯一區(qū)別是函數(shù)總向調(diào)用者返回?cái)?shù)據(jù)指巡,而過(guò)程則不返回?cái)?shù)據(jù)淑履。

5.2 創(chuàng)建函數(shù)

CREATE [OR REPLACE] FUNCTION function_name
 (arg1 [ { IN | OUT | IN OUT }] type1 [DEFAULT value1],
 [arg2 [ { IN | OUT | IN OUT }] type2 [DEFAULT value1]],
 ......
 [argn [ { IN | OUT | IN OUT }] typen [DEFAULT valuen]])
 [ AUTHID DEFINER | CURRENT_USER ]
RETURN return_type
 IS | AS
    <類(lèi)型.變量的聲明部分>
BEGIN
    執(zhí)行部分
    RETURN expression
EXCEPTION
    異常處理部分
END function_name;   

IN, OUT, IN OUT是形參的模式。若省略藻雪,則為 IN 模式秘噪。 IN 模式的形參只能將實(shí)參傳遞給形參,進(jìn)入函數(shù)內(nèi)部勉耀,但只能讀不能寫(xiě)指煎,函數(shù)返回時(shí)實(shí)參的值不變。 OUT 模式的形參會(huì)忽略調(diào)用時(shí)的實(shí)參值(或說(shuō)該形參的初始值總是 NULL)便斥,但在函數(shù)內(nèi)部可以被讀或?qū)懼寥溃瘮?shù)返回時(shí)形參的值會(huì)賦予給實(shí)參。 IN OUT 具有前兩種模式的特性枢纠,即調(diào)用時(shí)像街,實(shí)參的值總是傳遞給形參,結(jié)束時(shí),形參的值傳遞給實(shí)參宅广。調(diào)用時(shí)葫掉,對(duì)于 IN 模式的實(shí)參可以是常量或變量,但對(duì)于 OUTIN OUT模式的實(shí)參必須是變量跟狱。只有在確認(rèn)function_name函數(shù)是新函數(shù)或是要更新的函數(shù)時(shí)俭厚,才使用OR REPALCE關(guān)鍵字,否則容易刪除有用的函數(shù)驶臊。

--獲取某部門(mén)的工資總和
CREATE OR REPLACE
FUNCTION get_salary(
  Dept_no NUMBER,
  Emp_count OUT NUMBER)
  RETURN NUMBER
IS
  V_sum NUMBER;
BEGIN
  SELECT SUM(SALARY), count(*) INTO V_sum, emp_count
    FROM EMPLOYEES WHERE DEPARTMENT_ID=dept_no;
  RETURN v_sum;
EXCEPTION
   WHEN NO_DATA_FOUND THEN
      DBMS_OUTPUT.PUT_LINE('你需要的數(shù)據(jù)不存在!');
   WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE(SQLCODE||'---'||SQLERRM);
END get_salary;

5.3 函數(shù)調(diào)用

在調(diào)用函數(shù)時(shí)挪挤,可以使用以下三種方法:

  • 位置表示法:按形參的排列順序,依次寫(xiě)出實(shí)參的名稱(chēng)关翎,而將形參與實(shí)參關(guān)聯(lián)起來(lái)進(jìn)行傳遞 argument_value1[,argument_value2 …]
  • 名稱(chēng)表示法:寫(xiě)出實(shí)參對(duì)應(yīng)的形參扛门,而將形參與實(shí)參關(guān)聯(lián)起來(lái)進(jìn)行傳遞 argument => parameter [,…]
  • 組合傳遞:同時(shí)使用位置表示法和名稱(chēng)表示法為函數(shù)傳遞參數(shù),使用位置表示法所傳遞的參數(shù)必須放在名稱(chēng)表示法所傳遞的參數(shù)前面纵寝。
-- 位置表示法
DECLARE
  V_num NUMBER;
  V_sum NUMBER;
BEGIN
  V_sum :=get_salary(10, v_num);
  DBMS_OUTPUT.PUT_LINE('部門(mén)號(hào)為:10的工資總和:'||v_sum||'论寨,人數(shù)為:'||v_num);
END;

-- 名稱(chēng)表示法
DECLARE
  V_num NUMBER;
    V_sum NUMBER;
BEGIN
    V_sum :=get_salary(emp_count => v_num, dept_no => 10);
    DBMS_OUTPUT.PUT_LINE('部門(mén)號(hào)為:10的工資總和:'||v_sum||',人數(shù)為:'||v_num);
END;

-- 組合傳遞
CREATE OR REPLACE FUNCTION demo_fun(
  Name VARCHAR2,--注意VARCHAR2不能給精度爽茴,如:VARCHAR2(10)葬凳,其它類(lèi)似
  Age INTEGER,
  Sex VARCHAR2)
  RETURN VARCHAR2
AS
  V_var VARCHAR2(32);
BEGIN
  V_var := name||':'||TO_CHAR(age)||'歲.'||sex;
  RETURN v_var;
END;
 
DECLARE
  Var VARCHAR(32);
BEGIN
  Var := demo_fun('user1', 30, sex => '男');
  DBMS_OUTPUT.PUT_LINE(var);
 
  Var := demo_fun('user2', age => 40, sex => '男');
  DBMS_OUTPUT.PUT_LINE(var);
 
  Var := demo_fun('user3', sex => '女', age => 20);
  DBMS_OUTPUT.PUT_LINE(var);
END;

5.4 參數(shù)默認(rèn)值

在CREATE OR REPLACE FUNCTION 語(yǔ)句中聲明函數(shù)參數(shù)時(shí)可以使用DEFAULT關(guān)鍵字為輸入?yún)?shù)指定默認(rèn)值。

CREATE OR REPLACE FUNCTION demo_fun(
  Name VARCHAR2,
  Age INTEGER,
  Sex VARCHAR2 DEFAULT '男')
  RETURN VARCHAR2
AS
  V_var VARCHAR2(32);
BEGIN
  V_var := name||':'||TO_CHAR(age)||'歲.'||sex;
  RETURN v_var;
END;

具有默認(rèn)值的函數(shù)創(chuàng)建后室奏,在函數(shù)調(diào)用時(shí)火焰,如果沒(méi)有為具有默認(rèn)值的參數(shù)提供實(shí)際參數(shù)值,函數(shù)將使用該參數(shù)的默認(rèn)值胧沫。但當(dāng)調(diào)用者為默認(rèn)參數(shù)提供實(shí)際參數(shù)時(shí)昌简,函數(shù)將使用實(shí)際參數(shù)值。在創(chuàng)建函數(shù)時(shí)绒怨,只能為輸入?yún)?shù)設(shè)置默認(rèn)值纯赎,而不能為輸入/輸出參數(shù)設(shè)置默認(rèn)值。

DECLARE
 varVARCHAR(32);
BEGIN
 Var := demo_fun('user1', 30);
 DBMS_OUTPUT.PUT_LINE(var);
 Var := demo_fun('user2', age => 40);
 DBMS_OUTPUT.PUT_LINE(var);
 Var := demo_fun('user3', sex => '女', age => 20);
 DBMS_OUTPUT.PUT_LINE(var);
END;

5.5 存儲(chǔ)過(guò)程

創(chuàng)建過(guò)程語(yǔ)法:

CREATE [OR REPLACE] PROCEDURE procedure_name
([arg1 [ IN | OUT | IN OUT ]] type1 [DEFAULT value1],
 [arg2 [ IN | OUT | IN OUT ]] type2 [DEFAULT value1]],
 ......
 [argn [ IN | OUT | IN OUT ]] typen [DEFAULT valuen])
    [ AUTHID DEFINER | CURRENT_USER ]
{ IS | AS }
  <聲明部分>
BEGIN
  <執(zhí)行部分>
EXCEPTION
  <可選的異常錯(cuò)誤處理程序>
END procedure_name;

范例

-- 用戶(hù)連接登記記錄窖逗;  
CREATE TABLE logtable (userid VARCHAR2(10), logdate date);
 
CREATE OR REPLACE PROCEDURE logexecution
IS
BEGIN
INSERT INTO logtable (userid, logdate) VALUES (USER, SYSDATE);
END;

-- 刪除指定員工記錄址否; 
CREATE OR REPLACE
PROCEDURE DelEmp
(v_empno IN employees.employee_id%TYPE)
AS
No_result EXCEPTION;
BEGIN
   DELETE FROM employees WHERE employee_id = v_empno;
   IF SQL%NOTFOUND THEN
      RAISE no_result;
   END IF;
   DBMS_OUTPUT.PUT_LINE('編碼為'||v_empno||'的員工已被刪除!');
EXCEPTION
   WHEN no_result THEN
      DBMS_OUTPUT.PUT_LINE('溫馨提示:你需要的數(shù)據(jù)不存在!');
   WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE(SQLCODE||'---'||SQLERRM);
END DelEmp;

-- 插入員工記錄: 
CREATE OR REPLACE
PROCEDURE InsertEmp(
   v_empno     in employees.employee_id%TYPE,
   v_firstname in employees.first_name%TYPE,
   v_lastname  in employees.last_name%TYPE,
   v_deptno    in employees.department_id%TYPE
   )
AS
   empno_remaining EXCEPTION;
   PRAGMA EXCEPTION_INIT(empno_remaining, -1);
   /* -1 是違反唯一約束條件的錯(cuò)誤代碼 */
BEGIN
   INSERT INTO EMPLOYEES(EMPLOYEE_ID, FIRST_NAME, LAST_NAME, HIRE_DATE,DEPARTMENT_ID)
   VALUES(v_empno, v_firstname,v_lastname, sysdate, v_deptno);
   DBMS_OUTPUT.PUT_LINE('溫馨提示:插入數(shù)據(jù)記錄成功!');
EXCEPTION
   WHEN empno_remaining THEN
      DBMS_OUTPUT.PUT_LINE('溫馨提示:違反數(shù)據(jù)完整性約束!');
   WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE(SQLCODE||'---'||SQLERRM);
END InsertEmp;

-- 使用存儲(chǔ)過(guò)程向departments表中插入數(shù)據(jù)。 
CREATE OR REPLACE
PROCEDURE insert_dept
  (v_dept_id IN departments.department_id%TYPE,
   v_dept_name IN departments.department_name%TYPE,
   v_mgr_id IN departments.manager_id%TYPE,
   v_loc_id IN departments.location_id%TYPE)
IS
   ept_null_error EXCEPTION;
   PRAGMA EXCEPTION_INIT(ept_null_error, -1400);
   ept_no_loc_id EXCEPTION;
   PRAGMA EXCEPTION_INIT(ept_no_loc_id, -2291);
BEGIN
   INSERT INTO departments
   (department_id, department_name, manager_id, location_id)
   VALUES
   (v_dept_id, v_dept_name, v_mgr_id, v_loc_id);
   DBMS_OUTPUT.PUT_LINE('插入部門(mén)'||v_dept_id||'成功');
EXCEPTION
   WHEN DUP_VAL_ON_INDEX THEN
      RAISE_APPLICATION_ERROR(-20000, '部門(mén)編碼不能重復(fù)');
   WHEN ept_null_error THEN
      RAISE_APPLICATION_ERROR(-20001, '部門(mén)編碼碎紊、部門(mén)名稱(chēng)不能為空');
   WHEN ept_no_loc_id THEN
      RAISE_APPLICATION_ERROR(-20002, '沒(méi)有該地點(diǎn)');
END insert_dept;
 
/** 調(diào)用實(shí)例1
DECLARE
   ept_20000 EXCEPTION;
   PRAGMA EXCEPTION_INIT(ept_20000, -20000);
   ept_20001 EXCEPTION;
   PRAGMA EXCEPTION_INIT(ept_20001, -20001);
   ept_20002 EXCEPTION;
   PRAGMA EXCEPTION_INIT(ept_20002, -20002);
BEGIN
   insert_dept(300, '部門(mén)300', 100, 2400);
   insert_dept(310, NULL, 100, 2400);
   insert_dept(310, '部門(mén)310', 100, 900);
EXCEPTION
   WHEN ept_20000 THEN
      DBMS_OUTPUT.PUT_LINE('ept_20000部門(mén)編碼不能重復(fù)');
   WHEN ept_20001 THEN
      DBMS_OUTPUT.PUT_LINE('ept_20001部門(mén)編碼佑附、部門(mén)名稱(chēng)不能為空');
   WHEN ept_20002 THEN
      DBMS_OUTPUT.PUT_LINE('ept_20002沒(méi)有該地點(diǎn)');
   WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE('others出現(xiàn)了其他異常錯(cuò)誤');
END;
 
DECLARE
   ept_20000 EXCEPTION;
   PRAGMA EXCEPTION_INIT(ept_20000, -20000);
   ept_20001 EXCEPTION;
   PRAGMA EXCEPTION_INIT(ept_20001, -20001);
   ept_20002 EXCEPTION;
   PRAGMA EXCEPTION_INIT(ept_20002, -20002);
BEGIN
   insert_dept(v_dept_name => '部門(mén)310', v_dept_id => 310,
               v_mgr_id => 100, v_loc_id => 2400);
   insert_dept(320, '部門(mén)320', v_mgr_id => 100, v_loc_id => 900);
EXCEPTION
   WHEN ept_20000 THEN
      DBMS_OUTPUT.PUT_LINE('ept_20000部門(mén)編碼不能重復(fù)');
   WHEN ept_20001 THEN
      DBMS_OUTPUT.PUT_LINE('ept_20001部門(mén)編碼、部門(mén)名稱(chēng)不能為空');
   WHEN ept_20002 THEN
      DBMS_OUTPUT.PUT_LINE('ept_20002沒(méi)有該地點(diǎn)');
   WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE('others出現(xiàn)了其他異常錯(cuò)誤');
END;
**/

5.6 調(diào)用存儲(chǔ)過(guò)程

存儲(chǔ)過(guò)程建立完成后仗考,只要通過(guò)授權(quán)音同,用戶(hù)就可以在SQLPLUS 、ORACLE開(kāi)發(fā)工具或第三方開(kāi)發(fā)工具中來(lái)調(diào)用運(yùn)行秃嗜。對(duì)于參數(shù)的傳遞也有三種:按位置傳遞权均、按名稱(chēng)傳遞和組合傳遞顿膨,傳遞方法與函數(shù)的一樣。ORACLE 使用EXECUTE 語(yǔ)句來(lái)實(shí)現(xiàn)對(duì)存儲(chǔ)過(guò)程的調(diào)用: EXEC[UTE] procedure_name( parameter1, parameter2…);

-- 查詢(xún)指定員工記錄叽赊; 
CREATE OR REPLACE
PROCEDURE QueryEmp
(v_empno IN  employees.employee_id%TYPE,
 v_ename OUT employees.first_name%TYPE,
 v_sa-  OUT employees.salary%TYPE)
AS
BEGIN
       SELECT last_name || last_name, salary INTO v_ename, v_sal
    FROM employees
    WHERE employee_id = v_empno;
       DBMS_OUTPUT.PUT_LINE('溫馨提示:編碼為'||v_empno||'的員工已經(jīng)查到!');
EXCEPTION
       WHEN NO_DATA_FOUND THEN
      DBMS_OUTPUT.PUT_LINE('溫馨提示:你需要的數(shù)據(jù)不存在!');
      WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE(SQLCODE||'---'||SQLERRM);
END QueryEmp;
--調(diào)用
 DECLARE
    v1 employees.first_name%TYPE;
    v2 employees.salary%TYPE;
 BEGIN
   QueryEmp(100, v1, v2);
   DBMS_OUTPUT.PUT_LINE('姓名:'||v1);
   DBMS_OUTPUT.PUT_LINE('工資:'||v2);
   QueryEmp(103, v1, v2);
   DBMS_OUTPUT.PUT_LINE('姓名:'||v1);
   DBMS_OUTPUT.PUT_LINE('工資:'||v2);
   QueryEmp(104, v1, v2);
   DBMS_OUTPUT.PUT_LINE('姓名:'||v1);
   DBMS_OUTPUT.PUT_LINE('工資:'||v2);
END;


-- 計(jì)算指定部門(mén)的工資總和恋沃,并統(tǒng)計(jì)其中的職工數(shù)量。 
CREATE OR REPLACE
PROCEDURE proc_demo
(
  dept_no NUMBER DEFAULT 10,
    sal_sum OUT NUMBER,
    emp_count OUT NUMBER
  )
IS
BEGIN
    SELECT SUM(salary), COUNT(*) INTO sal_sum, emp_count
  FROM employees WHERE department_id = dept_no;
EXCEPTION
   WHEN NO_DATA_FOUND THEN
      DBMS_OUTPUT.PUT_LINE('溫馨提示:你需要的數(shù)據(jù)不存在!');
   WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE(SQLCODE||'---'||SQLERRM);
END proc_demo;
 
DECLARE
V_num NUMBER;
V_sum NUMBER(8, 2);
BEGIN
  Proc_demo(30, v_sum, v_num);
DBMS_OUTPUT.PUT_LINE('溫馨提示:30號(hào)部門(mén)工資總和:'||v_sum||',人數(shù):'||v_num);
  Proc_demo(sal_sum => v_sum, emp_count => v_num);
DBMS_OUTPUT.PUT_LINE('溫馨提示:10號(hào)部門(mén)工資總和:'||v_sum||',人數(shù):'||v_num);
END;

在PL/SQL 程序中還可以在塊內(nèi)建立本地函數(shù)和過(guò)程必指,這些函數(shù)和過(guò)程不存儲(chǔ)在數(shù)據(jù)庫(kù)中囊咏,但可以在創(chuàng)建它們的PL/SQL 程序中被重復(fù)調(diào)用。本地函數(shù)和過(guò)程在PL/SQL 塊的聲明部分定義塔橡,它們的語(yǔ)法格式與存儲(chǔ)函數(shù)和過(guò)程相同梅割,但不能使用CREATE OR REPLACE 關(guān)鍵字。

-- 建立本地過(guò)程葛家,用于計(jì)算指定部門(mén)的工資總和户辞,并統(tǒng)計(jì)其中的職工數(shù)量; 
DECLARE
V_num NUMBER;
V_sum NUMBER(8, 2);
PROCEDURE proc_demo
  (
    Dept_no NUMBER DEFAULT 10,
    Sal_sum OUT NUMBER,
    Emp_count OUT NUMBER
  )
IS
BEGIN
    SELECT SUM(salary), COUNT(*) INTO sal_sum, emp_count
    FROM employees WHERE department_id=dept_no;
EXCEPTION
   WHEN NO_DATA_FOUND THEN
      DBMS_OUTPUT.PUT_LINE('你需要的數(shù)據(jù)不存在!');
   WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE(SQLCODE||'---'||SQLERRM);
END proc_demo;
--調(diào)用方法:
BEGIN
    Proc_demo(30, v_sum, v_num);
DBMS_OUTPUT.PUT_LINE('30號(hào)部門(mén)工資總和:'||v_sum||'癞谒,人數(shù):'||v_num);
    Proc_demo(sal_sum => v_sum, emp_count => v_num);
DBMS_OUTPUT.PUT_LINE('10號(hào)部門(mén)工資總和:'||v_sum||'底燎,人數(shù):'||v_num);
END;

5.7 AUTHID

過(guò)程中的AUTHID 指令可以告訴ORACLE ,這個(gè)過(guò)程使用誰(shuí)的權(quán)限運(yùn)行.默任情況下弹砚,存儲(chǔ)過(guò)程會(huì)作為調(diào)用者的過(guò)程運(yùn)行书蚪,但是具有設(shè)計(jì)者的特權(quán).這稱(chēng)為設(shè)計(jì)者權(quán)利運(yùn)行.

-- 建立過(guò)程,使用AUTOID DEFINER迅栅; 
Connect HR/qaz
DROP TABLE logtable;
CREATE table logtable (userid VARCHAR2(10), logdate date);
 
CREATE OR REPLACE PROCEDURE logexecution
    AUTHID DEFINER
IS
BEGIN
   INSERT INTO logtable (userid, logdate) VALUES (USER, SYSDATE);
END;
 
GRANT EXECUTE ON logexecution TO PUBLIC;
 
CONNECT / AS SYSDBA
GRANT CONNECT TO testuser1 IDENTIFIED BY userpwd1;
 
CONNECT testuser1/userpwd1
INSERT INTO HR.LOGTABLE VALUES (USER, SYSDATE);
EXECUTE HR.logexecution
 
CONNECT HR/qaz
SELECT * FROM HR.logtable;
-- 建立過(guò)程,使用AUTOID CURRENT_USER晴玖; 
CONNECT HR/qaz
 
CREATE OR REPLACE PROCEDURE logexecution
  AUTHID CURRENT_USER
IS
BEGIN
   INSERT INTO logtable (userid, logdate) VALUES (USER, SYSDATE);
END;
 
GRANT EXECUTE ON logexecution TO PUBLIC;
 
CONNECT testuser1/userpwd1
INSERT INTO HR.LOGTABLE VALUES (USER, SYSDATE);
EXECUTE HR.logexecution

5.8 PRAGMA AUTONOMOUS_TRANSACTION

ORACLE可以支持事務(wù)處理中的事務(wù)處理的概念读存。這種子事務(wù)處理可以完成它自己的工作,獨(dú)立于父事務(wù)處理進(jìn)行提交或者回滾.通過(guò)使用這種方法呕屎,開(kāi)發(fā)者就能夠這樣的過(guò)程让簿,無(wú)論父事務(wù)處理是提交還是回滾,它都可以成功執(zhí)行秀睛。

-- 建立過(guò)程尔当,使用自動(dòng)事務(wù)處理進(jìn)行日志記錄;
DROP TABLE logtable;
 
CREATE TABLE logtable(
  Username varchar2(20),
  Dassate_time date,
  Mege varchar2(60)
);
 
CREATE TABLE temp_table( N number );
 
CREATE OR REPLACE PROCEDURE log_message(p_message varchar2)
  AS
  PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
  INSERT INTO logtable VALUES ( user, sysdate, p_message );
  COMMIT;
END log_message;
 
BEGIN
  Log_message (‘About to insert into temp_table‘);
  INSERT INTO temp_table VALUES (1);
  Log_message (‘Rollback to insert into temp_table‘);
  ROLLBACK;
END;
 
SELECT * FROM logtable;
SELECT * FROM temp_table;

-- 建立過(guò)程蹂安,沒(méi)有使用自動(dòng)事務(wù)處理進(jìn)行日志記錄椭迎; 
CREATE OR REPLACE PROCEDURE log_message(p_message varchar2)
  AS
BEGIN
  INSERT INTO logtable VALUES ( user, sysdate, p_message );
  COMMIT;
END log_message;
 
BEGIN
  Log_message ('About to insert into temp_table');
  INSERT INTO temp_table VALUES (1);
  Log_message ('Rollback to insert into temp_table');
  ROLLBACK;
END;
 
SELECT * FROM logtable;
SELECT * FROM temp_table;

5.9 開(kāi)發(fā)存儲(chǔ)過(guò)程步驟

開(kāi)發(fā)存儲(chǔ)過(guò)程、函數(shù)田盈、包及觸發(fā)器的步驟如下:
1 . 使用文字編輯處理軟件編輯存儲(chǔ)過(guò)程源碼
2 . 在SQLPLUS或用調(diào)試工具將存儲(chǔ)過(guò)程程序進(jìn)行解釋 START c:\stat.sql
3 . 調(diào)試源碼直到正確:a)使用SHOW ERROR提示錯(cuò)誤位置畜号;b)使用 user_errors數(shù)據(jù)字典查看各存儲(chǔ)過(guò)程的錯(cuò)誤位置
4 . 授權(quán)執(zhí)行權(quán)給相關(guān)的用戶(hù)或角色:如果存儲(chǔ)過(guò)程沒(méi)有授權(quán),只有建立者才可以運(yùn)行允瞧〖蛉恚可以用GRANT命令來(lái)進(jìn)行存儲(chǔ)過(guò)程的運(yùn)行授權(quán)蛮拔。

-- GRANT語(yǔ)法 
GRANT system_privilege | role
TO user | role | PUBLIC [WITH ADMIN OPTION]
 
GRANT object_privilege | ALL ON schema.object
TO user | role | PUBLIC [WITH GRANT OPTION]
 
-- 例子
CREATE OR REPLACE PUBLIC SYNONYM dbms_job FOR dbms_job

GRANT EXECUTE ON dbms_job TO PUBLIC WITH GRANT OPTION

5.10 刪除過(guò)程和函數(shù)

1.刪除過(guò)程 DROP PROCEDURE [user.]Procudure_name;
2 . 刪除函數(shù) DROP FUNCTION [user.]Function_name;

5.11 過(guò)程與函數(shù)的比較

**過(guò)程與函數(shù)具有如下優(yōu)點(diǎn): **

  • 共同使用的代碼可以只需要被編寫(xiě)和測(cè)試一次,而被需要該代碼的任何應(yīng)用程序(如:.NET痹升、C++建炫、JAVA、VB程序疼蛾,也可以是DLL庫(kù))調(diào)用肛跌。
  • 這種集中編寫(xiě)、集中維護(hù)更新据过、大家共享(或重用)的方法惋砂,簡(jiǎn)化了應(yīng)用程序的開(kāi)發(fā)和維護(hù),提高了效率與性能绳锅。
  • 這種模塊化的方法西饵,使得可以將一個(gè)復(fù)雜的問(wèn)題、大的程序逐步簡(jiǎn)化成幾個(gè)簡(jiǎn)單的鳞芙、小的程序部分眷柔,進(jìn)行分別編寫(xiě)、調(diào)試原朝。因此使程序的結(jié)構(gòu)清晰驯嘱、簡(jiǎn)單,也容易實(shí)現(xiàn)
  • 可以在各個(gè)開(kāi)發(fā)者之間提供處理數(shù)據(jù)喳坠、控制流程鞠评、提示信息等方面的一致性。
  • 節(jié)省內(nèi)存空間壕鹉。它們以一種壓縮的形式被存儲(chǔ)在外存中剃幌,當(dāng)被調(diào)用時(shí)才被放入內(nèi)存進(jìn)行處理。并且晾浴,如果多個(gè)用戶(hù)要執(zhí)行相同的過(guò)程或函數(shù)時(shí)负乡,就只需要在內(nèi)存中加載一個(gè)該過(guò)程或函數(shù)。
  • 提高數(shù)據(jù)的安全性與完整性脊凰。通過(guò)把一些對(duì)數(shù)據(jù)的操作放到過(guò)程或函數(shù)中抖棘,就可以通過(guò)是否授予用戶(hù)有執(zhí)行該過(guò)程或的權(quán)限,來(lái)限制某些用戶(hù)對(duì)數(shù)據(jù)進(jìn)行這些操作狸涌。

過(guò)程與函數(shù)的相同功能有:

  • 都使用IN模式的參數(shù)傳入數(shù)據(jù)切省、OUT模式的參數(shù)返回?cái)?shù)據(jù)。
  • 輸入?yún)?shù)都可以接受默認(rèn)值杈抢,都可以傳值或傳引導(dǎo)数尿。
  • 調(diào)用時(shí)的實(shí)際參數(shù)都可以使用位置表示法、名稱(chēng)表示法或組合方法惶楼。
  • 都有聲明部分右蹦、執(zhí)行部分和異常處理部分诊杆。
  • 其管理過(guò)程都有創(chuàng)建、編譯何陆、授權(quán)晨汹、刪除、顯示依賴(lài)關(guān)系等贷盲。

使用過(guò)程與函數(shù)的原則:

  • 如果需要返回多個(gè)值和不返回值淘这,就使用過(guò)程;如果只需要返回一個(gè)值巩剖,就使用函數(shù)铝穷。
  • 過(guò)程一般用于執(zhí)行一個(gè)指定的動(dòng)作,函數(shù)一般用于計(jì)算和返回一個(gè)值佳魔。
  • 可以SQL語(yǔ)句內(nèi)部(如表達(dá)式)調(diào)用函數(shù)來(lái)完成復(fù)雜的計(jì)算問(wèn)題曙聂,但不能調(diào)用過(guò)程。所以這是函數(shù)的特色鞠鲜。

6.1 觸發(fā)器類(lèi)型

觸發(fā)器在數(shù)據(jù)庫(kù)里以獨(dú)立的對(duì)象存儲(chǔ)宁脊,它與存儲(chǔ)過(guò)程和函數(shù)不同的是,存儲(chǔ)過(guò)程與函數(shù)需要用戶(hù)顯示調(diào)用才執(zhí)行贤姆,而觸發(fā)器是由一個(gè)事件來(lái)啟動(dòng)運(yùn)行榆苞。即觸發(fā)器是當(dāng)某個(gè)事件發(fā)生時(shí)自動(dòng)地隱式運(yùn)行。并且霞捡,觸發(fā)器不能接收參數(shù)坐漏。所以運(yùn)行觸發(fā)器就叫觸發(fā)或點(diǎn)火(firing)。ORACLE事件指的是對(duì)數(shù)據(jù)庫(kù)的表進(jìn)行的INSERT碧信、UPDATE及DELETE操作或?qū)σ晥D進(jìn)行類(lèi)似的操作仙畦。ORACLE將觸發(fā)器的功能擴(kuò)展到了觸發(fā)ORACLE,如數(shù)據(jù)庫(kù)的啟動(dòng)與關(guān)閉等音婶。所以觸發(fā)器常用來(lái)完成由數(shù)據(jù)庫(kù)的完整性約束難以完成的復(fù)雜業(yè)務(wù)規(guī)則的約束,或用來(lái)監(jiān)視對(duì)數(shù)據(jù)庫(kù)的各種操作莱坎,實(shí)現(xiàn)審計(jì)的功能衣式。

  1. DML觸發(fā)器:在DML語(yǔ)句進(jìn)行觸發(fā),可以在DML操作前或操作后進(jìn)行觸發(fā)檐什,并且可以對(duì)每個(gè)行或語(yǔ)句操作上進(jìn)行觸發(fā)碴卧。
  2. 替代觸發(fā)器:不能直接對(duì)由兩個(gè)以上的表建立的視圖進(jìn)行操作。所以給出了替代觸發(fā)器乃正。它就是ORACLE專(zhuān)門(mén)為進(jìn)行視圖操作的一種處理方法住册。
  3. 系統(tǒng)觸發(fā)器:可以在ORACLE數(shù)據(jù)庫(kù)系統(tǒng)的事件中進(jìn)行觸發(fā),如ORACLE系統(tǒng)的啟動(dòng)與關(guān)閉等瓮具。

6.2 觸發(fā)器組成

觸發(fā)事件:引起觸發(fā)器被觸發(fā)的事件荧飞。 例如:DML語(yǔ)句(INSERT, UPDATE, DELETE語(yǔ)句對(duì)表或視圖執(zhí)行數(shù)據(jù)處理操作)凡人、DDL語(yǔ)句(如CREATE、ALTER叹阔、DROP語(yǔ)句 在數(shù)據(jù)庫(kù)中創(chuàng)建挠轴、修改、刪除模式對(duì)象)耳幢、數(shù)據(jù)庫(kù)系統(tǒng)事件(如系統(tǒng)啟動(dòng)或退出岸晦、異常錯(cuò)誤)、用戶(hù)事件(如登錄或退出數(shù)據(jù)庫(kù))睛藻。
觸發(fā)時(shí)間:即該TRIGGER 是在觸發(fā)事件發(fā)生之前(BEFORE)還是之后(AFTER)觸發(fā)启上,也就是觸發(fā)事件和該TRIGGER 的操作順序。
觸發(fā)操作:即該TRIGGER 被觸發(fā)之后的目的和意圖店印,正是觸發(fā)器本身要做的事情冈在。 例如:PL/SQL 塊。
** 觸發(fā)對(duì)象:包括表吱窝、視圖讥邻、模式、數(shù)據(jù)庫(kù)院峡。只有在這些對(duì)象上發(fā)生了符合觸發(fā)條件的觸發(fā)事件兴使,才會(huì)執(zhí)行觸發(fā)操作。
觸發(fā)條件:由WHEN子句指定一個(gè)邏輯表達(dá)式照激。只有當(dāng)該表達(dá)式的值為T(mén)RUE時(shí)发魄,遇到觸發(fā)事件才會(huì)自動(dòng)執(zhí)行觸發(fā)器,使其執(zhí)行觸發(fā)操作俩垃。
** 觸發(fā)頻率
:說(shuō)明觸發(fā)器內(nèi)定義的動(dòng)作被執(zhí)行的次數(shù)励幼。即語(yǔ)句級(jí)(STATEMENT)觸發(fā)器和行級(jí)(ROW)觸發(fā)器。

語(yǔ)句級(jí)(STATEMENT)觸發(fā)器:是指當(dāng)某觸發(fā)事件發(fā)生時(shí)口柳,該觸發(fā)器只執(zhí)行一次苹粟;
行級(jí)(ROW)觸發(fā)器:是指當(dāng)某觸發(fā)事件發(fā)生時(shí),對(duì)受到該操作影響的每一行數(shù)據(jù)跃闹,觸發(fā)器都單獨(dú)執(zhí)行一次嵌削。

6.3 觸發(fā)器注意點(diǎn)

  • 觸發(fā)器不接受參數(shù)
  • 一個(gè)表上最多可有12個(gè)觸發(fā)器,但同一時(shí)間望艺、同一事件苛秕、同一類(lèi)型的觸發(fā)器只能有一個(gè)。并各觸發(fā)器之間不能有矛盾找默。
  • 在一個(gè)表上的觸發(fā)器越多艇劫,對(duì)在該表上的DML操作的性能影響就越大。
  • 觸發(fā)器最大為32KB惩激。若確實(shí)需要店煞,可以先建立過(guò)程蟹演,然后在觸發(fā)器中用CALL語(yǔ)句進(jìn)行調(diào)用。
  • 在觸發(fā)器的執(zhí)行部分只能用DML語(yǔ)句(SELECT浅缸、INSERT轨帜、UPDATE、DELETE)衩椒,不能使用DDL語(yǔ)句(CREATE蚌父、ALTER、DROP)毛萌。
  • 觸發(fā)器中不能包含事務(wù)控制語(yǔ)句(COMMIT苟弛,ROLLBACK,SAVEPOINT)阁将。因?yàn)橛|發(fā)器是觸發(fā)語(yǔ)句的一部分膏秫,觸發(fā)語(yǔ)句被提交、回退時(shí)做盅,觸發(fā)器也被提交缤削、回退了。
  • 在觸發(fā)器主體中調(diào)用的任何過(guò)程吹榴、函數(shù)亭敢,都不能使用事務(wù)控制語(yǔ)句。
  • 在觸發(fā)器主體中不能申明任何Longblob變量图筹。新值new和舊值old也不能向表中的任何longblob列帅刀。
  • 不同類(lèi)型的觸發(fā)器(如DML觸發(fā)器INSTEAD OF觸發(fā)器远剩、系統(tǒng)觸發(fā)器)的語(yǔ)法格式和作用有較大區(qū)別扣溺。

6.4 創(chuàng)建觸發(fā)器

創(chuàng)建觸發(fā)器的一般語(yǔ)法是:

CREATE [OR REPLACE] TRIGGER trigger_name
{BEFORE | AFTER }
{INSERT | DELETE | UPDATE [OF column [, column …]]}
[OR {INSERT | DELETE | UPDATE [OF column [, column …]]}...]
ON [schema.]table_name | [schema.]view_name
[REFERENCING {OLD [AS] old | NEW [AS] new| PARENT as parent}]
[FOR EACH ROW ]
[WHEN condition]
PL/SQL_BLOCK | CALL procedure_name;
  • BEFOREAFTER 指出觸發(fā)器的觸發(fā)時(shí)序分別為前觸發(fā)和后觸發(fā)方式,前觸發(fā)是在執(zhí)行觸發(fā)事件之前觸發(fā)當(dāng)前所創(chuàng)建的觸發(fā)器瓜晤,后觸發(fā)是在執(zhí)行觸發(fā)事件之后觸發(fā)當(dāng)前所創(chuàng)建的觸發(fā)器锥余。
  • FOR EACH ROW選項(xiàng)說(shuō)明觸發(fā)器為行觸發(fā)器。
  • 行觸發(fā)器和語(yǔ)句觸發(fā)器的區(qū)別表現(xiàn)在:行觸發(fā)器要求當(dāng)一個(gè)DML語(yǔ)句操走影響數(shù)據(jù)庫(kù)中的多行數(shù)據(jù)時(shí)痢掠,對(duì)于其中的每個(gè)數(shù)據(jù)行哈恰,只要它們符合觸發(fā)約束條件,均激活一次觸發(fā)器志群;而語(yǔ)句觸發(fā)器將整個(gè)語(yǔ)句操作作為觸發(fā)事件,當(dāng)它符合約束條件時(shí)蛔钙,激活一次觸發(fā)器锌云。
  • 當(dāng)省略FOR EACH ROW選項(xiàng)時(shí),BEFOREAFTER 觸發(fā)器為語(yǔ)句觸發(fā)器吁脱,而 INSTEAD OF 觸發(fā)器則只能為行觸發(fā)器桑涎。
  • REFERENCING 子句說(shuō)明相關(guān)名稱(chēng)彬向,在行觸發(fā)器的PL/SQL塊和 WHEN 子句中可以使用相關(guān)名稱(chēng)參照當(dāng)前的新、舊列值攻冷,默認(rèn)的相關(guān)名稱(chēng)分別為 OLDNEW 。觸發(fā)器的PL/SQL塊中應(yīng)用相關(guān)名稱(chēng)時(shí),必須在它們之前加冒號(hào):锁摔,但在 WHEN 子句中則不能加冒號(hào):损离。
  • WHEN 子句說(shuō)明觸發(fā)約束條件。 Condition 為一個(gè)邏輯表達(dá)時(shí)禁谦,其中必須包含相關(guān)名稱(chēng)胁黑,而不能包含查詢(xún)語(yǔ)句,也不能調(diào)用PL/SQL 函數(shù)州泊。WHEN 子句指定的觸發(fā)約束條件只能用在 BEFOREAFTER 行觸發(fā)器中丧蘸,不能用在 INSTEAD OF 行觸發(fā)器和其它類(lèi)型的觸發(fā)器中。
  • 當(dāng)一個(gè)基表被修改( `INSERT` ,  `UPDATE`,  `DELETE` )時(shí)要執(zhí)行的存儲(chǔ)過(guò)程遥皂,執(zhí)行時(shí)根據(jù)其所依附的基表改動(dòng)而自動(dòng)觸發(fā)力喷,因此與應(yīng)用程序無(wú)關(guān),用數(shù)據(jù)庫(kù)觸發(fā)器可以保證數(shù)據(jù)的一致性和完整性演训。 
    

每張表最多可建立12 種類(lèi)型的觸發(fā)器弟孟,它們是:

BEFORE INSERT
BEFORE INSERT FOR EACH ROW
AFTER INSERT
AFTER INSERT FOR EACH ROW
 
BEFORE UPDATE
BEFORE UPDATE FOR EACH ROW
AFTER UPDATE
AFTER UPDATE FOR EACH ROW
 
BEFORE DELETE
BEFORE DELETE FOR EACH ROW
AFTER DELETE
AFTER DELETE FOR EACH ROW 

6.5 觸發(fā)器觸發(fā)次序

  1. 執(zhí)行 BEFORE語(yǔ)句級(jí)觸發(fā)器;
  2. 對(duì)與受語(yǔ)句影響的每一行
    2.1 執(zhí)行 BEFORE行級(jí)觸發(fā)器
    2.2 執(zhí)行 DML語(yǔ)句
    2.3 執(zhí)行 AFTER行級(jí)觸發(fā)器
  3. 執(zhí)行 AFTER語(yǔ)句級(jí)觸發(fā)器

6.6 創(chuàng)建DML觸發(fā)器

觸發(fā)器名與過(guò)程名和包的名字不一樣,它是單獨(dú)的名字空間仇祭,因而觸發(fā)器名可以和表或過(guò)程有相同的名字披蕉,但在一個(gè)模式中觸發(fā)器名不能相同。

DML觸發(fā)器的限制

  • CREATE TRIGGER語(yǔ)句文本的字符長(zhǎng)度不能超過(guò)32KB乌奇;
  • 觸發(fā)器體內(nèi)的 SELECT 語(yǔ)句只能為 SELECT … INTO …結(jié)構(gòu)没讲,或者為定義游標(biāo)所使用的 SELECT 語(yǔ)句。
  • 觸發(fā)器中不能使用數(shù)據(jù)庫(kù)事務(wù)控制語(yǔ)句 COMMIT; ROLLBACK, SVAEPOINT 語(yǔ)句礁苗;
  • 由觸發(fā)器所調(diào)用的過(guò)程或函數(shù)也不能使用數(shù)據(jù)庫(kù)事務(wù)控制語(yǔ)句爬凑;
  • 觸發(fā)器中不能使用 LONG, LONG RAW 類(lèi)型;
  • 觸發(fā)器內(nèi)可以參照 LOB 類(lèi)型列的列值试伙,但不能通過(guò) : NEW 修改 LOB 列中的數(shù)據(jù)嘁信;

DML觸發(fā)器基本要點(diǎn)

  • 觸發(fā)時(shí)機(jī):指定觸發(fā)器的觸發(fā)時(shí)間。如果指定為 BEFORE疏叨,則表示在執(zhí)行DML操作之前觸發(fā)潘靖,以便防止某些錯(cuò)誤操作發(fā)生或?qū)崿F(xiàn)某些業(yè)務(wù)規(guī)則;如果指定為AFTER蚤蔓,則表示在執(zhí)行DML操作之后觸發(fā)卦溢,以便記錄該操作或做某些事后處理。
  • 觸發(fā)事件:引起觸發(fā)器被觸發(fā)的事件,即DML操作(INSERT单寂、UPDATE贬芥、DELETE)。既可以是單個(gè)觸發(fā)事件宣决,也可以是多個(gè)觸發(fā)事件的組合(只能使用OR邏輯組合蘸劈,不能使用AND邏輯組合)。
  • 條件謂詞:當(dāng)在觸發(fā)器中包含多個(gè)觸發(fā)事件(INSERT尊沸、UPDATE威沫、DELETE)的組合時(shí),為了分別針對(duì)不同的事件進(jìn)行不同的處理椒丧,需要使用ORACLE提供的如下條件謂詞壹甥。
    1) INSERTING :當(dāng)觸發(fā)事件是 INSERT 時(shí),取值為T(mén)RUE壶熏,否則為FALSE
    2) UPDATING [(column_1,column_2,…,column_x)]:當(dāng)觸發(fā)事件是 UPDATE 時(shí)句柠,如果修改了 column_x 列,則取值為 TRUE棒假,否則為 FALSE 溯职。其中 column_x 是可選的
    3) DELETING :當(dāng)觸發(fā)事件是 DELETE 時(shí),則取值為 TRUE 帽哑,否則為 FALSE
    解發(fā)對(duì)象:指定觸發(fā)器是創(chuàng)建在哪個(gè)表谜酒、視圖上。
  • 觸發(fā)類(lèi)型:是語(yǔ)句級(jí)還是行級(jí)觸發(fā)器妻枕。
  • 觸發(fā)條件:由WHEN子句指定一個(gè)邏輯表達(dá)式僻族,只允許在行級(jí)觸發(fā)器上指定觸發(fā)條件,指定UPDATING后面的列的列表屡谐。

當(dāng)觸發(fā)器被觸發(fā)時(shí)述么,要使用被插入、更新或刪除的記錄中的列值愕掏,有時(shí)要使用操作前度秘、操作后列的值
:NEW 修飾符訪問(wèn)操作完成后列的值,:OLD 修飾符訪問(wèn)操作完成前列的值

特性 INSERT UPDATE DELETE
OLD NULL 實(shí)際值 實(shí)際值
NEW 實(shí)際值 實(shí)際值 NULL
-- 建立一個(gè)觸發(fā)器, 當(dāng)職工表 emp 表被刪除一條記錄時(shí)饵撑,把被刪除記錄寫(xiě)到職工表刪除日志表中去
CREATE TABLE emp_his AS SELECT * FROM EMP WHERE 1=2;
CREATE OR REPLACE TRIGGER tr_del_emp
   BEFORE DELETE --指定觸發(fā)時(shí)機(jī)為刪除操作前觸發(fā)
   ON scott.emp
   FOR EACH ROW   --說(shuō)明創(chuàng)建的是行級(jí)觸發(fā)器
BEGIN
   --將修改前數(shù)據(jù)插入到日志記錄表 del_emp ,以供監(jiān)督使用剑梳。
   INSERT INTO emp_his(deptno , empno, ename , job ,mgr , sal , comm , hiredate )
       VALUES( :old.deptno, :old.empno, :old.ename , :old.job,:old.mgr, :old.sal, :old.comm, :old.hiredate );
END;
DELETE emp WHERE empno=7788;
DROP TABLE emp_his;
DROP TRIGGER del_emp;

-- 限制對(duì)Departments表修改(包括INSERT,DELETE,UPDATE)的時(shí)間范圍,
-- 即不允許在非工作時(shí)間修改departments表滑潘。 
CREATE OR REPLACE TRIGGER tr_dept_time
BEFORE INSERT OR DELETE OR UPDATE
ON departments
BEGIN
 IF (TO_CHAR(sysdate,'DAY') IN ('星期六', '星期日')) OR (TO_CHAR(sysdate, 'HH24:MI') 
     NOT BETWEEN '08:30' AND '18:00') THEN
     RAISE_APPLICATION_ERROR(-20001, '不是上班時(shí)間垢乙,不能修改departments表');
 END IF;
END;

-- 限定只對(duì)部門(mén)號(hào)為80的記錄進(jìn)行行觸發(fā)器操作。 
CREATE OR REPLACE TRIGGER tr_emp_sal_comm
BEFORE UPDATE OF salary, commission_pct
       OR DELETE
ON HR.employees
FOR EACH ROW
WHEN (old.department_id = 80)
BEGIN
 CASE
     WHEN UPDATING ('salary') THEN
        IF :NEW.salary < :old.salary THEN
 
           RAISE_APPLICATION_ERROR(-20001, '部門(mén)80的人員的工資不能降');
        END IF;
     WHEN UPDATING ('commission_pct') THEN
 
        IF :NEW.commission_pct < :old.commission_pct THEN
           RAISE_APPLICATION_ERROR(-20002, '部門(mén)80的人員的獎(jiǎng)金不能降');
        END IF;
     WHEN DELETING THEN
          RAISE_APPLICATION_ERROR(-20003, '不能刪除部門(mén)80的人員記錄');
     END CASE;
END;
 

-- 利用行觸發(fā)器實(shí)現(xiàn)級(jí)聯(lián)更新语卤。在修改了主表regions中的region_id之后(AFTER)追逮,
-- 級(jí)聯(lián)的蓖租、自動(dòng)的更新子表countries表中原來(lái)在該地區(qū)的國(guó)家的region_id。 
CREATE OR REPLACE TRIGGER tr_reg_cou
AFTER update OF region_id
ON regions
FOR EACH ROW
BEGIN
 DBMS_OUTPUT.PUT_LINE('舊的region_id值是'||:old.region_id
                  ||'羊壹、新的region_id值是'||:new.region_id);
 UPDATE countries SET region_id = :new.region_id
 WHERE region_id = :old.region_id;
END;

-- 在觸發(fā)器中調(diào)用過(guò)程。 
CREATE OR REPLACE PROCEDURE add_job_history
 ( p_emp_id          job_history.employee_id%type
   , p_start_date      job_history.start_date%type
  , p_end_date        job_history.end_date%type
   , p_job_id          job_history.job_id%type
   , p_department_id   job_history.department_id%type
   )
IS
BEGIN
 INSERT INTO job_history (employee_id, start_date, end_date,
                           job_id, department_id)
  VALUES(p_emp_id, p_start_date, p_end_date, p_job_id, p_department_id);
END add_job_history;
 
--創(chuàng)建觸發(fā)器調(diào)用存儲(chǔ)過(guò)程...
CREATE OR REPLACE TRIGGER update_job_history
 AFTER UPDATE OF job_id, department_id ON employees
 FOR EACH ROW
BEGIN
 add_job_history(:old.employee_id, :old.hire_date, sysdate,
                  :old.job_id, :old.department_id);
END;

6.7 創(chuàng)建替代(INSTEAD OF)觸發(fā)器

創(chuàng)建替代觸發(fā)器的一般語(yǔ)法是:

CREATE [OR REPLACE] TRIGGER trigger_name
INSTEAD OF
{INSERT | DELETE | UPDATE [OF column [, column …]]}
[OR {INSERT | DELETE | UPDATE [OF column [, column …]]}...]
ON [schema.] view_name --只能定義在視圖上
[REFERENCING {OLD [AS] old | NEW [AS] new| PARENT as parent}]
[FOR EACH ROW ] --因?yàn)镮NSTEAD OF觸發(fā)器只能在行級(jí)上觸發(fā),所以沒(méi)有必要指定
[WHEN condition]
PL/SQL_block | CALL procedure_name;
  • INSTEAD OF 選項(xiàng)使ORACLE激活觸發(fā)器齐婴,而不執(zhí)行觸發(fā)事件油猫。只能對(duì)視圖和對(duì)象視圖建立INSTEAD OF觸發(fā)器,而不能對(duì)表柠偶、模式和數(shù)據(jù)庫(kù)建立INSTEAD OF 觸發(fā)器情妖。
  • FOR EACH ROW選項(xiàng)說(shuō)明觸發(fā)器為行觸發(fā)器。行觸發(fā)器和語(yǔ)句觸發(fā)器的區(qū)別表現(xiàn)在:行觸發(fā)器要求當(dāng)一個(gè)DML語(yǔ)句操走影響數(shù)據(jù)庫(kù)中的多行數(shù)據(jù)時(shí)诱担,對(duì)于其中的每個(gè)數(shù)據(jù)行毡证,只要它們符合觸發(fā)約束條件,均激活一次觸發(fā)器蔫仙;而語(yǔ)句觸發(fā)器將整個(gè)語(yǔ)句操作作為觸發(fā)事件料睛,當(dāng)它符合約束條件時(shí),激活一次觸發(fā)器摇邦。當(dāng)省略 FOR EACH ROW 選項(xiàng)時(shí)恤煞,BEFORE 和AFTER 觸發(fā)器為語(yǔ)句觸發(fā)器,而INSTEAD OF 觸發(fā)器則為行觸發(fā)器施籍。
  • REFERENCING 子句說(shuō)明相關(guān)名稱(chēng)居扒,在行觸發(fā)器的PL/SQL塊和 WHEN 子句中可以使用相關(guān)名稱(chēng)參照當(dāng)前的新、舊列值丑慎,默認(rèn)的相關(guān)名稱(chēng)分別為 OLDNEW 喜喂。觸發(fā)器的PL/SQL塊中應(yīng)用相關(guān)名稱(chēng)時(shí),必須在它們之前加冒號(hào):竿裂,但在WHEN子句中則不能加冒號(hào):
  • WHEN 子句說(shuō)明觸發(fā)約束條件玉吁。 Condition 為一個(gè)邏輯表達(dá)時(shí),其中必須包含相關(guān)名稱(chēng)铛绰,而不能包含查詢(xún)語(yǔ)句诈茧,也不能調(diào)用PL/SQL 函數(shù)。 WHEN 子句指定的觸發(fā)約束條件只能用在 BEFOREAFTER 行觸發(fā)器中捂掰,不能用在INSTEAD OF 行觸發(fā)器和其它類(lèi)型的觸發(fā)器中敢会。
  • INSTEAD_OF 用于對(duì)視圖的DML觸發(fā),由于視圖有可能是由多個(gè)表進(jìn)行聯(lián)結(jié) join 而成这嚣,因而并非是所有的聯(lián)結(jié)都是可更新的鸥昏。但可以按照所需的方式執(zhí)行更新,例如下面情況:
CREATE OR REPLACE VIEW emp_view AS
SELECT deptno, count(*) total_employeer, sum(sal) total_salary
FROM emp GROUP BY deptno;

-- 在此視圖中直接刪除是非法: 
SQL>DELETE FROM emp_view WHERE deptno=10;
DELETE FROM emp_view WHERE deptno=10

-- ERROR 位于第 1 行:
-- ORA-01732: 此視圖的數(shù)據(jù)操縱操作非法 
-- 但是我們可以創(chuàng)建INSTEAD_OF觸發(fā)器來(lái)為 DELETE 操作執(zhí)行所需的處理姐帚,即刪除EMP表中所有基準(zhǔn)行:  

CREATE OR REPLACE TRIGGER emp_view_delete
   INSTEAD OF DELETE ON emp_view FOR EACH ROW
BEGIN
   DELETE FROM emp WHERE deptno= :old.deptno;
END emp_view_delete;
 
DELETE FROM emp_view WHERE deptno=10;
DROP TRIGGER emp_view_delete;
DROP VIEW emp_view;

創(chuàng)建復(fù)雜視圖吏垮,針對(duì)INSERT操作創(chuàng)建INSTEAD OF觸發(fā)器,向復(fù)雜視圖插入數(shù)據(jù)。

-- 創(chuàng)建視圖:
CREATE OR REPLACE FORCE VIEW "HR"."V_REG_COU" ("R_ID", "R_NAME", "C_ID", "C_NAME")
AS
 SELECT r.region_id,
    r.region_name,
    c.country_id,
    c.country_name
 FROM regions r,
    countries c
 WHERE r.region_id = c.region_id;

-- 創(chuàng)建觸發(fā)器: 
CREATE OR REPLACE TRIGGER "HR"."TR_I_O_REG_COU" INSTEAD OF
 INSERT ON v_reg_cou FOR EACH ROW DECLARE v_count NUMBER;
BEGIN
 SELECT COUNT(*) INTO v_count FROM regions WHERE region_id = :new.r_id;
 IF v_count = 0 THEN
    INSERT INTO regions
      (region_id, region_name
      ) VALUES
      (:new.r_id, :new.r_name
      );
 END IF;
 
 SELECT COUNT(*) INTO v_count FROM countries WHERE country_id = :new.c_id;
 IF v_count = 0 THEN
    INSERT
    INTO countries
      (
        country_id,
        country_name,
        region_id
      )
      VALUES
      (
        :new.c_id,
        :new.c_name,
        :new.r_id
      );
 END IF;
END;

創(chuàng)建INSTEAD OF觸發(fā)器需要注意以下幾點(diǎn):

  • 只能被創(chuàng)建在視圖上膳汪,并且該視圖沒(méi)有指定WITH CHECK OPTION選項(xiàng)唯蝶。
  • 不能指定BEFORE 或 AFTER選項(xiàng)。
  • FOR EACH ROW子可是可選的遗嗽,即INSTEAD OF觸發(fā)器只能在行級(jí)上觸發(fā)粘我、或只能是行級(jí)觸發(fā)器,沒(méi)有必要指定痹换。
  • 沒(méi)有必要在針對(duì)一個(gè)表的視圖上創(chuàng)建INSTEAD OF觸發(fā)器征字,只要?jiǎng)?chuàng)建DML觸發(fā)器就可以了。

6.8 創(chuàng)建系統(tǒng)事件觸發(fā)器

ORACLE10G提供的系統(tǒng)事件觸發(fā)器可以在DDL或數(shù)據(jù)庫(kù)系統(tǒng)上被觸發(fā)娇豫。DDL指的是數(shù)據(jù)定義語(yǔ)言匙姜,如CREATE 、ALTER及DROP 等冯痢。而數(shù)據(jù)庫(kù)系統(tǒng)事件包括數(shù)據(jù)庫(kù)服務(wù)器的啟動(dòng)或關(guān)閉氮昧,用戶(hù)的登錄與退出、數(shù)據(jù)庫(kù)服務(wù)錯(cuò)誤等系羞。創(chuàng)建系統(tǒng)觸發(fā)器的語(yǔ)法如下:

CREATE OR REPLACE TRIGGER [sachema.]trigger_name
{BEFORE|AFTER}
{ddl_event_list | database_event_list}
ON { DATABASE | [schema.]SCHEMA }
[WHEN condition]
PL/SQL_block | CALL procedure_name;
  • ddl_event_list :一個(gè)或多個(gè)DDL 事件郭计,事件間用 OR 分開(kāi);
  • database_event_list :一個(gè)或多個(gè)數(shù)據(jù)庫(kù)事件椒振,事件間用 OR 分開(kāi)昭伸;
  • 系統(tǒng)事件觸發(fā)器既可以建立在一個(gè)模式上,又可以建立在整個(gè)數(shù)據(jù)庫(kù)上澎迎。當(dāng)建立在模式(SCHEMA)之上時(shí)庐杨,只有模式所指定用戶(hù)的DDL操作和它們所導(dǎo)致的錯(cuò)誤才激活觸發(fā)器, 默認(rèn)時(shí)為當(dāng)前用戶(hù)模式。當(dāng)建立在數(shù)據(jù)庫(kù)(DATABASE)之上時(shí)夹供,該數(shù)據(jù)庫(kù)所有用戶(hù)的DDL操作和他們所導(dǎo)致的錯(cuò)誤灵份,以及數(shù)據(jù)庫(kù)的啟動(dòng)和關(guān)閉均可激活觸發(fā)器。要在數(shù)據(jù)庫(kù)之上建立觸發(fā)器時(shí)哮洽,要求用戶(hù)具有ADMINISTER DATABASE TRIGGER權(quán)限填渠。

系統(tǒng)觸發(fā)器的種類(lèi)和事件出現(xiàn)的時(shí)機(jī)表(前或后)

事件 允許的時(shí)機(jī) 說(shuō)明
STARTUP AFTER 啟動(dòng)數(shù)據(jù)庫(kù)實(shí)例之后觸發(fā)
SHUTDOWN BEFORE 關(guān)閉數(shù)據(jù)庫(kù)實(shí)例之前觸發(fā)(非正常關(guān)閉不觸發(fā))
SERVERERROR AFTER 數(shù)據(jù)庫(kù)服務(wù)器發(fā)生錯(cuò)誤之后觸發(fā)
LOGON AFTER 成功登錄連接到數(shù)據(jù)庫(kù)后觸發(fā)
LOGOFF BEFORE 開(kāi)始斷開(kāi)數(shù)據(jù)庫(kù)連接之前觸發(fā)
CREATE BEFORE,AFTER 在執(zhí)行CREATE語(yǔ)句創(chuàng)建數(shù)據(jù)庫(kù)對(duì)象之前鸟辅、之后觸發(fā)
DROP BEFORE氛什,AFTER 在執(zhí)行DROP語(yǔ)句刪除數(shù)據(jù)庫(kù)對(duì)象之前、之后觸發(fā)
ALTER BEFORE匪凉,AFTER 在執(zhí)行ALTER語(yǔ)句更新數(shù)據(jù)庫(kù)對(duì)象之前枪眉、之后觸發(fā)
DDL BEFORE,AFTER 在執(zhí)行大多數(shù)DDL語(yǔ)句之前再层、之后觸發(fā)
GRANT BEFORE贸铜,AFTER 執(zhí)行GRANT語(yǔ)句授予權(quán)限之前堡纬、之后觸發(fā)
REVOKE BEFORE,AFTER 執(zhí)行REVOKE語(yǔ)句收權(quán)限之前蒿秦、之后觸犯發(fā)
RENAME BEFORE烤镐,AFTER 執(zhí)行RENAME語(yǔ)句更改數(shù)據(jù)庫(kù)對(duì)象名稱(chēng)之前、之后觸犯發(fā)
AUDIT / NOAUDIT BEFORE棍鳖,AFTER 執(zhí)行AUDIT或NOAUDIT進(jìn)行審計(jì)或停止審計(jì)之前职车、之后觸發(fā)

6.9 系統(tǒng)觸發(fā)器事件屬性

事件屬性\事件 Startup/Shutdown Servererror Logon/Logoff DDL DML
事件名稱(chēng) ?* ?* ?* ?*
數(shù)據(jù)庫(kù)名稱(chēng) ?*
數(shù)據(jù)庫(kù)實(shí)例號(hào) ?*
錯(cuò)誤號(hào) ?*
用戶(hù)名 ?*
模式對(duì)象類(lèi)型 ?*
模式對(duì)象名稱(chēng) ?*
?*

除DML語(yǔ)句的列屬性外,其余事件屬性值可通過(guò)調(diào)用ORACLE定義的事件屬性函數(shù)來(lái)讀取

函數(shù)名稱(chēng) 數(shù)據(jù)類(lèi)型 說(shuō) 明
Ora_sysevent VARCHAR2(20) 激活觸發(fā)器的事件名稱(chēng)
Instance_num NUMBER 數(shù)據(jù)庫(kù)實(shí)例名
Ora_database_name VARCHAR2(50) 數(shù)據(jù)庫(kù)名稱(chēng)
Server_error(posi) NUMBER 錯(cuò)誤信息棧中posi指定位置中的錯(cuò)誤號(hào)
Is_servererror(err_number) BOOLEAN 檢查err_number指定的錯(cuò)誤號(hào)是否在錯(cuò)誤信息棧中鹊杖,如果在則返回TRUE,否則返回FALSE扛芽。在觸發(fā)器內(nèi)調(diào)用此函數(shù)可以判斷是否發(fā)生指定的錯(cuò)誤骂蓖。
Login_user VARCHAR2(30) 登陸或注銷(xiāo)的用戶(hù)名稱(chēng)
Dictionary_obj_type VARCHAR2(20) DDL語(yǔ)句所操作的數(shù)據(jù)庫(kù)對(duì)象類(lèi)型
Dictionary_obj_name VARCHAR2(30) DDL語(yǔ)句所操作的數(shù)據(jù)庫(kù)對(duì)象名稱(chēng)
Dictionary_obj_owner VARCHAR2(30) DDL語(yǔ)句所操作的數(shù)據(jù)庫(kù)對(duì)象所有者名稱(chēng)
Des_encrypted_password VARCHAR2(2) 正在創(chuàng)建或修改的經(jīng)過(guò)DES算法加密的用戶(hù)口令
-- ex1. 創(chuàng)建觸發(fā)器,存放有關(guān)事件信息
DESC ora_sysevent
DESC ora_login_user
 
--創(chuàng)建用于記錄事件用的表
CREATE TABLE ddl_event
(crt_date timestamp PRIMARY KEY,
 event_name VARCHAR2(20),
 user_name VARCHAR2(10),
 obj_type VARCHAR2(20),
 obj_name VARCHAR2(20));
 
--創(chuàng)建觸犯發(fā)器
CREATE OR REPLACE TRIGGER tr_ddl
AFTER DDL ON SCHEMA
BEGIN
   INSERT INTO ddl_event VALUES
   (systimestamp,ora_sysevent, ora_login_user,
    ora_dict_obj_type, ora_dict_obj_name);
END tr_ddl;

-- ex2. 創(chuàng)建登錄川尖、退出觸發(fā)器登下。 
CREATE TABLE log_event
(user_name VARCHAR2(10),
 address VARCHAR2(20),
 logon_date timestamp,
 logoff_date timestamp);
 
--創(chuàng)建登錄觸發(fā)器
CREATE OR REPLACE TRIGGER tr_logon
AFTER LOGON ON DATABASE
BEGIN
   INSERT INTO log_event (user_name, address, logon_date)
   VALUES (ora_login_user, ora_client_ip_address, systimestamp);
END tr_logon;

--創(chuàng)建退出觸發(fā)器
CREATE OR REPLACE TRIGGER tr_logoff
BEFORE LOGOFF ON DATABASE
BEGIN
   INSERT INTO log_event (user_name, address, logoff_date)
   VALUES (ora_login_user, ora_client_ip_address, systimestamp);
END tr_logoff;

6.10 使用觸發(fā)器謂詞

ORACLE 提供三個(gè)參數(shù)INSERTING, UPDATING, DELETING 用于判斷觸發(fā)了哪些操作。

謂詞 行為
INSERTING 如果觸發(fā)語(yǔ)句是 INSERT 語(yǔ)句叮喳,則為T(mén)RUE,否則為FALSE
UPDATING 如果觸發(fā)語(yǔ)句是 UPDATE語(yǔ)句被芳,則為T(mén)RUE,否則為FALSE
DELETING 如果觸發(fā)語(yǔ)句是 DELETE 語(yǔ)句,則為T(mén)RUE,否則為FALSE

6.11 重新編譯觸發(fā)器

如果在觸發(fā)器內(nèi)調(diào)用其它函數(shù)或過(guò)程馍悟,當(dāng)這些函數(shù)或過(guò)程被刪除或修改后畔濒,觸發(fā)器的狀態(tài)將被標(biāo)識(shí)為無(wú)效。當(dāng)DML語(yǔ)句激活一個(gè)無(wú)效觸發(fā)器時(shí)锣咒,ORACLE將重新編譯觸發(fā)器代碼侵状,如果編譯時(shí)發(fā)現(xiàn)錯(cuò)誤,這將導(dǎo)致DML語(yǔ)句執(zhí)行失敗毅整∪ば郑可以調(diào)用ALTER TRIGGER語(yǔ)句重新編譯已經(jīng)創(chuàng)建的觸發(fā)器,格式為:

ALTER TRIGGER [schema.] trigger_name COMPILE [ DEBUG]

6.12 刪除和使能觸發(fā)器

  • 刪除觸發(fā)器:DROP TRIGGER trigger_name;

當(dāng)刪除其他用戶(hù)模式中的觸發(fā)器名稱(chēng)悼嫉,需要具有DROP ANY TRIGGER系統(tǒng)權(quán)限艇潭,當(dāng)刪除建立在數(shù)據(jù)庫(kù)上的觸發(fā)器時(shí),用戶(hù)需要具有ADMINISTER DATABASE TRIGGER系統(tǒng)權(quán)限戏蔑。
此外蹋凝,當(dāng)刪除表或視圖時(shí),建立在這些對(duì)象上的觸發(fā)器也隨之刪除辛臊。

  • 禁用或啟用觸發(fā)器: ALTER TIGGER trigger_name [DISABLE | ENABLE ];

(1) 有效狀態(tài)ENABLE:當(dāng)觸發(fā)事件發(fā)生時(shí)仙粱,處于有效狀態(tài)的數(shù)據(jù)庫(kù)觸發(fā)器 TRIGGER 將被觸發(fā)。
(2) 無(wú)效狀態(tài)DISABLE:當(dāng)觸發(fā)事件發(fā)生時(shí)彻舰,處于無(wú)效狀態(tài)的數(shù)據(jù)庫(kù)觸發(fā)器 TRIGGER 將不會(huì)被觸發(fā)伐割,此時(shí)就跟沒(méi)有這個(gè)數(shù)據(jù)庫(kù)觸發(fā)器TRIGGER 一樣候味。

ALTER TRIGGER語(yǔ)句一次只能改變一個(gè)觸發(fā)器的狀態(tài),而ALTER TABLE語(yǔ)句則一次能夠改變與指定表相關(guān)的所有觸發(fā)器的使用狀態(tài)隔心。

--格式為
ALTER TABLE [schema.]table_name {ENABLE|DISABLE} ALL TRIGGERS;

--例:使表EMP 上的所有TRIGGER 失效:
ALTER TABLE emp DISABLE ALL TRIGGERS;

6.13 觸發(fā)器的應(yīng)用舉例

例1. 創(chuàng)建一個(gè)DML語(yǔ)句級(jí)觸發(fā)器白群,當(dāng)對(duì)emp表執(zhí)行INSERT, UPDATE, DELETE 操作時(shí),它自動(dòng)更新dept_summary 表中的數(shù)據(jù)硬霍。由于在PL/SQL塊中不能直接調(diào)用DDL語(yǔ)句帜慢,所以,利用ORACLE內(nèi)置包DBMS_UTILITY中的EXEC_DDL_STATEMENT過(guò)程唯卖,由它執(zhí)行DDL語(yǔ)句創(chuàng)建觸發(fā)器粱玲。

CREATE TABLE dept_summary(
 Deptno NUMBER(2),
 Sal_sum NUMBER(9, 2),
 Emp_count NUMBER);
 
INSERT INTO dept_summary(deptno, sal_sum, emp_count)
 SELECT deptno, SUM(sal), COUNT(*)
FROM emp
GROUP BY deptno;
 
--創(chuàng)建一個(gè)PL/SQL過(guò)程disp_dept_summary
--在觸發(fā)器中調(diào)用該過(guò)程顯示dept_summary標(biāo)中的數(shù)據(jù)。
CREATE OR REPLACE PROCEDURE disp_dept_summary
IS
 Rec dept_summary%ROWTYPE;
 CURSOR c1 IS SELECT * FROM dept_summary;
BEGIN
 OPEN c1;
 FETCH c1 INTO REC;
 DBMS_OUTPUT.PUT_LINE('deptno    sal_sum    emp_count');
 DBMS_OUTPUT.PUT_LINE('-------------------------------------');
 WHILE c1%FOUND LOOP
    DBMS_OUTPUT.PUT_LINE(RPAD(rec.deptno, 6)||
      To_char(rec.sal_sum, '$999,999.99')||
      LPAD(rec.emp_count, 13));
    FETCH c1 INTO rec;
 END LOOP;
 CLOSE c1;
END;
BEGIN
 DBMS_OUTPUT.PUT_LINE('插入前');
 Disp_dept_summary();
 DBMS_UTILITY.EXEC_DDL_STATEMENT('
    CREATE OR REPLACE TRIGGER trig1
      AFTER INSERT OR DELETE OR UPDATE OF sal ON emp
    BEGIN
      DBMS_OUTPUT.PUT_LINE(''正在執(zhí)行trig1 觸發(fā)器…'');
      DELETE FROM dept_summary;
      INSERT INTO dept_summary(deptno, sal_sum, emp_count)
      SELECT deptno, SUM(sal), COUNT(*)
      FROM emp GROUP BY deptno;
    END;
 ');
 
 
 INSERT INTO dept(deptno, dname, loc)
 VALUES(90, ‘demo_dept’, ‘none_loc’);
 INSERT INTO emp(ename, deptno, empno, sal)
 VALUES(USER, 90, 9999, 3000);
 
 DBMS_OUTPUT.PUT_LINE('插入后');
 Disp_dept_summary();
 
 UPDATE emp SET sal=1000 WHERE empno=9999;
 DBMS_OUTPUT.PUT_LINE('修改后');
 Disp_dept_summary();
 
 DELETE FROM emp WHERE empno=9999;
 DELETE FROM dept WHERE deptno=90;
 
 DBMS_OUTPUT.PUT_LINE('刪除后');
 Disp_dept_summary();
 DBMS_UTILITY.EXEC_DDL_STATEMENT(‘DROP TRIGGER trig1’);
EXCEPTION
   WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE(SQLCODE||'---'||SQLERRM);
 
END;

例2:創(chuàng)建DML語(yǔ)句行級(jí)觸發(fā)器拜轨。當(dāng)對(duì)emp表執(zhí)行INSERT, UPDATE, DELETE 操作時(shí)抽减,它自動(dòng)更新dept_summary 表中的數(shù)據(jù)。由于在PL/SQL塊中不能直接調(diào)用DDL語(yǔ)句橄碾,所以卵沉,利用ORACLE內(nèi)置包DBMS_UTILITY中的EXEC_DDL_STATEMENT過(guò)程,由它執(zhí)行DDL語(yǔ)句創(chuàng)建觸發(fā)器法牲。

BEGIN
  DBMS_OUTPUT.PUT_LINE('插入前');
  Disp_dept_summary();
  DBMS_UTILITY.EXEC_DDL_STATEMENT(
    'CREATE OR REPLACE TRIGGER trig2_update
      AFTER UPDATE OF sal ON emp
      REFERENCING OLD AS old_emp NEW AS new_emp
      FOR EACH ROW
      WHEN (old_emp.sal != new_emp.sal)
    BEGIN
      DBMS_OUTPUT.PUT_LINE(''正在執(zhí)行trig2_update 觸發(fā)器…'');
      DBMS_OUTPUT.PUT_LINE(''sal 舊值:''|| :old_emp.sal);
      DBMS_OUTPUT.PUT_LINE(''sal 新值:''|| :new_emp.sal);
      UPDATE dept_summary
        SET sal_sum=sal_sum + :new_emp.sal - :old_emp.sal
        WHERE deptno = :new_emp.deptno;
    END;'
  );
   
  DBMS_UTILITY.EXEC_DDL_STATEMENT(
    'CREATE OR REPLACE TRIGGER trig2_insert
      AFTER INSERT ON emp
      REFERENCING NEW AS new_emp
      FOR EACH ROW
    DECLARE
      I NUMBER;
    BEGIN
      DBMS_OUTPUT.PUT_LINE(''正在執(zhí)行trig2_insert 觸發(fā)器…'');
      SELECT COUNT(*) INTO I
      FROM dept_summary WHERE deptno = :new_emp.deptno;
      IF I > 0 THEN
        UPDATE dept_summary
        SET sal_sum=sal_sum+:new_emp.sal,
        Emp_count=emp_count+1
        WHERE deptno = :new_emp.deptno;
      ELSE
        INSERT INTO dept_summary
        VALUES (:new_emp.deptno, :new_emp.sal, 1);
      END IF;
    END;'
  );
 
  DBMS_UTILITY.EXEC_DDL_STATEMENT(
    'CREATE OR REPLACE TRIGGER trig2_delete
      AFTER DELETE ON emp
      REFERENCING OLD AS old_emp
      FOR EACH ROW
    DECLARE
      I NUMBER;
    BEGIN
      DBMS_OUTPUT.PUT_LINE(''正在執(zhí)行trig2_delete 觸發(fā)器…'');
      SELECT emp_count INTO I
      FROM dept_summary WHERE deptno = :old_emp.deptno;
      IF I >1 THEN
        UPDATE dept_summary
        SET sal_sum=sal_sum - :old_emp.sal,
        Emp_count=emp_count - 1
        WHERE deptno = :old_emp.deptno;
      ELSE
        DELETE FROM dept_summary WHERE deptno = :old_emp.deptno;
      END IF;
    END;'
  );
 
  INSERT INTO dept(deptno, dname, loc)
    VALUES(90, 'demo_dept', 'none_loc');
  INSERT INTO emp(ename, deptno, empno, sal)
    VALUES(USER, 90, 9999, 3000);
  INSERT INTO emp(ename, deptno, empno, sal)
    VALUES(USER, 90, 9998, 2000);
  DBMS_OUTPUT.PUT_LINE('插入后');
  Disp_dept_summary();
 
  UPDATE emp SET sal = sal*1.1 WHERE deptno=90;
  DBMS_OUTPUT.PUT_LINE('修改后');
  Disp_dept_summary();
 
  DELETE FROM emp WHERE deptno=90;
  DELETE FROM dept WHERE deptno=90;
  DBMS_OUTPUT.PUT_LINE('刪除后');
  Disp_dept_summary();
 
  DBMS_UTILITY.EXEC_DDL_STATEMENT('DROP TRIGGER trig2_update');
  DBMS_UTILITY.EXEC_DDL_STATEMENT('DROP TRIGGER trig2_insert');
  DBMS_UTILITY.EXEC_DDL_STATEMENT('DROP TRIGGER trig2_delete');
EXCEPTION
   WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE(SQLCODE||'---'||SQLERRM);
END;

例3:利用ORACLE提供的條件謂詞INSERTING史汗、UPDATING和DELETING創(chuàng)建與例2具有相同功能的觸發(fā)器。

BEGIN
    DBMS_OUTPUT.PUT_LINE('插入前');
    Disp_dept_summary();
    DBMS_UTILITY.EXEC_DDL_STATEMENT(
        'CREATE OR REPLACE TRIGGER trig2
            AFTER INSERT OR DELETE OR UPDATE OF sal
ON emp
            REFERENCING OLD AS old_emp NEW AS new_emp
            FOR EACH ROW
        DECLARE
            I NUMBER;
        BEGIN
            IF UPDATING AND :old_emp.sal != :new_emp.sal THEN
            DBMS_OUTPUT.PUT_LINE(''正在執(zhí)行trig2 觸發(fā)器…'');
                DBMS_OUTPUT.PUT_LINE(''sal 舊值:''|| :old_emp.sal);
                DBMS_OUTPUT.PUT_LINE(''sal 新值:''|| :new_emp.sal);
                UPDATE dept_summary
                    SET sal_sum=sal_sum + :new_emp.sal - :old_emp.sal
                WHERE deptno = :new_emp.deptno;
            ELSIF INSERTING THEN
                DBMS_OUTPUT.PUT_LINE(''正在執(zhí)行trig2觸發(fā)器…'');
                SELECT COUNT(*) INTO I
        FROM dept_summary
        WHERE deptno = :new_emp.deptno;
                IF I > 0 THEN
                    UPDATE dept_summary
          SET sal_sum=sal_sum+:new_emp.sal,
              Emp_count=emp_count+1
          WHERE deptno = :new_emp.deptno;
            ELSE
          INSERT INTO dept_summary
            VALUES (:new_emp.deptno, :new_emp.sal, 1);
        END IF;
      ELSE
        DBMS_OUTPUT.PUT_LINE(''正在執(zhí)行trig2觸發(fā)器…'');
        SELECT emp_count INTO I
        FROM dept_summary WHERE deptno = :old_emp.deptno;
      IF I > 1 THEN
        UPDATE dept_summary
        SET sal_sum=sal_sum - :old_emp.sal,
        Emp_count=emp_count - 1
        WHERE deptno = :old_emp.deptno;
      ELSE
          DELETE FROM dept_summary
          WHERE deptno = :old_emp.deptno;
      END IF;
    END IF;
    END;'
  );
 
  INSERT INTO dept(deptno, dname, loc)
    VALUES(90, 'demo_dept', 'none_loc');
  INSERT INTO emp(ename, deptno, empno, sal)
    VALUES(USER, 90, 9999, 3000);
  INSERT INTO emp(ename, deptno, empno, sal)
    VALUES(USER, 90, 9998, 2000);
  DBMS_OUTPUT.PUT_LINE('插入后');
  Disp_dept_summary();
 
  UPDATE emp SET sal = sal*1.1 WHERE deptno=90;
  DBMS_OUTPUT.PUT_LINE('修改后');
  Disp_dept_summary();
 
  DELETE FROM emp WHERE deptno=90;
  DELETE FROM dept WHERE deptno=90;
  DBMS_OUTPUT.PUT_LINE('刪除后');
  Disp_dept_summary();
 
  DBMS_UTILITY.EXEC_DDL_STATEMENT('DROP TRIGGER trig2');
EXCEPTION
   WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE(SQLCODE||'---'||SQLERRM);
END;

例4:創(chuàng)建INSTEAD OF 觸發(fā)器拒垃。首先創(chuàng)建一個(gè)視圖myview, 由于該視圖是復(fù)合查詢(xún)所產(chǎn)生的視圖停撞,所以不能執(zhí)行DML語(yǔ)句。根據(jù)用戶(hù)對(duì)視圖所插入的數(shù)據(jù)判斷需要將數(shù)據(jù)插入到哪個(gè)視圖基表中悼瓮,然后對(duì)該基表執(zhí)行插入操作怜森。

DECLARE
    No NUMBER;
    Name VARCHAR2(20);
BEGIN
    DBMS_UTILITY.EXEC_DDL_STATEMENT('
        CREATE OR REPLACE VIEW myview AS
            SELECT empno, ename, ''E'' type FROM emp
            UNION
            SELECT dept.deptno, dname, ''D'' FROM dept
    ');
    -- 創(chuàng)建INSTEAD OF 觸發(fā)器trigger3;
    DBMS_UTILITY.EXEC_DDL_STATEMENT('
        CREATE OR REPLACE TRIGGER trig3
            INSTEAD OF INSERT ON myview
            REFERENCING NEW n
            FOR EACH ROW
        DECLARE
            Rows INTEGER;
        BEGIN
            DBMS_OUTPUT.PUT_LINE(''正在執(zhí)行trig3觸發(fā)器…'');
            IF :n.type = ''D'' THEN
                SELECT COUNT(*) INTO rows
                    FROM dept WHERE deptno = :n.empno;
                IF rows = 0 THEN
                    DBMS_OUTPUT.PUT_LINE(''向dept表中插入數(shù)據(jù)…'');
                    INSERT INTO dept(deptno, dname, loc)
                        VALUES (:n.empno, :n.ename, ''none’’);
                ELSE
                    DBMS_OUTPUT.PUT_LINE(''編號(hào)為''|| :n.empno||
                     ''的部門(mén)已存在,插入操作失敯怠副硅!'');
                 END IF;
            ELSE
                SELECT COUNT(*) INTO rows
                    FROM emp WHERE empno = :n.empno;
                IF rows = 0 THEN
                    DBMS_OUTPUT.PUT_LINE('’向emp表中插入數(shù)據(jù)…’’);
                    INSERT INTO emp(empno, ename)
                        VALUES(:n.empno, :n.ename);
                ELSE
                    DBMS_OUTPUT.PUT_LINE(''編號(hào)為''|| :n.empno||
                      ''的人員已存在,插入操作失敗!'');
                END IF;
            END IF;
        END;
    ');
 
    INSERT INTO myview VALUES (70, 'demo', 'D');
    INSERT INTO myview VALUES (9999, USER, 'E');
    SELECT deptno, dname INTO no, name FROM dept WHERE deptno=70;
    DBMS_OUTPUT.PUT_LINE('員工編號(hào):'||TO_CHAR(no)||'姓名:'||name);
    SELECT empno, ename INTO no, name FROM emp WHERE empno=9999;
    DBMS_OUTPUT.PUT_LINE('部門(mén)編號(hào):'||TO_CHAR(no)||'姓名:'||name);
  DELETE FROM emp WHERE empno=9999;
  DELETE FROM dept WHERE deptno=70;
    DBMS_UTILITY.EXEC_DDL_STATEMENT('DROP TRIGGER trig3');
END;

例5:利用ORACLE事件屬性函數(shù)翅萤,創(chuàng)建一個(gè)系統(tǒng)事件觸發(fā)器恐疲。首先創(chuàng)建一個(gè)事件日志表eventlog,由它存儲(chǔ)用戶(hù)在當(dāng)前數(shù)據(jù)庫(kù)中所創(chuàng)建的數(shù)據(jù)庫(kù)對(duì)象套么,以及用戶(hù)的登陸和注銷(xiāo)培己、數(shù)據(jù)庫(kù)的啟動(dòng)和關(guān)閉等事件,之后創(chuàng)建trig4_ddl胚泌、trig4_before和trig4_after觸發(fā)器省咨,它們調(diào)用事件屬性函數(shù)將各個(gè)事件記錄到eventlog數(shù)據(jù)表中。

BEGIN
    -- 創(chuàng)建用于記錄事件日志的數(shù)據(jù)表
    DBMS_UTILITY.EXEC_DDL_STATEMENT('
        CREATE TABLE eventlog(
            Eventname VARCHAR2(20) NOT NULL,
            Eventdate date default sysdate,
            Inst_num NUMBER NULL,
            Db_name VARCHAR2(50) NULL,
            Srv_error NUMBER NULL,
            Username VARCHAR2(30) NULL,
            Obj_type VARCHAR2(20) NULL,
            Obj_name VARCHAR2(30) NULL,
            Obj_owner VARCHAR2(30) NULL
        )
    ');
 
    -- 創(chuàng)建DDL觸發(fā)器trig4_ddl
    DBMS_UTILITY.EXEC_DDL_STATEMENT('
        CREATE OR REPLACE TRIGGER trig4_ddl
            AFTER CREATE OR ALTER OR DROP
ON DATABASE
        DECLARE
            Event VARCHAR2(20);
            Typ VARCHAR2(20);
            Name VARCHAR2(30);
            Owner VARCHAR2(30);
        BEGIN
            -- 讀取DDL事件屬性
            Event := SYSEVENT;
            Typ := DICTIONARY_OBJ_TYPE;
            Name := DICTIONARY_OBJ_NAME;
            Owner := DICTIONARY_OBJ_OWNER;
            --將事件屬性插入到事件日志表中
            INSERT INTO scott.eventlog(eventname, obj_type, obj_name, obj_owner)
                VALUES(event, typ, name, owner);
        END;
    ');
 
    -- 創(chuàng)建LOGON玷室、STARTUP和SERVERERROR 事件觸發(fā)器
    DBMS_UTILITY.EXEC_DDL_STATEMENT('
        CREATE OR REPLACE TRIGGER trig4_after
            AFTER LOGON OR STARTUP OR SERVERERROR
      ON DATABASE
        DECLARE
            Event VARCHAR2(20);
            Instance NUMBER;
            Err_num NUMBER;
            Dbname VARCHAR2(50);
            User VARCHAR2(30);
        BEGIN
            Event := SYSEVENT;
            IF event = ''LOGON'' THEN
                User := LOGIN_USER;
                INSERT INTO eventlog(eventname, username)
                    VALUES(event, user);
            ELSIF event = ''SERVERERROR'' THEN
                Err_num := SERVER_ERROR(1);
                INSERT INTO eventlog(eventname, srv_error)
                    VALUES(event, err_num);
            ELSE
                Instance := INSTANCE_NUM;
                Dbname := DATABASE_NAME;
                INSERT INTO eventlog(eventname, inst_num, db_name)
                    VALUES(event, instance, dbname);
      END IF;
    END;
  ');
 
  -- 創(chuàng)建LOGOFF和SHUTDOWN 事件觸發(fā)器
  DBMS_UTILITY.EXEC_DDL_STATEMENT('
    CREATE OR REPLACE TRIGGER trig4_before
      BEFORE LOGOFF OR SHUTDOWN
      ON DATABASE
    DECLARE
      Event VARCHAR2(20);
      Instance NUMBER;
      Dbname VARCHAR2(50);
      User VARCHAR2(30);
    BEGIN
      Event := SYSEVENT;
      IF event = ''LOGOFF'' THEN
        User := LOGIN_USER;
        INSERT INTO eventlog(eventname, username)
          VALUES(event, user);
      ELSE
        Instance := INSTANCE_NUM;
        Dbname := DATABASE_NAME;
        INSERT INTO eventlog(eventname, inst_num, db_name)
          VALUES(event, instance, dbname);
      END IF;
    END;
  ');
END;
 
CREATE TABLE mydata(mydate NUMBER);
CONNECT SCOTT/TIGER
 
COL eventname FORMAT A10
COL eventdate FORMAT A12
COL username FORMAT A10
COL obj_type FORMAT A15
COL obj_name FORMAT A15
COL obj_owner FORMAT A10
SELECT eventname, eventdate, obj_type, obj_name, obj_owner, username, Srv_error
  FROM eventlog;
 
DROP TRIGGER trig4_ddl;
DROP TRIGGER trig4_before;
DROP TRIGGER trig4_after;
DROP TABLE eventlog;
DROP TABLE mydata;

例6. 復(fù)雜的審計(jì)功能零蓉,將EMP 表的變化情況記錄到AUDIT_TABLE和AUDIT_TABLE_VALUES中笤受。

CREATE TABLE audit_table(
    Audit_id     NUMBER,
    User_name VARCHAR2(20),
    Now_time DATE,
    Terminal_name VARCHAR2(10),
    Table_name VARCHAR2(10),
    Action_name VARCHAR2(10),
    Emp_id NUMBER(4));
 
CREATE TABLE audit_table_val(
    Audit_id NUMBER,
    Column_name VARCHAR2(10),
    Old_val NUMBER(7,2),
    New_val NUMBER(7,2));
 
CREATE SEQUENCE audit_seq
    START WITH 1000
    INCREMENT BY 1
    NOMAXVALUE
    NOCYCLE NOCACHE;
 
CREATE OR REPLACE TRIGGER audit_emp
    AFTER INSERT OR UPDATE OR DELETE ON emp
    FOR EACH ROW
DECLARE
    Time_now DATE;
    Terminal CHAR(10);
BEGIN
    Time_now:=sysdate;
    Terminal:=USERENV('TERMINAL');
    IF INSERTING THEN
        INSERT INTO audit_table
    VALUES(audit_seq.NEXTVAL, user, time_now,
           terminal, 'EMP', 'INSERT', :new.empno);
    ELSIF DELETING THEN
        INSERT INTO audit_table
    VALUES(audit_seq.NEXTVAL, user, time_now,
           terminal, 'EMP', 'DELETE', :old.empno);
    ELSE
        INSERT INTO audit_table
    VALUES(audit_seq.NEXTVAL, user, time_now,
           terminal, 'EMP', 'UPDATE', :old.empno);
        IF UPDATING('SAL') THEN
            INSERT INTO audit_table_val
                VALUES(audit_seq.CURRVAL, 'SAL', :old.sal, :new.sal);
        ELSE UPDATING('DEPTNO')
            INSERT INTO audit_table_val
                VALUES(audit_seq.CURRVAL, 'DEPTNO', :old.deptno, :new.deptno);
        END IF;
    END IF;
END;

例7. 增強(qiáng)數(shù)據(jù)的完整性管理,修改DEPT表的DEPTNO列時(shí)敌蜂,同時(shí)把EMP表中相應(yīng)的DEPTNO也作相應(yīng)的修改箩兽;

CREATE SEQUENCE update_sequence
    INCREMENT BY 1
    START WITH 1000
    MAXVALUE 5000 CYCLE;
 
ALTER TABLE emp
    ADD update_id NUMBER;
 
CREATE OR REPLACE PACKAGE integritypackage AS
    Updateseq NUMBER;
END integritypackage;
 
CREATE OR REPLACE PACKAGE BODY integritypackage AS
END integritypackage;
 
CREATE OR REPLACE TRIGGER dept_cascade1
    BEFORE UPDATE OF deptno ON dept
DECLARE
    Dummy NUMBER;
BEGIN
    SELECT update_sequence.NEXTVAL INTO dummy FROM dual;
    Integritypackage.updateseq:=dummy;
END;
 
CREATE OR REPLACE TRIGGER dept_cascade2
    AFTER DELETE OR UPDATE OF deptno ON dept
    FOR EACH ROW
BEGIN
    IF UPDATING THEN
        UPDATE emp SET deptno=:new.deptno,
     update_id=integritypackage.updateseq
        WHERE emp.deptno=:old.deptno AND update_id IS NULL;
    END IF;
    IF DELETING THEN
        DELETE FROM emp
            WHERE emp.deptno=:old.deptno;
    END IF;
END;
 
CREATE OR REPLACE TRIGGER dept_cascade3
    AFTER UPDATE OF deptno ON dept
BEGIN
    UPDATE emp SET update_id=NULL
        WHERE update_id=integritypackage.updateseq;
END;
 
SELECT * FROM EMP ORDER BY DEPTNO;
UPDATE dept SET deptno=25 WHERE deptno=20;

例8. 幫助實(shí)現(xiàn)安全控制;保證對(duì)EMP表的修改僅在工作日的工作時(shí)間章喉;

CREATE TABLE company_holidays(day DATE);
 
INSERT INTO company_holidays
    VALUES(sysdate);
INSERT INTO company_holidays
VALUES(TO_DATE('21-10月-01', 'DD-MON-YY'));
 
CREATE OR REPLACE TRIGGER emp_permit_change
    BEFORE INSERT OR DELETE OR UPDATE ON emp
DECLARE
    Dummy NUMBER;
    Not_on_weekends EXCEPTION;
    Not_on_holidays EXCEPTION;
    Not_working_hours EXCEPTION;
BEGIN
    /* check for weekends */
IF TO_CHAR(SYSDATE, 'DAY') IN ('星期六', '星期日') THEN
    RAISE not_on_weekends;
END IF;
    /* check for company holidays */
SELECT COUNT(*) INTO dummy FROM company_holidays
    WHERE TRUNC(day)=TRUNC(SYSDATE);
IF dummy >0 THEN
    RAISE not_on_holidays;
END IF;
    /* check for work hours(8:00 AM to 18:00 PM */
IF (TO_CHAR(SYSDATE,'HH24')<8 OR TO_CHAR(SYSDATE, 'HH24')>18) THEN
  RAISE not_working_hours;
END IF;
EXCEPTION
  WHEN not_on_weekends THEN
    RAISE_APPLICATION_ERROR(-20324,
'May not change employee table during the weekends');
  WHEN not_on_holidays THEN
    RAISE_APPLICATION_ERROR(-20325,
'May not change employee table during a holiday');
  WHEN not_working_hours THEN
    RAISE_APPLICATION_ERROR(-20326,
'May not change employee table during no_working hours');
END;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末汗贫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子秸脱,更是在濱河造成了極大的恐慌落包,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摊唇,死亡現(xiàn)場(chǎng)離奇詭異妥色,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)遏片,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)撮竿,“玉大人吮便,你說(shuō)我怎么就攤上這事〈碧ぃ” “怎么了髓需?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)房蝉。 經(jīng)常有香客問(wèn)我僚匆,道長(zhǎng),這世上最難降的妖魔是什么搭幻? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任咧擂,我火速辦了婚禮,結(jié)果婚禮上檀蹋,老公的妹妹穿的比我還像新娘松申。我一直安慰自己,他們只是感情好俯逾,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布贸桶。 她就那樣靜靜地躺著,像睡著了一般桌肴。 火紅的嫁衣襯著肌膚如雪皇筛。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,071評(píng)論 1 285
  • 那天坠七,我揣著相機(jī)與錄音水醋,去河邊找鬼旗笔。 笑死,一個(gè)胖子當(dāng)著我的面吹牛离例,可吹牛的內(nèi)容都是我干的换团。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼宫蛆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼艘包!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起耀盗,我...
    開(kāi)封第一講書(shū)人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤想虎,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后叛拷,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體舌厨,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年忿薇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了裙椭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡署浩,死狀恐怖揉燃,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情筋栋,我是刑警寧澤炊汤,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站弊攘,受9級(jí)特大地震影響抢腐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜襟交,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一迈倍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧捣域,春花似錦授瘦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至丘侠,卻和暖如春徒欣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蜗字。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工打肝, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留脂新,地道東北人肃廓。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓悲幅,卻偏偏與公主長(zhǎng)得像囱桨,于是被迫代替她去往敵國(guó)和親役纹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法囤踩,類(lèi)相關(guān)的語(yǔ)法蚓聘,內(nèi)部類(lèi)的語(yǔ)法壳鹤,繼承相關(guān)的語(yǔ)法鉴嗤,異常的語(yǔ)法斩启,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,582評(píng)論 18 399
  • PL/SQL 這是對(duì)Oracle-SQL知識(shí)點(diǎn)詳細(xì)介紹的文章系列,其他文章如下: Oracle-SQL系列知識(shí)點(diǎn)(...
    GuaKin_Huang閱讀 6,965評(píng)論 0 14
  • 觸發(fā)器分類(lèi) SQL Server提供三類(lèi)觸發(fā)器: DML觸發(fā)器:在數(shù)據(jù)庫(kù)中發(fā)生數(shù)據(jù)操作(Insert醉锅、Update...
    不知名的蛋撻閱讀 1,690評(píng)論 0 5
  • 文:紫眸逝雨傾城 悠悠長(zhǎng)歌泣 流月匆匆誰(shuí)相惜 輕握手中的沙 看懂了時(shí)間卻心兒錯(cuò)伢 這一筆落下 沁透了前世的人啊 讓...
    紫眸逝雨傾城閱讀 203評(píng)論 0 6
  • 目錄君 第十二章 入土為安 張曉宇死后的第三天兔簇。 陳德喜對(duì)趙春蘭說(shuō):“妹妹,人死不能復(fù)生硬耍,依我看還是將曉宇早日入...
    曹靜鄭州閱讀 1,807評(píng)論 0 2