1. 程式人生 > >DB2靜態SQL和動態SQL 的比較與實踐

DB2靜態SQL和動態SQL 的比較與實踐

SQL 語言作為標準的查詢語言,幾乎被所有的資料庫管理系統 (DBMS) 所支援,併成為國際標準。標準的 SQL 語言一般包括三類,即 DDL (Data Definition Language, 資料描述語言 ) 、DML (Data Manipulation Language, 資料操縱語言 ) 和 DCL(Data Control Language,資料控制語言 )。通過這些標準的 SQL 語句,使得各種資料庫能以一種較為統一的方式被訪問。

DB2(本文以下專指 DB2 UDB for LinuxUnix和 Windows 版本)允許使用者通過多種程式設計介面傳送 SQL 語句到資料庫引擎,然後由引擎統一編譯並且執行。SQL 語句從編譯和執行的角度可以分為兩種,靜態 SQL和 動態 SQL,這兩種 SQL 在使用方式、執行機制和效能表現等方面各有特點 :

靜態 SQL:靜態 SQL 語句一般用於嵌入式 SQL 應用中,在程式執行前,SQL 語句必須是確定的,例如 SQL 語句中涉及的列名和表名必須是存在的。靜態 SQL 語句的編譯是在應用程式執行前進行的,編譯的結果會儲存在資料庫內部。而後程式執行時,資料庫將直接執行編譯好的 SQL 語句,降低執行時的開銷。

動態 SQL:動態 SQL 語句是在應用程式執行時被編譯和執行的,例如,使用 DB2 的互動式工具 CLP 訪問資料庫時,使用者輸入的 SQL 語句是不確定的,因此 SQL 語句只能被動態地編譯。動態 SQL 的應用較多,常見的 CLI 和 JDBC 應用程式都使用動態 SQL。

表 1列舉了靜態 SQL 和動態 SQL 的比較結果。

表 1. 靜態 SQL 和動態 SQL 的比較

靜態 SQL動態 SQLSQL 語句直接嵌入到宿主程式語言,程式需要預編譯處理這些嵌入的 SQL 語句SQL 語句一般作為宿主語言的變量出現。嵌入式動態 SQL 應用需要預編譯,非嵌入式 SQL 應用則無需預編譯SQL 語句在程式被編譯時已知,涉及的資料庫物件已存在SQL 語句在程式被編譯時未知,涉及的資料庫物件可以是執行時才建立的SQL 語句在程式執行前被編譯SQL 語句在程式執行時被編譯SQL 語句的編譯結果在 DB2 的目錄 (catalog) 中持久化儲存SQL 語句的編譯結果快取在資料庫的記憶體裡執行時僅讀取目錄 (catalog)執行時編譯 SQL 語句需對目錄 (catalog) 加鎖SQL 語句的優化是根據編譯時的資料庫統計資訊進行的,不能完全反映執行時的情況SQL 語句的優化是根執行時的資料庫統計資訊進行的對 SQL 語句所訪問的資料物件的許可權檢查是在繫結時進行的對 SQL 語句所訪問的資料物件的許可權檢查是在執行時進行的許可權控制的粒度是包(package,一組 SQL 語句的編譯結果),使用者僅需要訪問包的許可權許可權控制的粒度是 SQL 語句,使用者需要具有訪問 SQL 語句中每個資料物件的許可權如果 SQL 語句中的物件被修改,如 DDL 執行,整個包都需要重新綁定當 SQL 語句中的物件被修改時,僅執行過的語句在下次執行時需要重新編譯

根據程式設計方法的不同,DB2 的應用程式開還可以分為嵌入式 SQL 程式設計和非嵌入式程式設計 :

嵌入式 SQL 程式設計將 SQL 語句嵌入到宿主語言 (host) 的程式中,例如 C/C++ 程式。因為宿主語言不識別 SQL 語句,先要對程式進行預編譯,把 SQL 語句轉變為對 DB2 服務的呼叫,並重寫原始碼,最後再使用宿主語言的編譯器對應用程式進行編譯。嵌入式 SQL 都需要被繫結到特定的資料庫中,可分為嵌入式靜態 SQL 和嵌入式動態 SQL。

非嵌入式應用程式不需要預編譯,且方法較多,如 CLI、JDBC、ODBC、ADO.NET 等等,這些方法中都使用動態 SQL。表 2列舉了常見的 DB2 程式設計介面。

表 2. DB2 的程式設計介面一覽

程式設計介面靜態 / 動態是否為嵌入式嵌入式 SQL靜態和動態是DB2 CLI動態否SQLJ靜態是JDBC動態否ADO.NET,OLE DB動態否Perl DBI動態否PDO(PHP資料物件 )動態否

在下面的幾個章節中,我們將陸續從使用角度上描述靜態和動態 SQL 在各種程式設計介面中的應用,並運用一些例項來介紹在具體的場景中如何選擇。 

靜態 SQL 應用

嵌入式靜態 SQL

無論是嵌入式靜態 SQL 還是嵌入式動態 SQL,都需要先進行預編譯,並繫結到特定的資料庫。DB2 的嵌入式 SQL 應用程式支援以下幾種語言:C,C++,COBOL,FORTRAN 和 REXX™。

對嵌入式靜態 SQL 而言,只能使用編譯時確定的 SQL 語句和訪問編譯時已經存在的資料庫物件。清單 1是一個查詢表的例子,使用 C 語言 :

清單 1. 嵌入式靜態 SQL 的 C 語言片斷

//test.sqc 
EXEC SQL INCLUDE SQLCA; 
EXEC SQL BEGIN DECLARE SECTION; 
sqlint32 t_seq = 0; 
char t_name[64]={0}; 
EXEC SQL END DECLARE SECTION; 
... 
t_seq = 5; 
EXEC SQL SELECT c_name INTO :t_name FROM test_tbl WHERE seq=:t_seq; 
printf("c_name = %s \n", t_name);

使用下面的命令,對上述程式碼進行預編譯和編譯:

清單 2. 嵌入式靜態 SQL 的編譯命令

# 需要先連線資料庫 
db2 connect to TESTDB 
# 使用 PREP 命令對 sqc 原始檔進行預編譯,這將生成 test.c 原始檔 
db2 PREP test.sqc 
# 使用 C 編譯器對 test.c 進行編譯 
xlC -q64 -I$DB2PATH/include -g -L$DB2PATH/lib -ldb2 -o test test.c

SQLJ

SQLJ 是應用於靜態 SQL 的 Java程式設計介面。使用 SQLJ 編寫應用程式和使用其他的語言介面相似,一個典型的 SQLJ 應用程式主要包括以下幾個方面:

載入包含 SQLJ 和 JDBC 的 Java 包。

定義收發資料的載體變數。

連線至資料庫,執行相應的 SQL 語句並且正確處理錯誤情況,最後從資料庫斷開。

清單 3是 SQLJ 中執行 SELECT 語句的程式碼片斷。

清單 3. 簡單的 SQLJ 程式片斷

// 載入相關包 
import sqlj.runtime.*; 
import java.sql.*; 
// 連線至資料庫 
Connection con0 = DriverManager.getConnection(url); 
// 執行相關的 SQL 語句 
#sql [ctx] iter = {SELECT NAME FROM EMP}; 
// 得到結果 
while (iter.next()) { 
System.out.println(iter.LASTNAME()); 
}

在 SQLJ 應用程式中,可以使用 ExecutionContext 類去控制和監控 SQL 語句的執行,如 清單 4所示。

清單 4. 在 SQLJ 使用 ExecutionContext

// 分配儲存執行上下文的變數 
ExecutionContext exeCtx=new ExecutionContext(); 
// 關聯變數和要執行的語句 
#sql [connCtx, exeCtx] {DELETE FROM EMP WHERE SALARY > 10000}; 
// 獲取結果 
System.out.println("Deleted " + exeCtx.getUpdateCount() + " rows");

動態 SQL 應用

嵌入式動態 SQL

與嵌入式靜態 SQL 相同,嵌入式動態 SQL 也需要預編譯。不同的是,嵌入式動態 SQL 將 SQL 語句存放在宿主語言的字元型變數中,這樣的 SQL 語句在預編譯時是不被處理的,而是被當作主機變數對待,直到程式執行時才被編譯執行。

得益於動態 SQL 的優點,嵌入式動態 SQL 可以處理執行時才確定的 SQL 語句,例如由程式執行時拼接的 SQL 語句。為了處理返回結果未知的 SELECT 語句,嵌入式動態 SQL 使用 SQLDA(SQL descriptor area) 結構和 DESCRIBE 語句獲取結果集的結構和屬性。SQLDA 結構如 圖 1所示。HEADER 描述整個結果集的資訊,而每個 SQLVAR 結構描述結果集中一個欄位的資訊。

圖 1. SQLDA 結構

清單 5展示瞭如何使用 SQLDA 結構和 DESCRIBE 語句處理 SELECT 語句。

清單 5. 使用 SQLDA 結構和 DESCRIBE 語句的虛擬碼

//test1.sqc 

// 宣告兩個 SQLDA 指標,minsqlda 將是一個最小的 SQLDA 結構,用於 PREPARE 語句, 
// 此時結果集的欄位數量未知,所以只需一個最小的 SQLDA,即包含 HEADER 和一個 SQLVAR 
struct sqlda * minsqlda = new sqlda; 
struct sqlda * fulsqlda = NULL; 

strcpy(hostVarStmt, "SELECT name FROM TEST_TBL"); 

// PREPARE 將填寫 minsqlda 的 header,sqldabc 為 SQLDA 總長度,sqln 為 SQLVAR 數量,即欄位數量 
EXEC SQL PREPARE STMT INTO :*minsqlda FROM :hostVarStmt; 

// 根據從 minsqlda 中獲取的長度,分配完整的 SQLDA 結構 fulsqlda,其中將包括合適數量的 SQLVAR 結構 
fulsqlda = (struct sqlda *)malloc(SQLDASIZE(minsqlda->sqln)); 

// 使用 DESCRIBE 語句,獲取結果集中每個欄位的描述資訊,包括各欄位的型別 (sqltype) 和長度 (sqllen) 
EXEC SQL DESCRIBE STMT INTO :fulsqlda; 

Loop 

// 根據每個欄位的長度,分配記憶體,將地址儲存在對應 SQLVAR 的 sqldata 中 


// 宣告遊標 
EXEC SQL DECLARE c1 CURSOR FOR STMT; 
EXEC SQL OPEN c1; 

// 讀取記錄,記錄中每個欄位的內容將寫入 fulsqlda 中對應 SQLVAR 結構的 sqldata 指向的記憶體 
EXEC SQL FETCH c1 USING DESCRIPTOR :*fulsqlda; 

// 迴圈讀取所有記錄 
while (sqlca.sqlcode != 100) 

EXEC SQL FETCH c1 USING DESCRIPTOR :*fulsqlda; 


EXEC SQL CLOSE c1; 

DB2 CLI

DB2 CLI(Call Level Interface)基於微軟的 ODBC(Open Database Connectivity)標準,同時也增加了 DB2 特有的功能。它允許開發人員使用 C/C++ 語言訪問 DB2 並通過函式呼叫將動態 SQL 語句傳遞給 DB2。DB2 CLI 一方面在 ODBC 的環境中作為 ODBC 驅動被 ODBC 管理器載入,另一方面,應用程式也可以直接使用 DB2 CLI API,此時具有更好的效能。清單 6展示了 CLI 如何執行一個 DELETE 語句。

清單 6. CLI 應用程式片斷

/* SQL statements to be executed */ 
SQLCHAR * stmt1 = (SQLCHAR *)"delete from test1 where col1 = 5"; 

/* directly execute statement 1 */ 
cliRC = SQLExecDirect(hstmt, stmt1, SQL_NTS);

對一個返回結果未知的 SELECT 查詢語句,需要使用相關的 CLI API 函式動態地獲取對結果集的描述,並取回資料。圖 2展示了這個處理過程。

圖 2. DB2 CLI 應用程式處理 SELECT 語句流程

與嵌入式動態 SQL 應用相比,CLI 程式的靈活性更強。表 3列舉了 CLI 應用程式和嵌入式動態 SQL 應用程式的比較。

表 3. CLI 應用程式和嵌入式動態 SQL 應用程式的比較

CLI嵌入式動態 SQLDB2 CLI 應用程式使用 API 函式傳送 SQL 語句,應用程式的編譯、連線和執行獨立於特定資料庫,即無需預編譯,也不需要繫結到某個資料庫例項。嵌入式動態 SQL 應用程式需要預編譯,並且需要繫結到特定的資料庫例項。CLI 提供的滾動遊標(scroll cursor)支援前、後向移動一行或者多行記錄,而移動的起點可以是第一行、最後一行或者之前儲存的位置。只支援順序前向讀取遊標,並使用 FETCH 語句取得記錄。可以獲取儲存過程呼叫返回的結果集。並支援多種 DB2 伺服器。可以獲取儲存過程的輸出型或輸入 - 輸出型引數的值,但不能獲取儲存過程返回的結果集。只支援 C/C++ 語言。支援 C/C++, FORTRAN、COBOL 和 Java(SQLJ)。使用 CLI API 函式 SQLDescribeCol()、SQLAttribute() 描述未知 SQL 語句結果集的資訊,包括結果集欄位長度、型別、精度等資訊。使用 SQLDA 結構和 DESCRIBE 語句獲取未知 SQL 語句結果集的欄位列表,包括型別、長度等資訊。

動態sql語句示例
1 :普通SQL語句可以用Exec執行 

eg: Select * from tableName 
Exec('select * from tableName') 
Exec sp_executesql N'select * from tableName' -- 請注意字串前一定要加N 

2: 欄位名,表名,資料庫名之類作為變數時,必須用動態SQL 

eg:  
declare @fname varchar(20) 
set @fname = 'FiledName' 
Select @fname from tableName -- 錯誤,不會提示錯誤,但結果為固定值FiledName,並非所要。 
Exec('select ' + @fname + ' from tableName') -- 請注意 加號前後的 單引號的邊上加空格 

當然將字串改成變數的形式也可 
declare @fname varchar(20) 
set @fname = 'FiledName' --設定欄位名 

declare @s varchar(1000) 
set @s = 'select ' + @fname + ' from tableName' 
Exec(@s) -- 成功 
exec sp_executesql @s -- 此句會報錯 



declare @s Nvarchar(1000) -- 注意此處改為nvarchar(1000) 
set @s = 'select ' + @fname + ' from tableName' 
Exec(@s) -- 成功  
exec sp_executesql @s -- 此句正確 

3. 輸出引數 
declare @num int, 
@sqls nvarchar(4000) 
set @sqls='select count(*) from tableName' 
exec(@sqls) 
--如何將exec執行結果放入變數中? 

declare @num int, 
@sqls nvarchar(4000) 
set @sqls='select @a=count(*) from tableName ' 
exec sp_executesql @sqls,N'@a int output',@num output 
select @num