2007年7月18日 星期三

[轉]SQLite 用法( 五)--如何用sqlite 執行標準 sql 語法

[本文轉載來源為:http://webmaster.blog.sohu.com/54167184.html]

(2) SQL語句操作
本節介紹如何用sqlite 執行標準 sql 語法。



i.1 執行sql語句

int sqlite3_exec(sqlite3*, const char *sql, sqlite3_callback, void *, char **errmsg );

這就是執行一條 sql 語句的函數。

第1個參數不再說了,是前面open函數得到的指標。說了是關鍵數據架構。

第2個參數const char *sql 是一條 sql 語句,以\0結尾。

第3個參數sqlite3_callback 是回調,當這條語句執行之後,sqlite3會去調用你提供的這個函數。(什麼是回調函數,自己找別的資料學習)

第4個參數void * 是你所提供的指標,你可以傳遞任何一個指標參數到這裡,這個參數最終會傳到回調函數裡面,如果不需要傳遞指標給回調函數,可以填NULL。等下我們再看回調函數的寫法,以及這個參數的使用。

第5個參數char ** errmsg 是錯誤訊息。注意是指標的指標。sqlite3裡面有很多固定的錯誤訊息。執行 sqlite3_exec 之後,執行失敗時可以查閱這個指標(直接 printf(“%s\n”,errmsg))得到一串字元串訊息,這串訊息告訴你錯在什麼地方。sqlite3_exec函數透過修改你傳入的指標的指標,把你提供的指標指向錯誤提示訊息,這樣sqlite3_exec函數外面就可以透過這個 char*得到具體錯誤提示。

說明︰通常,sqlite3_callback 和它後面的 void * 這兩個位置都可以填 NULL。填NULL表示你不需要回調。比如你做 insert 操作,做 delete 操作,就沒有必要使用回調。而當你做 select 時,就要使用回調,因為 sqlite3 把數據查出來,得透過回調告訴你查出了什麼數據。



i.2 exec 的回調

typedef int (*sqlite3_callback)(void*,int,char**, char**);

你的回調函數必須定義成上面這個函數的類型。下面給個簡單的例子︰

//sqlite3的回調函數

// sqlite 每查到一條記錄,就調用一次這個回調

int LoadMyInfo( void * para, int n_column, char ** column_value, char ** column_name )

{

//para是你在 sqlite3_exec 裡傳入的 void * 參數

//透過para參數,你可以傳入一些特殊的指標(比如類指標、架構指標),然後在這裡面強製轉換成對應的類型(這裡面是void*類型,必須強製轉換成你的類型才可用)。然後操作這些數據

//n_column是這一條記錄有多少個字段 (即這條記錄有多少列)

// char ** column_value 是個關鍵值,查出來的數據都儲存在這裡,它實際上是個1維數組(不要以為是2維數組),每一個元素都是一個 char * 值,是一個字段內容(用字元串來表示,以\0結尾)

//char ** column_name 跟 column_value是對應的,表示這個字段的字段名稱



//這裡,我不使用 para 參數。忽略它的存在.



int i;

printf( “記錄包含 %d 個字段\n”, n_column );

for( i = 0 ; i <> 字段值:%s\n”, column_name[i], column_value[i] );

}

printf( “------------------\n“ );

return 0;

}



int main( int , char ** )

{

sqlite3 * db;

int result;

char * errmsg = NULL;



result = sqlite3_open( “c:\\Dcg_database.db”, db );

if( result != SQLITE_OK )

{

//數據庫打開失敗

return -1;

}

//數據庫操作代碼

//創建一個測試表,表名叫 MyTable_1,有2個字段︰ ID 和 name。其中ID是一個自動增加的類型,以後insert時可以不去指定這個字段,它會自己從0開始增加

result = sqlite3_exec( db, “create table MyTable_1( ID integer primary key autoincrement, name nvarchar(32) )”, NULL, NULL, errmsg );

if(result != SQLITE_OK )

{

printf( “創建表失敗,錯誤碼:%d,錯誤原因:%s\n”, result, errmsg );

}

//插入一些記錄

result = sqlite3_exec( db, “insert into MyTable_1( name ) values ( ‘走路’ )”, 0, 0, errmsg );

if(result != SQLITE_OK )

{

printf( “插入記錄失敗,錯誤碼:%d,錯誤原因:%s\n”, result, errmsg );

}

result = sqlite3_exec( db, “insert into MyTable_1( name ) values ( ‘騎單車’ )”, 0, 0, errmsg );

if(result != SQLITE_OK )

{

printf( “插入記錄失敗,錯誤碼:%d,錯誤原因:%s\n”, result, errmsg );

}

result = sqlite3_exec( db, “insert into MyTable_1( name ) values ( ‘坐汽車’ )”, 0, 0, errmsg );

if(result != SQLITE_OK )

{

printf( “插入記錄失敗,錯誤碼:%d,錯誤原因:%s\n”, result, errmsg );

}





//開始查詢數據庫

result = sqlite3_exec( db, “select * from MyTable_1”, LoadMyInfo, NULL, errmsg );



//關閉數據庫

sqlite3_close( db );

return 0;

}



