天天看點

PL/SQL動态SQL(原創)

概述

使用動态sql是在編寫pl/sql過程時經常使用的方法之一。很多情況下,比如根據業務的需要,如果輸入不同查詢條件,則生成不同的執行sql查詢語句,對于這種情況需要使用動态sql來完成。再比如,對于分頁的情況,對于不同的表,必定存在不同的字段,是以使用靜态sql則隻能針對某幾個特定的表來形成分頁。而使用動态的sql,則可以對不同的表,不同的字段進行不同的分頁。這些情況的處理通常都是用動态sql來完成。

動态sql和靜态sql

靜态sql

靜态sql通常用于完成可以确定的任務。比如傳遞部門号調用存儲過程,傳回該部門的所有雇員及薪水資訊,則該語句為

select ename,sal into lv_ename,lv_sal from scott.emp where deptno=&dno;

對于上述類似的dml語句在第一次運作時進行編譯,而後續再次調用,則不再編譯該過程。即一次編譯,多次調用,使用的相同的執行計劃。此種方式被稱之為使用的是靜态的sql。

動态sql

動态sql通常是用來根據不同的需求完成不同的任務。比如分頁查詢,對于表emp分頁,需要使用字段雇員姓名,薪水,雇用日期,且按薪水降序生成報表,每頁顯示行資料。而對于表sales,需要使用字段雇員名稱,客戶名稱,銷售數量,銷售日期,且按銷售日期升序排列。以上兩種情況,可以建立存儲過程來對其進行分頁,通過定義變量,根據輸入不同的表名,字段名,排序方法來生成不同的sql語句。對于輸入不同的參數,sql在每次運作時需要事先對其編譯。即多次調用則需要多次編譯,此稱之為動态sql。動态sql語句通常存放在字元串變量中,且sql語句可以包含占位符(使用冒号開頭)。也可以直接将動态sql緊跟在execute immediate語句之後,如

execute immediate 'alter table emp enable row movement'

兩者的異同

靜态sql為直接嵌入到pl/sql中的代碼,而動态sql在運作時,根據不同的情況産生不同的sql語句。

靜态sql為在執行前編譯,一次編譯,多次運作。動态sql同樣在執行前編譯,但每次執行需要重新編譯。

靜态sql可以使用相同的執行計劃,對于确定的任務而言,靜态sql更具有高效性。但缺乏靈活性動态sql使用了不同的執行計劃,效率不如靜态sql,但能夠解決複雜的問題。

動态sql語句的幾種方法

a.使用execute immediate語句

包括ddl語句,dcl語句,dml語句以及單行的select 語句。該方法不能用于處理多行查詢語句。

b.使用open-for,fetch和close語句

對于處理動态多行的查詢操作,可以使用open-for語句打開遊标,使用fetch語句循環提取資料,最終使用close語句關閉遊标。

c.使用批量動态sql

即在動态sql中使用bulk子句,或使用遊标變量時在fetch中使用bulk ,或在forall語句中使用bulk子句來實作。

d.使用系統提供的pl/sql包dbms_sql來實作動态sql(該方式本文中不做介紹)。   

動态sql的文法

下面是動态sql常用的文法之一

execute immediate dynamic_sql_string

[into defined_variable1, defined_variable2, ...]

[using [in | out | in out] bind_argument1, bind_argument2,

...][{returning | return} field1, field2, ... into bind_argument1,

bind_argument2, ...]

文法描述

dynamic_sql_string:存放指定的sql語句或pl/sql塊的字元串變量

defined_variable1:用于存放單行查詢結果,使用時必須使用into關鍵字,類似于使用

select ename into v_name from scott.emp;

隻不過在動态sql時,将into defined_variable1移出到dynamic_sql_string語句之外。

bind_argument1:用于給動态sql語句傳入或傳出參數,使用時必須使用using關鍵字,in表示傳入的參數,out表示傳出的參數,in out則既可以傳入,也可傳出。returning | return 子句也是存放sql動态傳回值的變量。

使用要點

a.execute immediate執行dml時,不會送出該dml事務,需要使用顯示送出(commit)或作為execute immediate自身的一部分。

b.execute immediate執行ddl,dcl時會自動送出其執行的事務。

c.對于多行結果集的查詢,需要使用遊标變量或批量動态sql,或者使用臨時表來實作。

d.當執行sql時,其尾部不需要使用分号,當執行pl/sql 代碼時,其尾部需要使用分号。

f.動态sql中的占位符以冒号開頭,緊跟任意字母或數字表示。

動态sql的使用(ddl,dcl,dml以及單行結果集)

例:使用execute immediate處理ddl操作。在存儲過程之中封裝一簡單的ddl語句,通過傳入表名來進行調用。     

sql> create table tb2 as select * from emp;

table created.

sql> create or replace procedure trunc_table(table_name varchar2)

  2  as

  3  sql_statement varchar2(100);

  4  begin

  5    sql_statement := 'truncate table ' || table_name;     --為變量進行指派,用于生成動态sql語句

  6    execute immediate sql_statement;    --使用execute immediate執行動态sql語句

  7  end;

  8  /

procedure created.

sql> select count(1) from tb2;

  count(1)

----------

        14

sql> exec trunc_table('tb2');

pl/sql procedure successfully completed.

例:使用execute immediate處理dcl操作,下面使用sys帳戶建立存儲過程grant_sys_priv用于給使用者授予權限

sql> conn sys/redhat@orcl as sysdba

sql>create or replace procedure grant_sys_priv(priv varchar2, username varchar2)

is

    sql_stat varchar2(100);

begin

    sql_stat := 'grant ' || priv || ' to ' || username; 

    execute immediate sql_stat;

end;

/  

sql> exec grant_sys_priv('connect','usr1');

例:使用execute immediate處理沒有參數傳入傳出的dml語句

sql> select * from tb2 where empno = 7900;

     empno ename           sal

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

      7900    james           950

sql> declare

  2    sql_stat varchar2(100);

  3  begin

  4    sql_stat := 'delete from scott.tb2 where empno = 7900';

  5    execute immediate sql_stat;

  6  end;

  7  /

no rows selected

例:有參數傳入的dml語句(使用using子句),對于使用了參數傳入的動态sql,需要使用using子句來指明傳入的參數。在下面的示例中,為表tb2插入一條記錄,在dml語句中使用了四個占位符(占位符用以冒号開頭,緊跟任意字母或數字表示)。是以在使用execute immediate使用using子句為其指定其參數。

sql>   declare

  3    lv_empno tb2.empno%type :=7900;

  4    lv_ename tb2.ename%type :='james';

  5    lv_sal   tb2.sal%type :=950;

  6  begin 

  7    sql_stat := 'insert into tb2 values(:1,:2,:3)';   --dml語句中使用了占位符

  8    execute immediate sql_stat using lv_empno,lv_ename,lv_sal;      --為占位符指定參數或值

  9    commit;

 10* end;

sql> select * from tb2 where empno=7900;

  empno   ename           sal

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

      7900   james             950

例:處理包含returning子句的dml語句。下面的示例中,對表tb2進行更新,使用了兩個占位符,一個是:percent,一個是:eno,是以在使用execute immediate執行動态。dml時,需要使用using子句且帶兩個輸入參數。其次,動态dml中使用了returning sal into :salary,是以execute immediate後也必須使用returning into varialbe_name。

sql>  declare

  2     sql_stat varchar2(100);

  3     v_sal tb2.sal%type;

  4   begin

  5     sql_stat :='update tb2 set sal = sal * (1 + :percent/100)'

  6     || ' where empno = :eno returning sal into :v_sal';    --使用了占位符:eno,:salary,以及returning子句

  7     execute immediate sql_stat using &1,&2 returning into v_sal;   --必須使用using及returning子句

  8     commit;

  9     dbms_output.put_line('new salary: ' || v_sal);

 10   end;

 11  /

enter value for 1: 10

enter value for 2: 7900

old   7:    execute immediate sql_stat using &1,&2 returning into v_sal;

new   7:    execute immediate sql_stat using 10,7900 returning into v_sal;

new salary: 1045

例:處理包含檢索值的單行查詢。下面的示例中,使用select 查詢獲得單行結果集,使用了占位符:name,是以也需要使用using子句為其傳遞參數

  3    emp_record tb2%rowtype;

  5    sql_stat := 'select * from tb2 where ename = upper(:name)';

  6    execute immediate sql_stat into emp_record using '&name';

  7    dbms_output.put_line('the salary is ' || emp_record.sal || ' for ' || emp_record.ename);

  8  end;

  9  /

enter value for name: james

old   6:   execute immediate sql_stat into emp_record using '&name';

new   6:   execute immediate sql_stat into emp_record using 'james';

the salary is 1045 for james

遊标和動态sql處理多行結果集的查詢語句

其主要流程為

1.定義遊标變量

type cursortype is ref cursor;

cursor_variable cursortype;

2.打開遊标變量

open cursor_variable for dynamic_string

[using bind_argument[,bind_argument]...]

3.循環提取資料

fetch cursor_variable into {var1[,var2]...| record_variable};

exit when cursor_variable%notfound

4.關閉遊标變量

close cursor_variable;

使用遊标變量處理查詢多行結果集

下面的示例中,首先定義了一個遊标類型,接下來定義遊标變量,以及存放結果集的變量,動态查詢語句将獲得多個結果集。

open cursorname for select ... 時,其select 語句使用了字元串變量(動态sql),其後緊跟using子句。

  2    type emp_cur_type is ref cursor;

  3    emp_cv  emp_cur_type;

  4    emp_record tb2%rowtype;

  5    sql_stat varchar2(100);

  6    v_empno tb2.empno%type := &inputno;

  7  begin

  8    sql_stat := 'select * from tb2 where empno = :no';   --動态多行結果集查詢語句

  9    open emp_cv for sql_stat using v_empno;   --open時使用動态查詢語句以及using子句來傳遞參數

 10    loop

 11      fetch emp_cv into emp_record;     --從結果集中提取記錄

 12      exit when emp_cv%notfound;

 13      dbms_output.put_line('employee name: ' || emp_record.ename || ' ,salary: ' || emp_record.sal);

 14    end loop;

 15* end;

enter value for inputno: 7900

old   6:   v_empno tb2.empno%type := &inputno;

new   6:   v_empno tb2.empno%type := 7900;

employee name: james ,salary: 1045

bulk子句和動态sql的使用

動态sql中使用bulk子句的文法

execute immediate dynamic_string                          --dynamic_string用于存放動态sql字元串

[bulk collect into define_variable[,define_variable...]]        --存放查詢結果的集合變量

[using bind_argument[,argument...]]                            --使用參數傳遞給動态sql

[{returning | return}                                          --傳回子句

bulk collect into return_variable[,return_variable...]];     --存放傳回結果的集合變量

使用bulk collect into子句處理動态sql中t的多行查詢可以加快處理速度,進而提高應用程式的性能。當使用bulk子句時,集合類型可以是pl/sql所支援的索引表、嵌套表和varry,但集合元 素必須使用sql資料類型。常用的三種語句支援bulk子句,分别為execute immediate,   fetch 和forall。

下面的例子,首先定義了兩個表類型以及其變量,接下來使用動态sql語句來更新tb2的薪水,使用execute immediate配合bulk collec into 來處理結果集。

  2    type ename_table_type is table of tb2.ename%type index by binary_integer;

  3    type sal_table_type is table of tb2.sal%type index by binary_integer;

  4       ename_table ename_table_type;

  5       sal_table sal_table_type;

  6       sql_stat varchar2(200);

  7       v_percent number := &percent;

  8       v_dno     number := &dno;

  9  begin

 10    sql_stat := 'update tb2 set sal = sal * (1 + :percent/100)'

 11          || ' where deptno = :dno'

 12          || ' returning ename,sal into :name,:salary';

 13    execute immediate sql_stat using v_percent,v_dno

 14    returning bulk collect into ename_table,sal_table;

 15    for i in 1..ename_table.count loop

 16      dbms_output.put_line('employee ' || ename_table(i) || ' salary is: ' || sal_table(i));

 17    end loop;

 18* end;

enter value for percent: 10

old   7:      v_percent number := &percent;

new   7:      v_percent number := 10;

enter value for dno: 20

old   8:      v_dno     number := &dno;

new   8:      v_dno     number := 20;

employee smith salary is: 880

employee jones salary is: 3272.5

employee scott salary is: 3300

employee adams salary is: 1210

employee ford salary is: 3300

使用execute immediate 結合bulk子句處理多行查詢。下面示例中,與前一個示例相同,隻不過其動态sql有查詢語句組成,且傳回多個結果集,同樣使用了bulk collect into來傳遞結果。

sql>declare

  type ename_table_type is table of tb2.ename%type index by binary_integer;

  type sal_table_type is table of tb2.sal%type index by binary_integer;

     ename_table ename_table_type;

     sal_table sal_table_type;

     sql_stat varchar2(100);

     v_dno     number := &dno;

  sql_stat := 'select ename,sal from tb2 where deptno = :v_dno';     --動态dql語句,未使用returning子句

  execute immediate sql_stat bulk collect into ename_table,sal_table using v_dno;

  for i in 1..ename_table.count loop

    dbms_output.put_line('employee ' || ename_table(i) || ' salary is: ' || sal_table(i));

  end loop;

old   7:      v_dno     number := &dno;

new   7:      v_dno     number := 20;

使用fetch子句結合bulk子句處理多行結果集

下面的示例中首先定義了遊标類型,遊标變量以及複合類型,複合變量,接下來從動态sql中open遊标,然後使用fetch将結果存放到複合變量中。即使用open,fetch代替了execute immediate來完成動态sql的執行。

sql> declare

  2    type empcurtype is ref cursor;

  3    emp_cv empcurtype;

  4    type ename_table_type is table of tb2.ename%type index by binary_integer;

  5    ename_table ename_table_type;

  6    sql_stat varchar2(120);

  8    sql_stat := 'select ename from tb2 where deptno = :dno';

  9    open emp_cv for sql_stat using &dno;

 10    fetch emp_cv bulk collect into ename_table;

 11    for i in 1..ename_table.count loop

 12      dbms_output.put_line('employee name: ' || ename_table(i));

 13    end loop;

 14    close emp_cv;

 15*   end;enter value for dno: 20

old   9:   open emp_cv for sql_stat using &dno;

new   9:   open emp_cv for sql_stat using 20;

employee name: smith

employee name: jones

employee name: scott

employee name: adams

employee name: ford

在forall語句中使用bulk子句

下面是forall子句的文法

forall index in lower bound..upper bound           --forall循環計數

execute immediate dynamic_string               --結合execute immediate來執行動态sql語句

using bind_argument | bind_argument(index)     --綁定輸入參數

[bind_argument | bind_argument(index)]...

[{returning | return} bulk collect into bind_argument[,bind_argument...]];  --綁定傳回結果集

forall子句允許為動态sql輸入變量,但forall子句僅支援的dml(insert,delete,update)語句,不支援動态的select語句。

下面的示例中,首先聲明了兩個複合類型以及複合變量,接下來為複合變量ename_table指派,以形成動态sql語句。緊接着使用forall子句結合execute immediate 來提取結果集。

  2    type ename_table_type is table of tb2.ename%type;

  3    type sal_table_type is table of tb2.sal%type;

  4         ename_table ename_table_type;

  5         sal_table sal_table_type;

  6         sql_stat varchar2(100);

  8    ename_table := ename_table_type('blake','ford','miller');

  9    sql_stat := 'update tb2 set sal = sal * 1.1 where ename = :1'

 10            || ' returning sal into :2';

 11    forall i in 1..ename_table.count

 12      execute immediate sql_stat using ename_table(i) returning bulk collect into sal_table;

 13    for j in 1..sal_table.count loop

 14      dbms_output.put_line('the ' || ename_table(j) || '''' || 's new salalry is ' || sal_table(j));

 15    end loop;

 16*   end;

the blake's new salalry is 3448.5

the ford's new salalry is 3993

the miller's new salalry is 1573

常見錯誤

1、使用動态ddl時,不能使用綁定變量。下面的示例中,在建立表示,使用了綁定變量:dno,在執行的時候收到了錯誤資訊。

  3    v_deptno varchar2(5) := '30';

  5    sql_stat := 'create table tb_emp ' || ' as select * from emp '

  6            || ' where deptno = :dno';

  7    execute immediate sql_stat using v_deptno;

declare

*

error at line 1:

ora-01027: bind variables not allowed for data definition operations

ora-06512: at line 7

解決辦法,将綁定變量直接拼接,如下:

sql_stat := 'create table tb_tmp ' || 'as select * from scott.emp ' || 'where deptno = ' || v_deptno;

2、不能使用schema對象作為綁定參數,下面的示例中,動态sql語句查詢需要傳遞表名,是以收到了錯誤提示。

  sql_stat varchar2(100);

  v_tbname varchar2(20);

  v_count number;

  execute immediate 'select count(*) from :tb_name'

  into v_count;

  dbms_output.put_line('the table record is ' || v_count);

execute immediate 'select count(*) from ' || v_tbname into v_count;

3、動态sql塊不能使用分号結束(;)

下面的示例中,動态sql語句使用了分号來結束,收到錯誤提示。

  execute immediate 'select count(*) from emp;'    --此處多出了分号,應該去掉

end;4、動态pl/sql塊不能使用正斜杠來結束塊,但是塊結尾處必須要使用分号(;)

  2    plsql_block varchar2(300);

  4    plsql_block := 'declare ' ||

  5                 ' v_date date; ' ||

  6                 ' begin ' ||

  7                     ' select sysdate into v_date from dual; ' ||

  8                     ' dbms_output.put_line(to_char(v_date,''yyyy-mm-dd'')); ' ||

  9                 ' end;

 10                   /';      --此處多出了/,應該将其去掉

 11    execute immediate plsql_block;

 12* end;declare

ora-06550: line 2, column 18:

pls-00103: encountered the symbol "/" the symbol "/" was ignored.

ora-06512: at line 11

4、空值傳遞的問題

下面的示例中對表tb_emp更新,并将空值更新到sal列,直接使用using null收到錯誤提示。

  2    sql_stat varchar2(200);

  3    v_empno number := 7900;

  5    sql_stat := 'update tb2 set sal = 1.1 * :new_sal where empno = :eno';

  6    execute immediate sql_stat using null,v_empno;   --此處不能直接使用null

  execute immediate sql_stat using null,v_empno;

                                   *

error at line 6:

ora-06550: line 6, column 36:

pls-00457: expressions have to be of sql types

ora-06550: line 6, column 3:

pl/sql: statement ignored

正确的處理方法

  4    v_sal tb2.sal%type;   --聲明一個新變量,但不指派

  5  begin

  6    sql_stat := 'update tb2 set sal = 1.1 * :new_sal where empno = :eno';

  7    execute immediate sql_stat using v_sal,v_empno;

  8    commit;

  9  end;

 10  /

5、日期和字元型必須要使用引号來處理

下面的示例中,使用了日期型變量,未使用引号标注,且使用了變量綁定,但直接輸入日期型資料,而不加引号,則收到錯誤提示。

  2           sql_stat  varchar2(100);

  3           v_date    date :=&dt;

  4           v_empno   number :=7900;

  5           v_ename   tb2.ename%type;

  6           v_sal     tb2.sal%type;

  7         begin

  8           sql_stat := 'select ename,sal from tb2 where hiredate=:v_date';

  9           execute immediate sql_stat

 10           into v_ename,v_sal

 11           using v_date;

 12           dbms_output.put_line('employee name '||v_ename||', sal is '||v_sal);

 13*        end;

 14  /

enter value for dt: 1987-05-23

old   3:          v_date    date :=&dt;

new   3:          v_date    date :=1987-05-23;

         v_date    date :=1987-05-23;

                          *

error at line 3:

ora-06550: line 3, column 27:

pls-00382: expression is of wrong type

ora-06550: line 3, column 20:

pl/sql: item ignored

ora-06550: line 11, column 16:

pls-00320: the declaration of the type of this expression is incomplete or malformed

ora-06550: line 9, column 10:

處理辦法一

執行時輸入帶引号的字串

sql> /

enter value for dt: '1981-05-01'

old   3:   v_date    date :=&dt;

new   3:   v_date    date :='1981-05-01';

employee name blake, sal is 2850

處理辦法二

在聲明變量時指派用引号,如下

v_date    date :='&dt';

如存在字元格式轉換,可以直接使用轉換函數,如

v_date    date :=to_date('&dt','dd-mon-rr');

如果上面的例子中,動态sql語句不使用綁定日期變量,而是将其連接配接成字元串,則可以使用下面的方式來實作

  sql_stat  varchar2(100);

  v_date    date :='&dt';

  v_empno   number :=7900;

  v_ename   tb_emp.ename%type;

  v_sal     tb_emp.sal%type;

  sql_stat := 'select ename,sal from tb_emp where hiredate=' || chr(39) ||v_date|| chr(39);      --chr(39)代表單引号

  execute immediate sql_stat

  into v_ename,v_sal;

  dbms_output.put_line('employee name '||v_ename||', sal is '||v_sal);

6、單行select 查詢不能使用returning into傳回

下面的示例中,使用了動态的單行select查詢,并且使用了returning子句來傳回值。事實上,returning coloumn_name into 子句僅僅支援對dml結果集的傳回,是以,收到了錯誤提示。sql>declare

  3    v_empno  tb2.empno%type := &empno;

  4    v_ename  tb2.ename%type;

  6    sql_stat := ' select ename from tb2 where empno = :eno';

  7    execute immediate sql_stat using v_empno returning ename into v_ename;

  8    dbms_output.put_line('employee name: ' || v_ename);

enter value for empno: 7900

old   3:   v_empno  tb2.empno%type := &empno;

new   3:   v_empno  tb2.empno%type := 7900;

  execute immediate sql_stat using v_empno returning ename into v_ename;

                                                     *

error at line 7:

ora-06550: line 7, column 54:

pls-00103: encountered the symbol "ename" when expecting one of the following:

into bulk

the symbol "ename" was ignored.

上,returning coloumn_name into

解決方式

去掉動态sql語句中的returning coloumn_name into子句,在執行execute immediate時,直接使用into子句來傳遞值。

execute immediate sql_stat into v_ename using v_empno;

參考至:http://blog.csdn.net/robinson_0612/article/details/6118010

              http://blog.csdn.net/robinson_0612/article/details/6118049

本文原創,轉載請注明出處、作者

如有錯誤,歡迎指正

郵箱:[email protected]

作者:czmmiao  文章出處:http://czmmiao.iteye.com/blog/1822979