" 1、SQL 語句分類 " "1.1、分類方法及類型" "1.2、數據定義語言" "1.3、數據操縱語言" "1.4、其它語句" " 2、動態 SQL 理論 " "2.1、動態 SQL 的用途" "2.2、動態 SQL 的語法" "2.3、綁定變數" " 3、動態 SQL 實戰 " "3.1、封裝 ...
1、SQL 語句分類
1.1、分類方法及類型
在 Oracle 的官方手冊《Oracle Database SQL Reference: Types of SQL Statements》中將 SQL 語句分為 6 類,分別是:數據定義語言語句、數據操縱語言語句、事務控制語句、會話控制語句、系統控制語句、嵌入式 SQL 語句。
按照官方的分類方法,數據定義語言的語句是非常豐富的。本人曾在一些非官方資料中看到有人把數據定義語言做了進一步的細分。例如,把 GRANT 和 REVOKE 稱之為數據控制語言,即 DCL(Data Control Language);把 AUDIT、NOAUDIT 稱之為審計控制語言。
1.2、數據定義語言
數據定義語言(DDL,Data Definition Language)語句主要有以下用途:
- 1、創建、修改和刪除模式對象。
- 2、授予或回收用戶許可權或角色。
- 3、分析表、索引或集群的信息。
- 4、打開或關閉審計選項。
- 5、向數據字典中添加註釋。
DDL 語句主要有:
- CREATE,所有以 CREATE 開頭的語句都是。
- ALTER,所有以 ALTER 開頭的語句都是。
- DROP,所有以 DROP 開頭的語句都是。
- FLASHBACK,所有以 FLASHBACK 開頭的語句都是。
- TRUNCATE
- COMMENT
- RENAME
- PURGE
- GRANT
- REVOKE
- AUDIT
- NOAUDIT
- ANALYZE
- ASSOCIATE STATISTICS
- DISASSOCIATE STATISTICS
CREATE、ALTER 及 DROP 命令需要對指定對象的獨占訪問。例如,如果某個用戶在指定的表上具有打開的事務,則其它用戶發出的 ALTER TABLE 語句將失敗。
GRANT、REVOKE、ANALYZE、AUDIT 及 COMMENT 命令不需要對指定對象的獨占訪問。例如,你可以在其他用戶更新表時分析表。
Oracle 資料庫在每個 DDL 語句之前和之後都會隱式地提交當前事務。有些 DDL 語句可能會導致 Oracle 資料庫重新編譯或重新授權模式對象,如更改對象定義的語句。
1.3、數據操縱語言
數據操作語言(DML,Data Manipulation Language)語句用於訪問或操縱現有模式對象中的數據。這些語句不會隱式的提交當前事務。數據操作語言語句主要有:
- INSERT
- DELETE
- UPDATE
- SELECT
- MERGE
- LOCK TABLE
- EXPLAIN PLAN
- CALL
SELECT 語句只能訪問資料庫中的數據,但不能處理資料庫中的數據,儘管它可以在返回查詢結果之前對訪問的數據進行操作。
只有在動態執行時,PL/SQL 才支持 CALL 和 EXPLAIN PLAN 語句。PL/SQL 中完全支持所有其它 DML 語句。
1.4、其它語句
事務控制語句:事務控制語句用於管理 DML 語句所做的更改。事務控制語句主要有:COMMIT、ROLLBACK、SAVEPOINT、SET TRANSACTION 共 4 個。
會話控制語句:會話控制語句用於動態管理用戶會話的屬性。會話控制語句是 ALTER SESSION 和 SET ROLE,這些語句不會隱式提交當前事務。PL/SQL 不支持會話控制語句。
系統控制語句:單一系統控制語句 ALTER SYSTEM 動態管理 Oracle 資料庫實例的屬性。 此語句不會隱式提交當前事務,並且在 PL/SQL 中不受支持。
嵌入式 SQL 語句:嵌入式 SQL 語句將 DDL、DML 和事務控制語句嵌入到 SQL 程式中,個人感覺這個概念挺模糊的。嵌入式 SQL 語句由 Oracle 預編譯器支持。
2、動態 SQL 理論
在 PL/SQL 程式塊中的 SQL 分為兩種:靜態 SQL 和動態 SQL。靜態 SQL 是早期綁定(early binding)的,是指 SQL 語句在編譯時是明確的,執行的是確定對象,大多數的編譯情況屬於這種類型。動態 SQL 是後期綁定(late binding)的,是指 SQL 語句在編譯時是不確定的,編譯程式對動態語句部分不進行處理,只是在程式運行時動態地創建語句、對語句進行語法分析並執行該語句。
2.1、動態 SQL 的用途
1、執行 DDL、DCL 語句:在 PL/SQL 程式中執行 DDL 語句、DCL 語句和會話控制語句必須使用動態 SQL 語句。因為這幾類語句都不能在 PL/SQL 程式中直接運行的。
2、構造通用 SQL 處理:為了開發靈活和通用的程式,往往需要利用數據字典來生成相關的腳本。由於動態 SQL 特有的靈活性,我們很容易按照某種共性去構造通用和重用功能的代碼,例如基於某個表的動態欄位查詢。
3、執行複雜業務邏輯查詢:動態 SQL 語句的另外一個重要應用場景是複雜業務邏輯查詢和報表開發環境。在這兩種情況下,查詢參數是由用戶的輸入開關和輸入項而定,甚至查詢出的欄位也根據查詢參數有所不同,不用動態 SQL 很難實現。
儘管動態 SQL 如此靈活和強大,但凡事總有利弊。首先,動態 SQL 語句無法在編譯前期檢查 SQL 是否正確,必須等到運行期才會發現問題;其次,靜態 SQL 是一次解析,多次執行,雖然動態 SQL 也可以使用綁定變數的方式,但也可能會帶來一些意想不到的性能問題。例如,綁定變數在 SQL 要訪問的表存在數據傾斜時會提供錯誤的執行計劃;最後,動態 SQL 語句可讀性較差,不易維護。
除非是上述三類情況,否則不應該使用動態 SQL 語句。我曾接手過一個大量使用動態 SQL 語句的資料庫,可以看的出來,這些存儲過程是從網上或其它庫中拷貝過來的,而且那些動態 SQL 語句大多也不夠靈活和通用,又長期沒怎麼維護,很多一調用就報錯,基於這個資料庫的項目的可維護性可想而知!
2.2、動態 SQL 的語法
理論說了這麼多,那到底何為動態 SQL 語句呢?簡單的說就是存儲在字元串變數中的 SQL 命令。如下例中的v_sql
即為動態 SQL 語句:
DECLARE
v_sql VARCHAR2(200):='SELECT t.course_name, t.course_desc FROM demo.t_course t';
早期 Oracle 提供了DBMS_SQL
包來執行動態 SQL 語句,後來又提供了本地動態 SQL(Native Dynamic SQL),用來代替DBMS_SQL
執行動態 SQL 語句。使用本地動態 SQL 比使用DBMS_SQL
更簡單,且大部分情況比DBMS_SQL
更高效。但 Oracle 依然支持DBMS_SQL
,所以動態 SQL 既可以通過本地動態 SQL 來執行,也可以通過DBMS_SQL
包來執行。
通過本地動態 SQL 執行動態 SQL 語句的語法是:
EXECUTE IMMEDIATE dynamic_string
{INTO {define_variable1 [,define_variable2 ...] | record_name}}
[USING [IN|OUT|IN OUT] bind_argument1 [,[IN|OUT|IN OUT] bind_argument2] ...]
[returning_clause];
EXECUTE IMMEDIATE 解析並馬上執行動態的 SQL 語句或非運行時創建的 PL/SQL 塊。語法中的 INTO 子句用於將查詢返回的結果值傳遞給已定義的標量類型變數或記錄變數,但這裡只能接收單行查詢的返回結果。
使用 EXECUTE IMMEDIATE 方式比使用DBMS_SQL
方式寫法更簡潔,運行數度也更快。但DBMS_SQL
比 EXECUTE IMMEDIATE 更強大,某些極端複雜的情況只有DBMS_SQL
能實現,不過話又說回來了,如今過於複雜的功能基本也沒人會依賴於資料庫的,所以我個人覺得沒必要掌握DBMS_SQL
。但如果不幸需要維護使用了DBMS_SQL
的老代碼時,可以參考:《Oracle Database PL/SQL Packages and Types Reference: DBMS_SQL》。
2.3、綁定變數
綁定變數也就是動態 SQL 語句中的一個占位符,它告訴 Oracle 現在需要生成一個執行計劃,隨後會為這個占位符提供一個值。綁定變數的占位符是在動態 SQL 語句中指定的,可以是 :1、:2、:3 之類的,也可以是 :a、:b、:c 這樣的定義。
綁定變數在動態 SQL 中應用,主要是為了實現一次解析,多次執行,從而提高效率的目的。綁定變數的優勢是可以在庫緩存中共用游標,這樣可以避免硬解析及與之相關的額外開銷。因此,綁定變數是一種減少應用程式在分析查詢時使用栓鎖數目的有效方法,其次,使用綁定變數可以避免註入攻擊。
本地動態 SQL 語法中的 USING 子句用於在 DML 需要傳入的變數中、共用庫緩存游標、避免硬解析。USING 後的是綁定變數的實際值,按照動態 SQL 中綁定變數的順序賦值,不管名字是否重覆,全部都要賦值。而 RETURNING INTO 子句主要用於 DML 語句中所返回被影響的列值。
使用綁定變數的幾個限制:
- 1、綁定變數不能直接傳入 NULL 值。
- 2、USING 後的綁定變數必須是 SQL 類型,例如:字元型、整形、浮點型等,而 PL/SQL 的 BOOLEAN 等類型是不支持的。
- 3、綁定變數不能用表名或列別名來替換。
- 4、綁定變數只能代替 SQL 或 PL/SQL 中的字面量、變數和簡單表達式。
- 5、動態 DLL 語句中不允許出現綁定變數。因為在運行期間,引擎要解析動態語句中的對象,確保 SQL 語法正確,而綁定變數此時還是變數,這樣 Oracle 會認為對象不存在。此時可以通過拼接字元串的方式來實現。
3、動態 SQL 實戰
3.1、封裝執行 DML 的通用存儲過程
示例 1(證明動態 DLL 語句中不允許出現綁定變數):
DECLARE
v_sql VARCHAR2(100);
v_table_name VARCHAR2(30):='demo.t2';
BEGIN
v_sql := 'DROP TABLE :1';
EXECUTE IMMEDIATE v_sql USING v_table_name;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('異常:'||SQLCODE||': '||SUBSTR(SQLERRM,1,64));
END;
上例的輸出結果是:“異常:-903: ORA-00903: 表名無效”。這也就證明瞭 DROP TABLE 語句中是不允許使用綁定變數的,其它 DDL 也一樣,這裡就不一一證明瞭。上例的正確寫法是(將綁定變數換成拼接字元串):
DECLARE
v_sql VARCHAR2(100);
v_table_name VARCHAR2(30):='demo.t2';
BEGIN
v_sql := 'DROP TABLE '||v_table_name;
EXECUTE IMMEDIATE v_sql;
END;
示例 2(編寫一個簡單的動態查詢,遍歷輸出所有課程信息):
DECLARE
v_sql VARCHAR2(500);
v_table_name VARCHAR2(30):='T_COURSE';
v_field_list VARCHAR2(300);
v_cursor SYS_REFCURSOR;
v_course demo.t_course%ROWTYPE;
BEGIN
v_sql := 'SELECT WM_CONCAT(t.column_name) FROM USER_TAB_COLUMNS t WHERE t.table_name=:1';
EXECUTE IMMEDIATE v_sql INTO v_field_list USING v_table_name;
v_sql := 'SELECT '||v_field_list||' FROM '||v_table_name;
OPEN v_cursor FOR v_sql;
LOOP
FETCH v_cursor INTO v_course;
EXIT WHEN v_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_course.course_id||','||v_course.course_name||','||v_course.course_desc);
END LOOP;
CLOSE v_cursor;
END;
示例 3(封裝一個根據某個欄位值刪除表中數據的通用存儲過程):
CREATE OR REPLACE PROCEDURE sp_delete_data(
p_table IN VARCHAR2, -- 刪除表名稱
p_field IN VARCHAR2 := '', -- 刪除條件欄位名
p_value IN VARCHAR2 := '' -- 刪除條件欄位值
)
/**************************************************
功能:刪除 p_table 表中 p_field 欄位值為 p_value 的數據
修訂記錄:
版本號 修訂時間 修訂人 描述
1.0.0 2017-05-17 hanzz 1.創建此存儲過程
**************************************************/
AS
v_sql VARCHAR2(500);
BEGIN
IF p_field IS NULL THEN -- 未指定欄位時,刪除整個表的數據
v_sql := 'DELETE FROM '||p_table;
ELSE
v_sql := 'DELETE FROM '||p_table||' t WHERE t.'||p_field||'='''||p_value||'''';
END IF;
EXECUTE IMMEDIATE v_sql; -- 執行刪除語句
COMMIT; -- 提交刪除
END;
調用:
BEGIN
sp_delete_data('demo.t_course','course_id','1'); -- 刪除課程表中 ID 為 1 的課程
sp_delete_data(p_table => 'demo.t_course'); -- 刪除課程表中的所有數據
END;
示例 4(封裝一個根據某個欄位值修改另一個欄位值的通用存儲過程):
CREATE OR REPLACE PROCEDURE sp_update_data(
p_table_name IN VARCHAR2, -- 修改表名稱
p_update_field IN VARCHAR2, -- 被修改欄位名
p_update_value IN VARCHAR2, -- 被修改欄位值
p_where_field IN VARCHAR2 := '', -- 修改條件欄位名
p_where_value IN VARCHAR2 := '' -- 修改條件欄位值
)
/**************************************************
功能:將 p_table_name 表中 p_where_field 欄位值為 p_where_value 的行中 p_update_field 欄位的值修改為 p_update_value
修訂記錄:
版本號 修訂時間 修訂人 描述
1.0.0 2017-05-17 hanzz 1.創建此存儲過程
**************************************************/
AS
v_sql VARCHAR2(500);
BEGIN
IF p_where_field IS NULL THEN -- 未指定欄位時,修改整個表的數據
v_sql := 'UPDATE '||p_table_name||' SET '||p_update_field||'=:1';
EXECUTE IMMEDIATE v_sql USING p_update_value;
ELSE
v_sql := 'UPDATE '||p_table_name||' SET '||p_update_field||'=:1'||' WHERE '||p_where_field||'=:2';
EXECUTE IMMEDIATE v_sql USING p_update_value,p_where_value; -- 執行修改語句
END IF;
COMMIT; -- 提交修改
END;
調用:
BEGIN
sp_update_data('t_course','course_desc','漢語','course_id','1'); -- 將 ID 為 1 的課程備註改為漢語
sp_update_data('t_course','course_desc','電腦'); -- 將表中所有課程備註改為電腦
END;
3.2、批量編譯資料庫對象
視圖、函數、存儲過程、包、觸發器等模式對象依賴於基表,當基表被修改後,可能會導致 Oracle 資料庫重新編譯這些模式對象,如果編譯未通過就會直接掛掉(在 PL/SQL Developer 里看的話,對象名左上角會有一個紅色的星號)。而且好像一旦編譯未通過就不再重新編譯了,這樣會導致即使正確的修改,只要中間過程有檢測到編譯未通過最終也會掛掉。所以有時候不得不在所有修改完成後對那些掛掉的對象進行編譯。
編譯對象的語法:
ALTER PROCEDURE|FUNCTION|PACKAGE [<schema>.]<name> COMPILE [BODY];
編譯示例可參考:《.Net程式員學用Oracle系列(7):視圖、函數、存儲過程、包》。但有時候掛掉的對象太多,如果為每個掛掉的對象都寫條編譯語句來編譯顯然效率太低,這時候可以用下麵這段 PL/SQL 實現批量編譯資料庫中所有已掛掉的模式對象(包括:視圖、函數、存儲過程、包、觸發器)。
DECLARE
v_sql VARCHAR2(200);
BEGIN
FOR obj IN(
SELECT t.object_type,t.object_name FROM USER_OBJECTS t
WHERE t.object_type IN('VIEW','FUNCTION','PROCEDURE','PACKAGE','TRIGGER') AND t.status='INVALID'
ORDER BY t.object_type
)
LOOP
v_sql:='ALTER '||obj.object_type||' '||USER||'.'||obj.object_name||' COMPILE';
DBMS_OUTPUT.PUT_LINE(v_sql||';');
EXECUTE IMMEDIATE v_sql;
END LOOP;
END;
3.3、統計資料庫的總行數
示例 1(統計當前資料庫的總行數):
DECLARE
v_cursor SYS_REFCURSOR;
v_sql VARCHAR2(200); -- 存放統計行數的 SQL 語句
v_rows INT := 0; -- 單表行數
v_total_rows INT := 0; -- 總行數
v_total_tables INT :=0; -- 總表數
BEGIN
OPEN v_cursor FOR
SELECT 'SELECT COUNT(1) FROM '||LOWER(USER)||'.'||LOWER(t.table_name)
FROM USER_TABLES t;
LOOP
FETCH v_cursor INTO v_sql;
EXIT WHEN v_cursor%NOTFOUND;
EXECUTE IMMEDIATE v_sql INTO v_rows; -- 執行統計的 SQL 語句
v_total_rows := v_total_rows + v_rows; -- 逐表累加行數
v_total_tables := v_total_tables + 1; -- 累加表數
END LOOP;
CLOSE v_cursor;
DBMS_OUTPUT.PUT_LINE(USER||' 的總表數是:'||v_total_tables);
DBMS_OUTPUT.PUT_LINE(USER||' 的總行數是:'||v_total_rows);
END;
示例 2(統計指定用戶所擁有表的總行數):
DECLARE
v_cursor SYS_REFCURSOR;
v_owner VARCHAR2(30):='JIRA';
v_sql VARCHAR2(200); -- 存放統計行數的 SQL 語句
v_rows INT := 0; -- 單表行數
v_total_rows INT := 0; -- 總行數
v_total_tables INT :=0; -- 總表數
BEGIN
OPEN v_cursor FOR
SELECT 'SELECT COUNT(1) FROM '||LOWER(v_owner)||'.'||LOWER(t.table_name)
FROM DBA_TABLES t WHERE t.owner='JIRA';
LOOP
FETCH v_cursor INTO v_sql;
EXIT WHEN v_cursor%NOTFOUND;
EXECUTE IMMEDIATE v_sql INTO v_rows; -- 執行統計的 SQL 語句
v_total_rows := v_total_rows + v_rows; -- 逐表累加行數
v_total_tables := v_total_tables + 1; -- 累加表數
END LOOP;
CLOSE v_cursor;
DBMS_OUTPUT.PUT_LINE(v_owner||' 的總表數是:'||v_total_tables);
DBMS_OUTPUT.PUT_LINE(v_owner||' 的總行數是:'||v_total_rows);
END;
4、總結
本文主要講述了 Oracle 中的 SQL 分類和動態 SQL 語句。在 SQL 分類部分著重講解了最為常用的兩類 SQL 語言:DDL 和 DML。在動態 SQL 語句部分,著重講解了動態 SQL 語句的語法和應用,並提供了幾個本人自我感覺比較有代表性的案例。
本文鏈接:http://www.cnblogs.com/hanzongze/p/Oracle-plsql-3.html
版權聲明:本文為博客園博主 韓宗澤 原創,作者保留署名權!歡迎通過轉載、演繹或其它傳播方式來使用本文,但必須在明顯位置給出作者署名和本文鏈接!本人初寫博客,水平有限,若有不當之處,敬請批評指正,謝謝!