天天看點

PL/SQL --> 動态SQL的常見錯誤

--============================

-- PL/SQL --> 動态SQL的常見錯誤

    動态SQL在使用時,有很多需要注意的地方,如動态SQL語句結尾處不能使用分号(;),而動态PL/SQL結尾處需要使用分号(;),但不能使用正

斜杠結尾(/),以及shcema對象不能直接作為變量綁定。本文介紹了動态SQL的常見問題。

一、示範動态SQL的使用

    下面的示例中,首先使用動态SQL基于scott.emp建立表tb2,然後裡直接使用動态SQL從新表中擷取記錄數并輸出。再接下來是定義了一個動

    态PL/SQL代碼并執行以擷取目前的系統時間,最後使用動态SQL對新表進行更新。

       DECLARE                               --定義變量以及給變量設定初始值

         sql_stmt         VARCHAR2(100);

         plsql_block      VARCHAR2(300);

         v_deptno         NUMBER := 30;

         v_count          NUMBER;

         v_new_sal        VARCHAR2(5);

         v_empno          NUMBER := 7900;

       BEGIN

         sql_stmt := 'CREATE TABLE tb_emp ' ||        --為變量指派,生成動态SQL語句

                    'AS SELECT * FROM scott.emp WHERE deptno = ' || v_deptno;

         EXECUTE IMMEDIATE sql_stmt;                  --執行動态SQL語句

         EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM tb_emp' --直接使用EXECUTE IMMEDIATE後跟動态SQL串獲得新表的記錄數

           INTO v_count;

         DBMS_OUTPUT.PUT_LINE('The employee count is : ' || v_count);

         plsql_block := 'DECLARE ' ||             --聲明一個PL/SQL塊,存放到變量plsql_block中

                      ' v_date DATE; ' ||

                      'BEGIN ' ||

                      ' SELECT SYSDATE INTO v_date FROM DUAL; ' ||

                      ' DBMS_OUTPUT.PUT_LINE (TO_CHAR(v_date,''DD-MON-YYYY''));' ||

                      'END;';

         EXECUTE IMMEDIATE plsql_block;           --執行動态的PL/SQL塊

         sql_stmt := 'UPDATE tb_emp SET sal = sal + 100 WHERE empno =:eno ' ||  --更新新表的一條記錄

                    'RETURNING sal INTO :sal';                         --動态SQL語句中包含RETURNING子句傳回更新後的結果

         EXECUTE IMMEDIATE sql_stmt               --執行動态SQL塊

           USING v_empno

           RETURNING INTO v_new_sal;              --使用RETURNING子句将結果存放到變量v_new_sal中

         DBMS_OUTPUT.PUT_LINE('New salary is: ' || v_new_sal);

       END;

       The employee count is : 6

       04-JAN-2011

       New salary is: 1050

