繼續我們的學習,在這一章,我們來看看如何通過ODBC來操作數據源.
在上一章,我們已學習了第一步,怎樣連接一個數據源。一個連接定義了用戶與數據源的數據通道.它是靜態的.如果要想操作數據源,我們就必須使用語句(statement).可以認為語句就是發給數據源的命令.這個"命令"必須用SQL寫成.通過使用語句,我們就可以修改數據源的結構、在其中執行查詢、修改及刪除數據.
准備及使用語句可分為以下幾個步驟:
分配一個語句句柄(statement handle)
創建SQL語句
執行語句
銷毀語句
分配一個語句句柄
可以通過調用函數 SQLAllocHandle來分配一個語句句柄。例子如下:
.data?
hStmt dd ?
.code
......
invoke SQLAllocHandle, SQL_HANDLE_STMT, hConn, addr hStmt
創建SQL語句
這部分你就得靠自己了你必須知道SQL的語法.比如說,當你想創建一個表時,你就得知道CREATE TABLE語句.
執行語句
執行語句有四種方法,由它們是否被數據庫引擎編譯過(准備好)及被怎樣定義有關.
直接執行(Direct Execution) 由用戶程序定義SQL語句.語句在運行時(runtime)被編譯並執行.
編譯執行(Prepared Execution) 也是由用戶程序定義SQL語句,但編譯和執行被分為兩步:首先SQL被准備好(被編譯),接下來被執行.通過這種方法,我們可以只編譯SQL語句一次但執行相同的語句多次以節省時間.
存儲過程(Procedures) SQL語句被編譯並存放在數據源內,用戶程序可以在運行時調用這些語句.
目錄(Catalog) SQL被硬編碼(hardcoded)在ODBC驅動程序內. 目錄函數的目的是返回預定義的結果集例如數據庫中的所有表名. 總的來說,目錄函數用來獲得數據源的信息的用戶程序可以在運行時調用它們.
這四種方法各有優缺點.當你只運行某一SQL語句一次時,直接執行是一個很好的選擇;如果你要經常運行某一語句,則應首選編譯執行因為SQL語句僅在第一次運行時被編譯,在接下來的運行中,它將運行更快,因為已被編譯過了;存儲過程是注重速度的最佳選擇,因為它已被編譯過並存放在數據源中了。缺點是並不是所有的數據存儲都支持存儲過程.目錄主要是是用來獲得數據源結構的一些信息。
在本章中,我們主要看一下直接執行和編譯執行,因為它們是由我們的程序來完成的.編寫存儲過程是DBMS(數據庫管理系統)的事.而目錄將在稍後討論.
直接執行
要直接並快速執行SQL語句,以如下語法調用函數SQLExecDirect:
SQLExecDirect proto StatementHandle:DWORD, pStatementText:DWORD, TextLength:DWORD
StatementHandle. 要使用的語句句柄
pStatementText. 指向要執行的SQL語句的指針
TextLength. SQL語句的長度.
可能的返回值如下:
SQL_SUCCESS 操作順利
SQL_SUCCESS_WITH_INFO 操作順利但可能產生非致命錯誤
SQL_ERROR 操作失敗
SQL_INVALID_HANDLE 使用的語句句柄非法
SQL_NEED_DATA 如果在執行SQL語句前沒有提供足夠的參數將會獲得這個返回值.這時需調用SQLParamData 或 SQLPutData函數來提交參數.
SQL_NO_DATA 如果SQL語句不返回結果集,例如只是一個查詢動作,將獲得這個返回值。使用戶知道動作成功,但沒有結果集被返回。
SQL_STILL_EXECUTING 如果異步執行SQL語句, SQLExecDirect 會立刻返回這個值,表明語句正在執行。在通常情況下,如果你使用的一個多線程操作系統,異步執行是一個好主意。如果你希望異步執行,舊可以通過SQLSetStmtAttr來設置語句屬性。
例子:
.data
SQLStmt db "select * from Sales",0
.data?
hStmt dd ?
.code
.....
invoke SQLAllocHandle, SQL_HANDLE_STMT, hConn, addr hStmt
.if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO
invoke SQLExecDirect, hStmt, addr SQLStmt, sizeof SQLStmt
編譯執行
SQL的執行被分為兩步:第一步,必須通過調用函數SQLPrepare來*准備*語句。第二步,通過調用SQLExecute函數來執行語句.在使用編譯執行時,我們可以多次調用SQLExecute來執行同一語句. 結合使用SQL參數,這個方法對執行同一語句極為有效。
SQLPrepare 與SQLExecDirect使用相同的三個參數,所以這裡不再寫出函數原型。 SQLExecute 語法:
SQLExecute proto StatementHandle:DWORD
只須這一個參數,我想不再需要解釋了;)
例子:
.data
SQLStmt db "select * from Sales",0
.data?
hStmt dd ?
.code
.....
invoke SQLAllocHandle, SQL_HANDLE_STMT, hConn, addr hStmt
.if ax==SQL_SUCCESS || ax==SQL_SUCCESS_WITH_INFO
invoke SQLPrepare, hStmt, addr SQLStmt, sizeof SQLStmt
invoke SQLExecute, hStmt
你也許會想,編譯執行沒什麼強於直接執行的。上面的例子還不明顯。我們需要知道SQL語句的參數來仔細研究它。
語句參數(Statement Parameters)
這裡的參數是指由SQL語句使用的變量.比如說我們有一個叫做 "employee"的表,它有三個字段:"name", "surname"和 "TelephoneNo" 。現在我們要找一個叫做"Bob"的職員的電話號碼, 就可以使用以下SQL語句:
select telephoneNo from employee where name='Bob'
這條SQL語句象我們希望的那樣工作了。但是,如果我們又想找另一個職員的電話號碼怎麼辦?如果不使用參數,那只好再寫一條SQL語句,然後再一次編譯、執行它。
現在我們不會允許這種低效率的行為了。我們可以使用參數來實現目標。在上面的例子中,我們必須將字符串/值替換為 '?' (被稱為參數標志符(parameter marker)).SQL 語句將變成這樣:
select telephoneNo from employee where name=?
現在想一下這個問題:ODBC驅動程序如何知道用什麼值來替換參數標志符'?'?答案是: 我們必須提供需要的值.這種方法被稱為參數綁定(parameter binding).簡單點說,就是將一個參數標志符與用戶程序中的變量建立連接的過程.在上面的例子中,我們需要創建一個緩沖區來告訴ODBC驅動程序,當它需要一個參數的具體值時,將從我們提供的字符串緩沖區中獲得。一旦一個參數與一個變量綁定,它將一直保持綁定,直到被綁定到另一變量,或直到所有參數都被函數 SQLFreeStmt以(函數)參數 SQL_RESET_PARAMS釋放,或直到該語句被釋放.
將一個參數綁定到一個變量是通過調用函數 SQLBindParameter實現,語法如下:
SQLBindParameter proto StatementHandle:DWORD, ParameterNumber:DWORD, InputOutputType:DWORD, ValueType:DWORD, ParameterType:DWORD, ColumnSize:DWORD, DecimalDigits:DWORD, ParameterValuePtr:DWORD, BufferLength:DWORD, pStrLenOrIndPtr:DWORD
StatementHandle 語句句柄
ParameterNumber 參數個數,由1開始。這就是ODBC用來判斷參數描述符的方法。如果有三個參數,則最左邊是第一個參數,最右邊是第三個參數。
InputOutputType 表明參數是用來輸入還是輸出的標志.這裡的輸入是指ODBC驅動程序將使用參數中獲得的值,輸出是指ODBC驅動程序將在操作結束時將結果放入參數中.大多數情況下,我們使用參數作為輸入。而輸出參數經常與存儲過程有關.這個參數的兩個可能值為: SQL_PARAM_INPUT、 SQL_PARAM_INPUT_OUTPUT和SQL_PARAM_OUTPUT(譯者:似應為三個參數,但原文如此)
ValueType 指明用戶程序將要綁定到參數的值或緩沖區的類型。可能的類型為一組常數,以SQL_C_開頭。
ParameterType 參數的SQL類型。例如,如果SQL參數是文本字段,我們就在這裡填入值SQL_CHAR.查看MSDN中的ODBC程序員指南來獲得完整列表(ODBC Programmer's reference)。
ColumnSize 參數的長度。換句話說,可認為是與參數標志符相連接的列(字段)的長度.在我們的例子中, 我們的參數標志符對列"name"使用了標准值.如果該列被定義了20字節長,我們就該在ColumnSize中填入20.本文來自編程入門網
DecimalDigits 與參數描述符連接的列的小數位.
ParameterValuePtr 指向包含參數數據的緩沖區的指針.
BufferLength 由ParameterValuePtr指向的緩沖區的長度.
pStrLenOrIndPtr 指向一個雙字的指針,包含以下之一:
由ParameterValuePtr指向的緩沖區中包含的參數長度. 除非參數的類型是字符串或二進制值,否則這個值被忽略.別把它與BufferLength混淆,看一下這個例子就會明白:假設該參數是一個字符串,該列有20字節寬.所以我們分配了一個21字節長的緩沖區,並將其地址傳送到ParameterValuePtr。在調用函數SQLExecute前, 我們在緩沖區中放入了字符串"Bob".這個字符串有3字節長,因此我們需要在pStrLenOrIndPtr指向的雙字中放入3這個值.
SQL_NTS. 這個參數是一個0結尾字符串(null-terminated string).
SQL_NULL_DATA. 參數值為NULL.
SQL_DEFAULT_PARAM. 存儲過程將使用參數的默認值,而不是從用戶程序中獲得的值. 它僅適用於已定義了默認參數值的存儲過程.
SQL_DATA_AT_EXEC. 參數的數據將由SQLPutData傳送. 由於數據可能太大無法放入內存(比如整個文件的數據),我們可以告訴ODBC驅動程序我們將用SQLPutData替代.
可能你會說 pStrLenOrIndPtr的參數太多了,但通常情況下,我們只會用到第一或第三個選項。
例子:
.data
SQLString db "select telephoneNo from employee where name=?",0
Sample1 db "Bob",0
Sample2 db "Mary",0
.data?
buffer db 21 dup(?)
StrLen dd ?
.code
........
invoke SQLPrepare, hStmt, addr SQLString,sizeof SQLString
invoke SQLBindParameter, hStmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 20, 0, addr buffer, sizeof buffer, addr StrLen
;===========================================
; First run
;===========================================
invoke lstrcpy, addr buffer, addr Sample1
mov StrLen, sizeof Sample1
invoke SQLExecute, hStmt
;===========================================
; Second run
;===========================================
invoke lstrcpy, addr buffer, addr Sample2
mov StrLen, sizeof Sample2
invoke SQLExecute, hStmt
注意我們僅將參數與緩沖區僅綁定了一次,當我們修改了緩沖區的內容並調用了SQLExecute 很多次.不必再調用SQLPrepare.ODBC驅動程序知道去那裡找它需要的參數因為已通過SQLBindParameter函數告訴了它.
但現在我們還不能獲得查詢結果.訪問和適用結果集是下一章的內容。
假設我們已完成了很多SQL語句,並要執行一個新的語句,那麼沒必要重新分配一個語句句柄.只要使用SQL_UNBIND與SQL_RESET_PARAMS參數調用SQLFreeStmt函數來解除與參數的綁定就可以了. 接下來就可以接著使用原來的語句句柄來執行SQL語句了.
釋放語句
由調用SQLFreeHandle函數實現.
譯注:原標題為Preparing and Using Statements,因為ODBC驅動程序繁多,有解釋、編譯執行之分,所以Preparing不僅是指准備,也有預編譯的意思。
另:關於作者提到的MSDN,其ODBC部分可在Visual Studio及其部件中找到,如
C:\program Files\DevStudio\SharedIDE\Help\ODBC.hlp.