透過上面的例子,應該可以知道如何打開一個數據庫,如何做數據庫基本操作。

有這些知識,基本上可以應付很多數據庫操作了。



i.3 不使用回調查詢數據庫

上面介紹的 sqlite3_exec 是使用回調來執行 select 操作。還有一個方法可以直接查詢而不需要回調。但是,我個人感覺還是回調好,因為代碼可以更加整齊,只不過用回調很麻煩,你得聲明一個函數,如果這個函數是類成員函數,你還不得不把它聲明成 static 的(要問為什麼?這又是C++基礎了。C++成員函數實際上隱藏了一個參數︰this,C++調用類的成員函數的時候,隱含把類指標當成函數的第一個參數傳遞進去。結果,這造成跟前面說的 sqlite 回調函數的參數不相符。只有當把成員函數聲明成 static 時,它才沒有多餘的隱含的this參數)。

雖然回調顯得代碼整齊,但有時候你還是想要非回調的 select 查詢。這可以透過 sqlite3_get_table 函數做到。

int sqlite3_get_table(sqlite3*, const char *sql, char ***resultp, int *nrow, int *ncolumn, char **errmsg );

第1個參數不再多說,看前面的例子。

第2個參數是 sql 語句,跟 sqlite3_exec 裡的 sql 是一樣的。是一個很普通的以\0結尾的char *字元串。

第3個參數是查詢結果,它依然一維數組(不要以為是二維數組,更不要以為是三維數組)。它內存佈局是︰第一行是字段名稱,後面是緊接著是每個字段的值。下面用例子來說事。

第4個參數是查詢出多少條記錄(即查出多少行)。

第5個參數是多少個字段(多少列)。

第6個參數是錯誤訊息,跟前面一樣,這裡不多說了。

下面給個簡單例子:

int main( int , char ** )

{

sqlite3 * db;

int result;

char * errmsg = NULL;

char **dbResult; //是 char ** 類型,兩個*號

int nRow, nColumn;

int i , j;

int index;



result = sqlite3_open( “c:\\Dcg_database.db”, db );

if( result != SQLITE_OK )

{

//數據庫打開失敗

return -1;

}

//數據庫操作代碼

//假設前面已經創建了 MyTable_1 表

//開始查詢,傳入的 dbResult 已經是 char **,這裡又加了一個 取位址符,傳遞進去的就成了 char ***

result = sqlite3_get_table( db, “select * from MyTable_1”, dbResult, nRow, nColumn, errmsg );

if( SQLITE_OK == result )

{

//查詢成功



index = nColumn; //前面說過 dbResult 前面第一行數據是字段名稱,從 nColumn 索引開始才是真正的數據

printf( “查到%d條記錄\n”, nRow );

for( i = 0; i < j =" 0"> 字段值:%s\n”, dbResult[j], dbResult [index] );

++index; // dbResult 的字段值是連續的,從第0索引到第 nColumn - 1索引都是字段名稱,從第 nColumn 索引開始,後面都是字段值,它把一個二維的表(道統的行清單示法)用一個扁平的形式來表示

}

printf( “-------\n” );

}

}



//到這裡,不論數據庫查詢是否成功,都釋放 char** 查詢結果,使用 sqlite 提供的功能來釋放

sqlite3_free_table( dbResult );



//關閉數據庫

sqlite3_close( db );

return 0;

}



到這個例子為止,sqlite3 的常用用法都介紹完了。

用以上的方法,再配上 sql 語句,完全可以應付絕大多數數據庫需求。

但有一種情況,用上面方法是無法實現的︰需要insert、select 二進製。當需要處理二進製數據時,上面的方法就沒辦法做到。下面這一節說明如何插入二進製數據

(2) 操作二進製
sqlite 操作二進製數據需要用一個輔助的數據類型︰sqlite3_stmt * 。

這個數據類型記錄了一個“sql語句”。為什麼我把 “sql語句” 用雙引號引起來?因為你可以把 sqlite3_stmt * 所表示的內容看成是 sql語句,但是實際上它不是我們所熟知的sql語句。它是一個已經把sql語句解析了的、用sqlite自己標記記錄的內部數據架構。

正因為這個架構已經被解析了,所以你可以往這個語句裡插入二進製數據。當然,把二進製數據插到 sqlite3_stmt 架構裡可不能直接 memcpy ,也不能像 std::string 那樣用 + 號。必須用 sqlite 提供的函數來插入。



