Oracle數據庫之動態SQL

Oracle數據庫之動態SQL

1. 靜態SQLSQL與動態SQL

Oracle編譯PL/SQL程序塊分爲兩個種:一種爲前期聯編(early binding),即SQL語句在程序編譯期間就已經確定,大多數的編譯情況屬於這種類型;另外一種是後期聯編(late binding),即SQL語句只有在運行階段才能建立,例如當查詢條件爲用戶輸入時,那麼Oracle的SQL引擎就無法在編譯期對該程序語句進行確定,只能在用戶輸入一定的查詢條件後才能提交給SQL引擎進行處理。通常,靜態SQL採用前一種編譯方式,而動態SQL採用後一種編譯方式。

所謂靜態SQL指在PL/SQL塊中使用的SQL語句在編譯時是明確的,執行的是確定對象。而動態SQL是指在PL/SQL塊編譯時SQL語句是不確定的,如根據用戶輸入的參數的不同而執行不同的操作。編譯程序對動態語句部分不進行處理,只是在程序運行時動態地創建語句、對語句進行語法分析並執行該語句。

2. 動態SQL介紹

Oracle數據庫有兩種動態SQL技術:使用DBMS_SQL包和本地動態SQL。本地動態SQL是在Oracle 8i之後引入的一種新的執行動態SQL的機制,與使用DBMS_SQL包相比,它更簡單、運行速度更快、性能更高,所以DBMS_SQL包的方法已經很少用了。下面主要介紹的是本地動態SQL。

動態SQL語句是在運行時由程序創建的字符串,它們必須是有效的SQL語句或PL/SQL塊,也可以包含用於數據綁定的佔位符。佔位符是未聲明的標識符,名稱並不重要,只需以冒號開頭。如:

'DELETE FROM dept WHERE id = :1 RETURNING loc INTO :2'

'SELECT name, salary FROM employee WHERE salary > :s'

一般在下列的情況下我們才需要使用動態SQL:

  1. 在PL/SQL塊中執行數據定義語句,數據控制語句或會話控制語句(如ALTER SESSION),因爲在PL/SQL中,這樣的語句是不允許靜態執行的。
  2. 爲了獲取更多的靈活性。例如,想在運行時根據實際需求來爲SELECT語句的WHERE子句選擇不同的schema對象。
  3. 動態地使用包DBMS_SQL執行SQL語句,但是爲了獲得更好的性能,或是DBMS_SQL不支持的功能。

通常有三種執行不同類型的動態SQL方法:

  1. 使用EXECUTE IMMEDIATE語句。
    除不能處理多行查詢語句,其他的動態SQL包括DDL語句,DCL語句以及單行的SELECT查詢都可以。
  2. REF CURSOR動態遊標,使用OPEN-FOR,FETCH,CLOSE。
    能處理動態的多行查詢操作,必須要使用OPEN-FOR語句打開遊標,使用FETCH語句循環提取數據,最終使用CLOSE語句關閉遊標。
  3. 使用批量BULK COLLECT執行動態SQL。
    通過使用批量動態SQL語句,可以加快SQL語句處理,進而提高PL/SQL的性能。

3. 使用EXECUTE IMMEDIATE語句

語法:

EXECUTE IMMEDIATE dynamic_sql_stmt
  [ { into_clause | bulk_collect_into_clause } [ using_clause ]
  | using_clause [ dynamic_returning_clause ]
  | dynamic_returning_clause
  ] ;

說明:

dynamic_sql_stmt:是代表一條SQL語句或一個PL/SQL塊的字符串表達式。

into_clause:用於存放被選出的字段值的變量或被選出的行記錄。格式如:

INTO { variable [, variable ]... | record )

using_clause:SQL或PL/SQL字符串中包括用於參數綁定的佔位符時,該子句爲佔位符綁定值,也可用於返回值。輸入bind_argument參數是一個表達式,它的值將被輸入(IN模式)或輸出(OUT模式)或輸入輸出(IN OUT模式)到動態SQL語句或是PL/SQL塊中。一個輸出bind_argument參數就是一個能保存動態SQL返回值的變量。格式如:

USING [ IN | OUT | IN OUT ] bind_argument
  [ [,] [ [ IN | OUT | IN OUT ] bind_argument ]...

dynamic_returning_clause:指明用於存放返回值的變量或記錄。格式如:

{ RETURNING | RETURN } { into_clause | bulk_collect_into_clause }

注意:

  1. 可以把所有的綁定參數放到USING子句中,默認的參數模式是IN。對於含有RETURNING子句的DML語句來說,我們可以把OUT參數放到RETURNING INTO之後,並且不用指定它們的參數模式,因爲默認就是OUT。如果我們既使用了USING又使用RETURNING INTO,那麼,USING子句中就只能包含IN模式的參數了。
  2. 每個佔位符必須與USING子句和/或RETURNING INTO子句中的一個綁定參數對應。
  3. 可以使用數字、字符和字符串作爲綁定參數,但不能使用布爾類型(TRUE,FALSE和NULL)。
  4. 動態SQL是不支持PL/SQL特有的類型,所以不能使用布爾型或索引表。

示例1:

CREATE OR REPLACE PROCEDURE proc_test
(
    table_name IN VARCHAR2,  -- 表名
    field1 IN VARCHAR2,      -- 字段名1
    datatype1 IN VARCHAR2,   -- 字段類型1
    field2 IN VARCHAR2,      -- 字段名2
    datatype2 IN VARCHAR2    -- 字段類型2
) AS 
    str_sql VARCHAR2(200);
BEGIN
    str_sql := 'CREATE TABLE '||table_name||'('||field1||' '||datatype1||','||field2||' '||datatype2||')';
    EXECUTE IMMEDIATE str_sql;
    EXCEPTION
       WHEN others THEN 
            DBMS_OUTPUT.PUT_LINE(SQLCODE || ' - ' || SQLERRM);
END proc_test; 

示例2,使用USING子句:

CREATE OR REPLACE PROCEDURE proc_insert
(
    id IN NUMBER,
    name IN VARCHAR2
) AS
    str_sql VARCHAR2(200);
BEGIN
    str_sql := 'INSERT INTO dinya_test VALUES (:1,:2)';
    EXECUTE IMMEDIATE str_sql USING id, name;
    EXCEPTION
       WHEN others THEN 
            DBMS_OUTPUT.PUT_LINE(SQLCODE || ' - ' || SQLERRM);
END proc_insert;

示例3,USING向後兼容:

DECLARE
  str_sql VARCHAR2(200);
  v_eid NUMBER(4) := 1;
  v_ename VARCHAR2(20);
  v_address VARCHAR2(100);
  v_salary NUMBER(10, 4) := 5500.0000;
BEGIN
  str_sql := 'UPDATE emp SET salary = :1 WHERE id = :2 RETURNING name, address INTO :3, :4';

  EXECUTE IMMEDIATE str_sql
              USING v_salary, v_eid, OUT v_ename, OUT v_address;

  EXECUTE IMMEDIATE str_sql
              USING v_salary, v_eid
     RETURNING INTO v_ename, v_address;
  ...
END;

當動態INSERT、UPDATE或DELETE語句有一個RETURNING子句時,輸出綁定參數可以放到RETURNING INTO或USING子句的後面。

示例4,使用RETURNING INTO子句:

DECLARE
    p_id NUMBER := 1;
    v_count NUMBER;
    v_string VARCHAR2(200);
BEGIN
    v_string := 'SELECT COUNT(*) FROM table_name t WHERE t.id=:id';
    EXECUTE IMMEDIATE v_string USING p_id RETURNING INTO v_count;  
END;

4. REF CURSOR動態遊標,使用OPEN-FOR,FETCH,CLOSE

語法結構如下:

open-for

using_clause ::=

using

說明:

cursor_variable是一個弱類型(沒有返回類型)的遊標變量。

dynamic_string是字符串表達式,代表一個多行查詢。

在運行時,USING子句中的綁定變量可以替換動態SELECT語句中相對應的佔位符。

示例:

DECLARE
  TYPE empcurtyp IS REF CURSOR;

  emp_cv empcurtyp;
  v_ename VARCHAR2(15);
  v_sal NUMBER := 1000;
BEGIN
  OPEN emp_cv FOR
     'SELECT name, salary FROM employee WHERE salary > :s'
     USING v_sal;

  LOOP
    FETCH emp_cv INTO v_ename, v_sal;
    EXIT WHEN emp_cv%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE('姓名:' || v_ename || ',工資:' || v_sal);    
  END LOOP;

  CLOSE emp_cv;
END;

5. 批量動態SQL

批量綁定能減少PL/SQL和SQL引擎之間的切換,改善性能。批量綁定能讓Oracle把SQL語句中的一個變量與一個集合相綁定。集合類型可以是任何PL/SQL集合類型(索引表、嵌套表或變長數組)。但是,集合元素必須是SQL數據類型,如VARCHAR2、DATE或NUMBER。有三種語句支持動態批量綁定:EXECUTE IMMEDIATE、FETCH和FORALL。

5.1 EXECUTE IMMEDIATE批量

示例1:

DECLARE 
   TYPE ename_table_type IS TABLE OF employee.name%TYPE INDEX BY BINARY_INTEGER;
   ename_table ENAME_TABLE_TYPE;
   v_sql  VARCHAR2(100);
BEGIN 
   v_sql := 'SELECT name FROM employee WHERE did = :v_deptno';
   EXECUTE IMMEDIATE v_sql BULK COLLECT INTO ename_table USING &v_deptno;

   FOR i IN ename_table.FIRST .. ename_table.LAST LOOP
     dbms_output.put_line(ename_table(i));
   END LOOP;
END;

示例2:

DECLARE
    TYPE ename_table_type IS TABLE OF employee.name%TYPE INDEX BY BINARY_INTEGER;
    TYPE sal_table_type IS TABLE OF employee.salary%TYPE INDEX BY BINARY_INTEGER;
    ename_table   ENAME_TABLE_TYPE;
    sal_table     SAL_TABLE_TYPE;
    v_sql         VARCHAR2(200);
BEGIN
    v_sql := 'UPDATE employee SET salary = salary*(1 + :percent / 100)' ||
                ' where did=:dno' ||
                ' RETURING ename,sal INTO :name,:salary';
    EXECUTE IMMEDIATE v_sql
      USING &percent,&dno
      RETURNING BULK COLLECT INTO ename_table, sal_table;

    FOR i IN ename_table.FIRST .. ename_table.LAST LOOP
      DBMS_OUTPUT.PUT_LINE('姓名:' || ename_table(i) || ',新工資:' || sal_table(i));
    END LOOP;
END;

5.2 FETCH批量

示例:

DECLARE
  TYPE emp_cur_type IS REF CURSOR;
  TYPE num_list IS TABLE OF NUMBER;
  TYPE name_list IS TABLE OF VARCHAR2(50);

  emp_cur  emp_cur_type;
  emp_nums num_list;
  enames   name_list;
  sals     num_list;
BEGIN
  OPEN emp_cur FOR 'SELECT id, name FROM employee';
  FETCH emp_cur
  BULK COLLECT INTO emp_nums, enames;
  CLOSE emp_cur;

  EXECUTE IMMEDIATE 'SELECT salary FROM employee'
  BULK COLLECT INTO sals;
END;

5.3 FORALL批量

示例:

DECLARE
  TYPE num_list IS TABLE OF NUMBER;
  TYPE name_list IS TABLE OF VARCHAR2(50);
  emp_nums num_list;
  enames name_list;
BEGIN
  emp_nums := num_list(1, 2, 3, 4, 5);
  FORALL i IN emp_nums.FIRST .. emp_nums.LAST
    EXECUTE IMMEDIATE 'UPDATE employee SET salary = salary * 1.1 WHERE id = :1 ' ||
                      'RETURNING name INTO :2'
       USING emp_nums(i)
       RETURNING BULK COLLECT INTO enames;
  ...
END;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章