二、動态SQL的常見錯誤  

    1.使用動态DDL時,不能使用綁定變量

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

       DECLARE

         v_deptno         VARCHAR2(5) := '30';

         sql_stmt := 'CREATE TABLE tb_tmp ' || 'AS SELECT * FROM scott.emp ' ||

                    'WHERE deptno = :dno';

         EXECUTE IMMEDIATE sql_stmt

           USING v_deptno;

         EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM tb_tmp'

         DBMS_OUTPUT.PUT_LINE('The temp table count is  ' || v_count);

       *

       ERROR at line 1:

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

       ORA-06512: at line 8

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

           sql_stmt := 'CREATE TABLE tb_tmp ' || 'AS SELECT * FROM scott.emp ' || 'WHERE deptno = ' || v_deptno;

    2.不能使用schema對象作為綁定參數

       下面的示例中,動态SQL語句查詢需要傳遞表名,是以收到了錯誤提示。

         sql_stmt VARCHAR2(100);

         v_count  NUMBER;

         EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM :tb_name'

         DBMS_OUTPUT.PUT_LINE('The table record  is  ' || v_count);

       ORA-00903: invalid table name

       ORA-06512: at line 5

       處理辦法

           DECLARE

             sql_stmt VARCHAR2(100);

             v_tablename VARCHAR2(30) :='scott.emp';   --增加一個變量并指派

             v_count  NUMBER;

           BEGIN

             EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM ' || v_tablename   --使用|| 連接配接變量

              INTO v_count;

             DBMS_OUTPUT.PUT_LINE('The table record is  ' || v_count);

           END;

           The temp table count is  14

    3.動态SQL塊不能使用分号結束(;)

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

         --v_tablename VARCHAR2(30) :='scott.emp';

         EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM tb_emp;'   --此處多出了分号,應該去掉

       ORA-00911: invalid character

       ORA-06512: at line 6

           去掉動态SQL語句末尾的分号

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

         plsql_block VARCHAR2(300);

         plsql_block := 'DECLARE ' ||

                      ' BEGIN ' ||

                        ' SELECT SYSDATE INTO v_date FROM DUAL; ' ||

                        ' DBMS_OUTPUT.PUT_LINE (TO_CHAR(v_date,''YYYY-MM-DD''));' ||

                      'END;

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

         EXECUTE IMMEDIATE plsql_block;

       ORA-06550: line 3, column 2:

       PLS-00103: Encountered the symbol "/" The symbol "/" was ignored.

       ORA-06512: at line 13

           去掉動态PL/SQL 塊尾部的斜杠

    5.空值傳遞的問題

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

         v_empno  NUMBER := 7900;

         sql_stmt := 'UPDATE tb_emp SET sal = :new_sal WHERE empno=:eno';

           USING NULL,v_empno;     --此處不能直接使用NULL

           USING NULL,v_empno;

                *

       ERROR at line 7:

       ORA-06550: line 7, column 11:

       PLS-00457: expressions have to be of SQL types

       ORA-06550: line 6, column 3:

       PL/SQL: Statement ignored

       正确的處理辦法

             v_empno  NUMBER := 7900;

             v_sal    NUMBER;    --聲明一個新變量,但不指派

             sql_stmt := 'UPDATE tb_emp SET sal = :new_sal WHERE empno=:eno';

             EXECUTE IMMEDIATE sql_stmt

              USING v_sal, v_empno;

             COMMIT;

             DBMS_OUTPUT.PUT_LINE('The new sal is NULL');

    6.傳遞參數時順序不正确的問題

       使用USING傳遞參數到動态SQL或使用INTO子句傳遞結果集到變量應注意按正确的順序排列處理

       下面的示例中由于v_ename與v_sal為不同的資料類型,在使用INTO時不小心将順序颠倒,導緻錯誤産生。當然,如果資料類型相同,

       且不會存在溢出的情況下将沒有錯誤提示。

             TYPE emp_cur_type IS REF CURSOR;

             emp_cv   emp_cur_type;

             sql_stat VARCHAR2(100);

             v_dno    NUMBER := &dno;

             v_ename  VARCHAR2(25);

             v_sal    NUMBER;

             sql_stat := 'SELECT ename,sal FROM scott.emp WHERE deptno = :dno';

             OPEN emp_cv FOR sql_stat     --使用遊标來處理動态SQL

              USING v_dno;

             LOOP

              FETCH emp_cv

                INTO v_sal, v_ename;     --從結果集中提取記錄時,順序發生颠倒

              EXIT WHEN emp_cv%NOTFOUND;

              dbms_output.put_line('Employee name is :' || v_ename ||

                                 ',  The sal is :' || v_sal);

             END LOOP;

             CLOSE emp_cv;

           Enter value for dno: 20

           old   5:   v_dno    NUMBER := &dno;

           new   5:   v_dno    NUMBER := 20;

           *

           ERROR at line 1:

           ORA-01722: invalid number

           ORA-06512: at line 14

           更正參數變量的順序

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

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

         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=:v_date';    --使用了占位符:v_date進行變量綁定

         EXECUTE IMMEDIATE sql_stat

         INTO v_ename,v_sal

         USING v_date;

         DBMS_OUTPUT.PUT_LINE('Employee Name '||v_ename||', sal is '||v_sal);

       Enter value for dt: 1981-05-01              --執行時,輸入的字串中也未使用引号,此時收到錯誤提示

       old   3:   v_date    DATE :=&dt;

       new   3:   v_date    DATE :=1981-05-01;

         v_date    DATE :=1981-05-01;

                        *

       ERROR at line 3:

       ORA-06550: line 3, column 20:

       PLS-00382: expression is of wrong type

       ORA-06550: line 3, column 13:

       PL/SQL: Item ignored

       ORA-06550: line 13, column 9:

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

       ORA-06550: line 11, column 3:

       處理辦法一

           執行時輸入帶引号的字串

           flasher@ORCL> /

           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

           PL/SQL procedure successfully completed.

       處理辦法二

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

           v_date    DATE :='&dt';

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

           v_date    DATE :=TO_DATE('&dt','DD-MON-RR');

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

         v_date    DATE :='&dt';

         sql_stat := 'SELECT ename,sal FROM tb_emp WHERE hiredate=' || chr(39) ||v_date|| chr(39);--chr(39)代表單引号

         INTO v_ename,v_sal;

       Enter value for dt: 1981-05-01

       old   3:   v_date    DATE :='&dt';

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

       SELECT ename,sal FROM tb_emp WHERE hiredate='1981-05-01'

       Employee Name BLAKE, sal is 2850

       PL/SQL procedure successfully completed.

    8.單行SELECT 查詢不能使用RETURNING INTO傳回

       下面的示例中,使用了動态的單行SELECT查詢,并且使用了RETURNING子句來傳回值。事實上,RETURNING coloumn_name INTO 子句僅

       僅支援對DML結果集的傳回,是以,收到了錯誤提示。

         sql_stat VARCHAR2(200);

         v_empno  tb2.empno%TYPE := &eno;

         v_ename  tb2.ename%TYPE;

         sql_stat := 'SELECT ename FROM tb2 WHERE empno =:eno

                      RETURNING ename INTO :v_ename ';

           RETURNING INTO v_ename;

         DBMS_OUTPUT.PUT_LINE('The employee name is ' || v_ename);

           去掉動态SQL語句中的RETURNING coloumn_name INTO子句,在執行EXECUTE IMMEDIATE時,直接使用INTO子句來傳遞值。

         sql_stat := 'SELECT ename FROM tb2 WHERE empno =:eno';

                --       RETURNING ename INTO :v_ename ';     --去掉RETURNING子句

           INTO v_ename                                        --增加INTO子句來傳回變量值

           USING v_empno;

           --RETURNING INTO v_ename;                           --去掉RETURNING子句

三、總結

    1.使用動态DDL時,不能使用綁定變量。應該将綁定變量與原動态SQL使用連接配接符進行連接配接。

    2.不能使用schema對象作為綁定參數,将schema對象與原動态SQL使用連接配接符進行連接配接。

    3.動态SQL塊不能使用分号結束(;)。

    4.動态PL/SQL塊不能使用正斜杠來結束塊,但是塊結尾處必須要使用分号(;)。

    5.空值傳遞的時候,不能直接使用USING NULL子句,應當聲明變量,使用變量傳遞,當未給變量指派時,即為空值。

    6.參數的傳入傳出應保證順序的正确,以及防止資料溢出的問題。

    7.日期型或字元型在動态SQL中處理時,需要注意單引号個數的問題,特殊情況下可以使用chr(39)作為單引号使用。

    8.動态SQL中RETURNING INTO傳回DML操作的結果,對于SELECT查詢傳回的結果,在執行EXECUTE IMMEDIATE時,直接使用INTO子句來傳遞。

四、更多參考

有關SQL請參考

<a href="http://blog.csdn.net/robinson_0612/archive/2010/05/05/5559110.aspx">SQL基礎--&gt;分組與分組函數</a>

<a href="http://blog.csdn.net/robinson_0612/archive/2010/04/16/5495170.aspx">SQL 基礎--&gt;常用函數</a>

<a href="http://blog.csdn.net/robinson_0612/archive/2010/06/01/5640031.aspx">SQL 基礎--&gt; ROLLUP與CUBE運算符實作資料彙總</a>

<a href="http://blog.csdn.net/robinson_0612/archive/2010/05/22/5616877.aspx">SQL基礎--&gt;階層化查詢(START BY ... CONNECT BY PRIOR)</a>

    有關PL/SQL請參考

<a href="http://blog.csdn.net/robinson_0612/archive/2010/12/08/6063422.aspx">PL/SQL --&gt; 流程控制</a>

<a href="http://blog.csdn.net/robinson_0612/archive/2010/12/08/6063434.aspx">PL/SQL --&gt; 存儲過程</a>

<a href="http://blog.csdn.net/robinson_0612/archive/2010/12/11/6069531.aspx">PL/SQL --&gt; 函數</a>

<a href="http://blog.csdn.net/robinson_0612/archive/2010/12/15/6078622.aspx">PL/SQL --&gt; 遊标</a>

<a href="http://blog.csdn.net/robinson_0612/archive/2010/12/22/6092066.aspx">PL/SQL --&gt;隐式遊标(SQL%FOUND)</a>

<a href="http://blog.csdn.net/robinson_0612/archive/2010/12/16/6080119.aspx">PL/SQL --&gt; 異常處理(Exception)</a>

<a href="http://blog.csdn.net/robinson_0612/archive/2010/12/18/6084390.aspx">PL/SQL --&gt; PL/SQL記錄</a>

<a href="http://blog.csdn.net/robinson_0612/archive/2010/12/18/6084475.aspx">PL/SQL --&gt; 包的建立與管理</a>

<a href="http://blog.csdn.net/robinson_0612/archive/2010/12/22/6092097.aspx">PL/SQL --&gt; 包重載、初始化</a>

<a href="http://blog.csdn.net/robinson_0612/archive/2010/12/25/6098248.aspx">PL/SQL --&gt; DBMS_DDL包的使用</a>

<a href="http://blog.csdn.net/robinson_0612/archive/2010/12/25/6098258.aspx">PL/SQL --&gt; DML 觸發器</a>

<a href="http://blog.csdn.net/robinson_0612/archive/2010/12/25/6098263.aspx">PL/SQL --&gt; INSTEAD OF 觸發器</a>

<a href="http://blog.csdn.net/robinson_0612/archive/2011/01/05/6118010.aspx">PL/SQL --&gt; 動态SQL</a>