i.1 寫入二進製

下面說寫二進製的步驟。

要插入二進製,前提是這個表的字段的類型是 blob 類型。我假設有這么一張表︰

create table Tbl_2( ID integer, file_content blob )

首先聲明

sqlite3_stmt * stat;

然後,把一個 sql 語句解析到 stat 架構裡去︰

sqlite3_prepare( db, “insert into Tbl_2( ID, file_content) values( 10, ? )”, -1, stat, 0 );

上面的函數完成 sql 語句的解析。第一個參數跟前面一樣,是個 sqlite3 * 類型變量,第二個參數是一個 sql 語句。

這個 sql 語句特別之處在於 values 裡面有個 ? 號。在sqlite3_prepare函數裡,?號表示一個未定的值,它的值等下才插入。

第三個參數我寫的是-1,這個參數含義是前面 sql 語句的長度。如果小於0,sqlite會自動計算它的長度(把sql語句當成以\0結尾的字元串)。

第四個參數是 sqlite3_stmt 的指標的指標。解析以後的sql語句就放在這個架構裡。

第五個參數我也不知道是干什麼的。為0就可以了。

如果這個函數執行成功(返回值是 SQLITE_OK 且 stat 不為NULL ),那麼下面就可以開始插入二進製數據。

sqlite3_bind_blob( stat, 1, pdata, (int)(length_of_data_in_bytes), NULL ); // pdata為數據緩沖區,length_of_data_in_bytes為數據大小,以位元組為單位

這個函數一共有5個參數。

第1個參數︰是前面prepare得到的 sqlite3_stmt * 類型變量。

第2個參數︰?號的索引。前面prepare的sql語句裡有一個?號,假如有多個?號怎么插入?方法就是改變 bind_blob 函數第2個參數。這個參數我寫1,表示這裡插入的值要替換 stat 的第一個?號(這裡的索引從1開始計數,而非從0開始)。如果你有多個?號,就寫多個 bind_blob 語句,並改變它們的第2個參數就替換到不同的?號。如果有?號沒有替換,sqlite為它取值null。

第3個參數︰二進製數據起始指標。

第4個參數︰二進製數據的長度,以位元組為單位。

第5個參數︰是個析夠回調函數,告訴sqlite當把數據處理完後調用此函數來析夠你的數據。這個參數我還沒有使用過,因此理解也不深刻。但是一般都填NULL,需要釋放的內存自己用代碼來釋放。

bind完了之後,二進製數據就進入了你的“sql語句”裡了。你現下可以把它儲存到數據庫裡︰

int result = sqlite3_step( stat );

透過這個語句,stat 表示的sql語句就被寫到了數據庫裡。

最後,要把 sqlite3_stmt 架構給釋放︰

sqlite3_finalize( stat ); //把剛才分發的內容析構掉



i.2 讀出二進製

下面說讀二進製的步驟。

跟前面一樣,先聲明 sqlite3_stmt * 類型變量︰

sqlite3_stmt * stat;

然後,把一個 sql 語句解析到 stat 架構裡去︰

sqlite3_prepare( db, “select * from Tbl_2”, -1, stat, 0 );

當 prepare 成功之後(返回值是 SQLITE_OK ),開始查詢數據。

int result = sqlite3_step( stat );

這一句的返回值是 SQLITE_ROW 時表示成功(不是 SQLITE_OK )。

你可以循環執行 sqlite3_step 函數,一次 step 查詢出一條記錄。直到返回值不為 SQLITE_ROW 時表示查詢結束。

然後開始獲取第一個字段︰ID 的值。ID是個整數,用下面這個語句獲取它的值︰

int id = sqlite3_column_int( stat, 0 ); //第2個參數表示獲取第幾個字段內容,從0開始計算,因為我的表的ID字段是第一個字段,因此這裡我填0



下面開始獲取 file_content 的值,因為 file_content 是二進製,因此我需要得到它的指標,還有它的長度︰

const void * pFileContent = sqlite3_column_blob( stat, 1 );

int len = sqlite3_column_bytes( stat, 1 );

這樣就得到了二進製的值。

把 pFileContent 的內容儲存出來之後,不要忘了釋放 sqlite3_stmt 架構︰

sqlite3_finalize( stat ); //把剛才分發的內容析構掉



i.3 重複使用 sqlite3_stmt 架構

如果你需要重複使用 sqlite3_prepare 解析好的 sqlite3_stmt 架構,需要用函數︰ sqlite3_reset。

result = sqlite3_reset(stat);

這樣, stat 架構又成為 sqlite3_prepare 完成時的狀態,你可以重新為它 bind 內容

沒有留言: