2016年7月28日 星期四

學習初級Oracle PL/SQL (五)

Single Row Processing
現在,將開始使用PL/SQLinsert,update,delete旅程.一次insert一行,然後每次update一行,接著一次delete,最後一次select一行.

Inserts
PL/SQL將數據insert到數據庫中,您只需編寫一個SQL INSERT語句,其中PL/SQLvalue是文字,PL/SQL變量或SQL column. 這裡將會舉例常見的做法,然後通過進展持續改進你的設計. 你會明白PL/SQL程序員最常做的, 以及如何改善代碼. 請花多點時間了解這些方案.
正常的時候你很少使用PL/SQL執行一個簡單的INSERT語句, 因為你可以使用SQLPLUS scripts而不使用PL/SQL.更多的時候,你要insertupdate記錄時發現記錄已經存在於數據庫中,該記錄要insert還是update? Sqlplus scripts就不能有更好的做法.

以下是用PL/SQL insert的例子, empno建立一個sequence, scott.emp 增加一個記錄,並為ename,job,mgr,sal,comm 加入unique key防止重覆值:
create sequence emp_seq start with 8001 increment by 10;
alter table emp add constraint UK1_emp unique(ename,job,mgr,sal,comm) using index;
set serveroutput on size 1000000;

declare
n_empno          emp.empno%type;
n_mgr               emp.mgr%type;
n_deptno  emp.deptno%type;

-- n_count來保全insert結果.
n_count    number;

begin
       
        -- 假設,我們獲取BLAKE mgr number.
        begin
         select empno into n_mgr from emp where ename='BLAKE';
        exception
         when others then raise_application_error(-20002, SQLERRM||' get mgr number');
        end;

        -- 然後獲取sales deptno.
        begin
         select deptno into n_deptno from dept where dname='SALES';
        exception
         when others then raise_application_error(-20003, SQLERRM||' get deptno');
        end;

        -- 然後獲取 empno sequence.
        begin
         select emp_seq.nextval into n_empno from dual;
        exception
         when others then raise_application_error(-20004, SQLERRM||' get empno seq');
        end;

        -- 需要獲取的ID已齊全, 終於可以開始insert.
        begin
         insert into emp(empno,ename,job,mgr,hiredate,sal,comm,deptno) values(
         n_empno,'SALLY','SALESMAN',n_mgr,SYSDATE,1500,300,n_deptno);
         
         n_count := sql%rowcount;
        exception
         when others then raise_application_error(-20005, SQLERRM||' insert record');
        end;
        sys.dbms_output.put_line(to_char(n_count)||' record(s) inserted.');
end;
/

commit;
執行效果:
1 record(s) inserted.
PL/SQL procedure successfully completed.

假若再次執行則出現下方錯誤:
declare
*
ERROR at line 1:
ORA-20005: ORA-00001: unique constraint (SCOTT.UK1_EMP) violated insert record
ORA-06512: at line 39

Unique constraint exception -20005 出現.
現在, 如果你要處理這種特殊的exception, 可以使用以下三種策略:

- 使用"DUP_VAL_ON_INDEX"捕捉exception (good).
- 使用額外的PL/SQL代碼來檢測預重複值的存在 (better).
- 使用額外的SQL代碼來檢測預重複值的存在 (best)

讓我們來看看這些解決方案,goodbest.



使用"DUP_VAL_ON_INDEX"捕捉exception
當涉及到處理的代碼, 估計可能會引發一個DUP_VAL_ON_INDEX exception, 捕捉exception是最懶的解決方案.
Oracle 會在任何現有的unique indexunique key檢查table有沒有重覆值. 如果發現重覆,PL/SQL會使用 DUP_VAL_ON_INDEX exception. 那麼,你如何處理這個問題?

-- 承接上一段代碼更改如下
..
        begin
         insert into emp(empno,ename,job,mgr,hiredate,sal,comm,deptno) values(
         n_empno,'SALLY','SALESMAN',n_mgr,SYSDATE,1500,300,n_deptno);
         
         n_count := sql%rowcount;
        exception
         when DUP_VAL_ON_INDEX then
                n_count := 0;
                sys.dbms_output.put_line('Found duplicate record in table');
         when others then raise_application_error(-20005, SQLERRM||' insert record');
        end;
        sys.dbms_output.put_line(to_char(n_count)||' record(s) inserted.');
end;
/
執行效果:
Found duplicate record in table
0 record(s) inserted.

PL/SQL procedure successfully completed.

以上並沒有出現任何error,因為增加了WHEN DUP_VAL_ON_INDEX 的條件.可以更加好地處理重覆值的錯誤.



使用額外的PL/SQL代碼來檢測預重複值的存在
使用額外的PL/SQL代碼來檢測預重複值的存在是比使用"DUP_VAL_ON_INDEX"捕捉exception來的好,理由如下:

- 您維護您的PL/SQL程序執行的控制.
- 可以有條件地決定如何處理重複值.
- 您可以使用的檢測方式取得這個rowprimary key,當發現是重複值,更改insertupdate.

現在我們看看怎樣用PL/SQL來檢測重複值(留意綠字):
declare
n_empno          emp.empno%type;
n_mgr               emp.mgr%type;
n_deptno  emp.deptno%type;
        -- 設定變量
        v_ename           emp.ename%type;
        v_job                emp.job%type;
        v_mgr               emp.ename%type;
        n_sal                emp.sal%type;
        n_comm           emp.comm%type;

n_count    number;

    begin
         -- 自定檢查用的變量值
         v_ename := 'SALLY';
         v_job      := 'SALESMAN';
         v_mgr     := 'BLAKE';
         n_sal       := 1500;
         n_comm  := 300;

        --使用 uk1_emp 的條件檢查重複值"UK1_emp unique(ename,job,mgr,sal,comm)"
        begin
         select count(1) into n_count from emp c1 cross join emp c2 where
         c1.ename=v_ename and c1.job=v_job and c2.ename=v_mgr and c1.sal=n_sal and c1.comm=n_comm;
        exception
         when others then raise_application_error(-20005, SQLERRM||' on detect duplicate');
        end;

    -- 如果沒有發現重複值
    if n_count = 0 then
       
        -- 獲取BLAKE mgr number.
        begin
         select empno into n_mgr from emp where ename='BLAKE';
        exception
         when others then raise_application_error(-20002, SQLERRM||' get mgr number');
        end;

        -- 然後獲取sales deptno.
        begin
         select deptno into n_deptno from dept where dname='SALES';
        exception
         when others then raise_application_error(-20003, SQLERRM||' get deptno');
        end;

        -- 然後獲取 empno sequence.
        begin
         select emp_seq.nextval into n_empno from dual;
        exception
         when others then raise_application_error(-20004, SQLERRM||' get empno seq');
        end;

        -- 需要獲取的資已齊全, 可以開始insert.
        begin
         insert into emp(empno,ename,job,mgr,hiredate,sal,comm,deptno) values(
         n_empno,v_ename,v_job,n_mgr,SYSDATE,n_sal,n_comm,n_deptno);
         n_count := sql%rowcount;
        exception
         when others then raise_application_error(-20006, SQLERRM||' insert record');
        end;

    -- 否則
    else
     n_count := 0;
     sys.dbms_output.put_line('Duplicate record found');
    end if;

    sys.dbms_output.put_line(to_char(n_count)||' record(s) inserted.');
    end;
/
commit;
執行效果:
Duplicate record found
0 record(s) inserted.

PL/SQL procedure successfully completed.

執行結果雖然和DUP_VAL_ON_INDEX一樣, 但是已經能用if…else來控制重複後的處理, 以下例子處理發現重複值後更改為update (留意綠字).
declare
v_ename           emp.ename%type;
v_job                        emp.job%type;
v_mgr               emp.ename%type;
n_mgr               emp.mgr%type;
n_sal                        emp.sal%type;
n_comm           emp.comm%type;
n_empno          emp.empno%type;
n_deptno  emp.deptno%type;
-- n_count number;  -- 由於有2個動作insertupdate,所以更改為以下變量儲存結果
n_inserted         number := 0;
n_updated                number := 0;

begin
  -- 自定檢查用的變量值
  v_ename       := 'SALLY';
  v_job             := 'SALESMAN';
  v_mgr            := 'BLAKE';
  n_sal             := 1500;
  n_comm        := 300;
       
        --使用 uk1_emp 的條件檢查重複值"UK1_emp unique(ename,job,mgr,sal,comm)"
        begin
         select c1.empno into n_empno from emp c1 cross join emp c2 where
         c1.ename=v_ename and c1.job=v_job and c2.ename=v_mgr and c1.sal=n_sal and c1.comm=n_comm;
        exception
         when NO_DATA_FOUND then n_empno := NULL;
         when others then raise_application_error(-20005, SQLERRM||' on detect duplicate');
        end;

        if n_empno is NULL then
         -- 獲取 empno sequence.
         begin
          select emp_seq.nextval into n_empno from dual;
         exception
          when others then raise_application_error(-20004, SQLERRM||' get empno seq');
         end;
         -- 獲取BLAKE mgr number.
         begin
          select empno into n_mgr from emp where ename='BLAKE';
         exception
          when others then raise_application_error(-20002, SQLERRM||' get mgr number');
         end;
         -- 然後獲取sales deptno.
         begin
          select deptno into n_deptno from dept where dname='SALES';
         exception
          when others then raise_application_error(-20003, SQLERRM||' get deptno');
         end;

         -- 需要獲取的資已齊全, 可以開始insert.
         begin
          insert into emp(empno,ename,job,mgr,hiredate,sal,comm,deptno) values(
          n_empno,v_ename,v_job,n_mgr,SYSDATE,n_sal,n_comm,n_deptno);
          n_inserted := sql%rowcount;
         exception
          when others then raise_application_error(-20006, SQLERRM||' insert record');
      end;
        else
         begin
          update emp set sal=n_sal where empno=n_empno;
          n_updated := sql%rowcount;
         exception
          when others then raise_application_error(-20007, SQLERRM||' update record');
         end;
        end if;
        sys.dbms_output.put_line(to_char(n_inserted)||' record(s) inserted.');
        sys.dbms_output.put_line(to_char(n_updated)||' record(s) updated.');
end;
/

執行效果:
0 record(s) inserted.
1 record(s) updated.

PL/SQL procedure successfully completed.

IF-THEN-ELSE語句的語法是:
[ ] 為非必要

單一條件
IF < condition> THEN
-- do this if it's TRUE
[ELSE
-- do this if it's not TRUE]
END IF;
多個條件
IF <condition> THEN
-- do this if it's TRUE
[ELSIF <condition> THEN
-- do this if it's TRUE
ELSIF ...
ELSE
-- do this if it's not TRUE]
END IF;




使用額外的SQL代碼來檢測預重複值的存在

這幾乎是最好的,SQL執行EXISTS子句做檢測的工作!EXISTS子句被執行時,如果重複值不存在SQL會立刻insert.

以下例子:
-- 先把記錄刪除
delete emp where ename=’SALLY’;
declare
v_ename           emp.ename%type;
v_job                        emp.job%type;
d_hiredate         emp.hiredate%type;
n_sal                        emp.sal%type;
n_comm           emp.comm%type;
n_count            number;

begin
 v_ename         := 'SALLY';
 v_job              := 'SALESMAN';
 d_hiredate       := sysdate;
 n_sal               := 1500;
 n_comm          := 300;

        -- 可以簡單使用SQL, 不用PL/SQL
        begin
         insert into emp(empno,ename,job,mgr,hiredate,sal,comm,deptno)
         select emp_seq.nextval,v_ename,v_job,c1.mgr,sysdate,n_sal,n_comm,c2.deptno
         from emp c1 cross join dept c2 where c1.ename='BLAKE' and c2.dname='SALES'
         and not exists(select 1 from emp c3 where c3.ename=v_ename and c3.job=v_job
         and c3.mgr=c1.mgr);
         n_count := sql%rowcount;
        exception
         when others then raise_application_error(-20006, SQLERRM||' on insert');
        end;
 sys.dbms_output.put_line(to_char(n_count)||' record(s) inserted.');
end;
/
commit;

執行2次效果:
1 record(s) inserted.
PL/SQL procedure successfully completed.

0 record(s) inserted.

PL/SQL procedure successfully completed.




It’s Your Turn to Insert
你要編寫一個用PL/SQL insert 以下4個員工到 emp:
 - TOM,   ANALYST, JONES, sysdate-10, 2500, null, RESEARCH
 - BILLY, CLERK,   BLAKE, sysdate-5,  800,  null, SALES
 - KELLY, SALESMAN,BLAKE, sysdate-7,  1400, 200,  SALES
 - ALAN,  ANALYST, CLARK, sysdate-30, 3000, null, ACCOUNTING

提示: 你可以使用 Anonymous + Function 完成.
declare
n_count number := 0;

function add_emp(
v_ename           in emp.ename%type,
v_job                in emp.job%type,
v_mgr               in emp.ename%type,
d_hiredate in emp.hiredate%type,
n_sal                in emp.sal%type,
n_comm           in emp.comm%type,
v_dname           in dept.dname%type)
return number as
begin
 insert into emp(empno,ename,job,mgr,hiredate,sal,comm,deptno)
 select emp_seq.nextval,v_ename,v_job,c1.mgr,d_hiredate,n_sal,n_comm,c2.deptno
 from emp c1 cross join dept c2 where c1.ename=v_mgr and c2.dname=v_dname and
 not exists(select 1 from emp c3 where c3.ename=v_ename and c3.job=v_job and
 c3.mgr=c1.mgr);
  return sql%rowcount;
exception
 when others then raise_application_error(-20006, SQLERRM||' on insert');
end add_emp;

begin
 n_count := n_count + add_emp('TOM','ANALYST','JONES',sysdate-10,2500,null,'RESEARCH');
 n_count := n_count + add_emp('BILLY','CLERK','BLAKE',sysdate-5,800,null,'SALES');
 n_count := n_count + add_emp('KELLY','SALESMAN','BLAKE',sysdate-7,1400,200,'SALES');
 n_count := n_count + add_emp('ALAN','ANALYST','CLARK',sysdate-30,3000,null,'ACCOUNTING');
 sys.dbms_output.put_line(to_char(n_count)||' add record(s).');
end;
/


沒有留言:

張貼留言