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:
- 在PL/SQL塊中執行數據定義語句,數據控制語句或會話控制語句(如ALTER SESSION),因爲在PL/SQL中,這樣的語句是不允許靜態執行的。
- 爲了獲取更多的靈活性。例如,想在運行時根據實際需求來爲SELECT語句的WHERE子句選擇不同的schema對象。
- 動態地使用包DBMS_SQL執行SQL語句,但是爲了獲得更好的性能,或是DBMS_SQL不支持的功能。
通常有三種執行不同類型的動態SQL方法:
- 使用EXECUTE IMMEDIATE語句。
除不能處理多行查詢語句,其他的動態SQL包括DDL語句,DCL語句以及單行的SELECT查詢都可以。 - REF CURSOR動態遊標,使用OPEN-FOR,FETCH,CLOSE。
能處理動態的多行查詢操作,必須要使用OPEN-FOR語句打開遊標,使用FETCH語句循環提取數據,最終使用CLOSE語句關閉遊標。 - 使用批量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 }
注意:
- 可以把所有的綁定參數放到USING子句中,默認的參數模式是IN。對於含有RETURNING子句的DML語句來說,我們可以把OUT參數放到RETURNING INTO之後,並且不用指定它們的參數模式,因爲默認就是OUT。如果我們既使用了USING又使用RETURNING INTO,那麼,USING子句中就只能包含IN模式的參數了。
- 每個佔位符必須與USING子句和/或RETURNING INTO子句中的一個綁定參數對應。
- 可以使用數字、字符和字符串作爲綁定參數,但不能使用布爾類型(TRUE,FALSE和NULL)。
- 動態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
語法結構如下:
using_clause ::=
說明:
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;