顯示具有 variables 標籤的文章。 顯示所有文章
顯示具有 variables 標籤的文章。 顯示所有文章

2016年8月8日 星期一

學習初級Oracle PL/SQL (六)

Single Row Processing

Updates
現在,我開始用PL/SQL更新數據庫數據的一行或多行.讓我們先從一個簡單的update開始.
以下例子使用NO_DATA_FOUND if then 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;

UPDATE的語句中,你必須用WHERE子句作條件,否則將會全表更新.



使用SQL來執行複雜的更新
SQL UPDATE語句是非常強大的, 它在一個語句可以更新一個或多個列. 為什麼我花了這麼多時間說明和了解SQL? 因為,PL/SQL的全部目的是控制何時執行適當的SQL語句.它並不是用表現不佳的PL/SQL語句替換SQL.

在以下例子,你可以在SQL UPDATE語句一次更新多個column.
select c1.ename,c1.job,c2.ename mgr,c1.hiredate,c1.sal,c1.comm,c3.dname from emp c1 join emp c2 on c1.mgr=c2.empno join dept c3 on c1.deptno=c3.deptno where c1.job='SALESMAN' order by ename;
ENAME      JOB       MGR        HIREDATE         SAL       COMM DNAME
---------- --------- ---------- --------- ---------- ---------- --------------
ALLEN      SALESMAN  BLAKE      20-FEB-81       1600        300 SALES
KELLY      SALESMAN  KING       22-JUL-16       1400        200 SALES
MARTIN     SALESMAN  BLAKE      28-SEP-81       1250       1400 SALES
SALLY      SALESMAN  BLAKE      29-JUL-16       1500        300 SALES
TURNER     SALESMAN  BLAKE      08-SEP-81       1500          0 SALES
WARD       SALESMAN  BLAKE      22-FEB-81       1250        500 SALES
update emp c1 set (c1.mgr,c1.sal,c1.deptno) =(
select c2.mgr,c1.sal+5, c3.deptno from emp c2 join emp c4 on c2.mgr=c4.empno cross join dept c3
where c4.ename = case when c1.job='SALESMAN' then 'CLARK' else 'BLAKE' end
and c3.dname = case when c1.job='SALESMAN' then 'OPERATIONS' else 'SALES' end)
where c1.job='SALESMAN';
6 rows updated.
select c1.ename,c1.job,c2.ename mgr,c1.hiredate,c1.sal,c1.comm,c3.dname from emp c1 join emp c2 on c1.mgr=c2.empno join dept c3 on c1.deptno=c3.deptno where c1.job='SALESMAN' order by ename;

ENAME      JOB       MGR        HIREDATE         SAL       COMM DNAME
---------- --------- ---------- --------- ---------- ---------- --------------
ALLEN      SALESMAN  CLARK      20-FEB-81       1605        300 OPERATIONS
KELLY      SALESMAN  CLARK      22-JUL-16       1405        200 OPERATIONS
MARTIN     SALESMAN  CLARK      28-SEP-81       1255       1400 OPERATIONS
SALLY      SALESMAN  CLARK      29-JUL-16       1505        300 OPERATIONS
TURNER     SALESMAN  CLARK      08-SEP-81       1505          0 OPERATIONS
WARD       SALESMAN  CLARK      22-FEB-81       1255        500 OPERATIONS

以上例子用了SQLself-join, cross join, multi-update, sub-query case來更新了MGR,SALDEPTNO column.
使用update語句更新多項column,必須使用sub-query. MGRdeptnoEMP表中是以id顯示的,當然,我們不會記得id代表什麼.所以,我們必須用self-join,empno連接到mgr,cross join連接到dept 以顯示emp + dept 的各種可能性. 最後用上 2CASE,如果是SALESMANMGR=CLARK否則MGR= BLAKE. 如果DNAMESALESDNAME=OPERATIONS否則DNAME=SALES.



Deletes
在練習中, SQL DELETE 指令比起 insert, update, select 比較少用.然而,對於完整性,讓我們來看看如何在PL/SQL中使用它.

set serveroutput on size 1000000;
declare

-- n_count儲存delete指令的結果
n_count            number;
v_mgr           emp.ename%type := 'KING';
begin

 begin
  delete from emp c1 where c1.ename='ALAN' and c1.mgr=
  (select c2.empno from emp c2 where c2.ename=v_mgr);
   n_count := sql%rowcount;

 exception
  when OTHERS then raise_application_error(-20001, SQLERRM||' on delete WORKERS');
 end;

sys.dbms_output.put_line(to_char(n_count)||' record(s) delete.');
end;
/
最後, 讓我們來看看SELECT指令.



Selects
SQL SELECT語句在PL/SQL最常用的SQL語句,在這次練習中,將討論單SELECT語句的應該返回一行的結果.多數的PL/SQL stored procedures你會寫一個或多個的select語句.所以你可以使用PL/SQL. 現在我們回到最初的select例子,如下:
..
begin
  v_ename := 'SALLY';
  v_job     := 'SALESMAN';
  v_mgr    := 'BLAKE';
  n_sal     := 1500;
  n_comm        := 300;
        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

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

紅色的statement用來偵測有沒有出現where內的條件,用果有就count(empno)並儲存到n_count, 如果沒有n_count = 0就進行下一步. 假設如果你需要update table, 但是沒有找到內容或內容多於一個,哪怎麼處理呢?



No Data Found
如之前介紹exception中有2個常用的exception.其中一個使用最多的就是NO_DATA_FOUND.當使用SELECT語句找不到WHERE的條件時,便使用NO_DATA_FOUNDexception.例子如下:
..
        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;
         n_selected := sql%rowcount;
        exception
         when NO_DATA_FOUND then
         n_selected := sql%rowcount;
         sys.dbms_output.put_line('Caught raised exception NO_DATA_FOUND.')
         when others then raise_application_error(-20002, SQLERRM||' get mgr number');
        end;



Too Many Rows
假若SELECT內容多於一個則用TOO_MANY_ROWS, 這裡注意,由於我們初學的SELECT PL/SQL只能返回一行結果,所以多於一行是會出現錯誤的.
declare
v_ename emp.ename%type;
v_job       emp.job%type;
 begin
  v_job := 'MANAGER';

   begin
    select ename into v_ename from emp where job=v_job;
   exception
    when others then raise_application_error(-20002, SQLERRM||' select');
   end;

  sys.dbms_output.put_line(v_ename);
end;
/

declare
*
ERROR at line 1:
ORA-20002: ORA-01422: exact fetch returns more than requested number of rows select
ORA-06512: at line 12
-- exception加入TOO_MANY_ROWS作處理   
when TOO_MANY_ROWS then sys.dbms_output.put_line('Database found more than one record');
v_ename := NULL;

結果:
Database found more than one record

PL/SQL procedure successfully completed.



以下是一個糟糕的Loop方案來用single row方式顯示select語句的多行結果
declare
 v_ename emp.ename%type;
 n_empno emp.empno%type;

function job(
 a1_num in out         emp.empno%type,
 a2_job in         emp.job%type)
return emp.ename%type as
v_ename   emp.ename%type;

begin
 select empno, ename into a1_num,v_ename from emp where empno > a1_num and job like a2_job and rownum=1;
 return v_ename;
exception
 when no_data_found then return v_ename;
 when others then raise_application_error(-20001, SQLERRM);
end job;

begin
 n_empno := 0;
loop
 v_ename := job(n_empno,'MANAGER');
if v_ename is null then exit;
end if;
sys.dbms_output.put_line(v_ename);
end loop;
end;
/

結果:
JONES
BLAKE
CLARK

PL/SQL procedure successfully completed.

總結,目前為止,應該明白到的是,一個singleton只能返回一個行的SQL語句的結果.



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;
/