SQLite是目前最流行的開源嵌入式數據庫
SQLite的主要特征:
1. 管理簡單,甚至可以認為無需管理。
2. 操作方便,SQLite生成的數據庫文件可以在各個平台無縫移植。
3. 可以非常方便的以多種形式嵌入到其他應用程序中,如靜態庫、動態庫等。
4. 易於維護。
綜上所述,SQLite的主要優勢在於靈巧、快速和可靠性高。
由於SQLite在運行時占用的資源較少,而且無需任何管理開銷,因此對於PDA、智能手機等 移動設備來說,SQLite的優勢毋庸置疑。
和RDBMS相比SQLite的一些劣勢:
1. C/S應用: 如果你有多個客戶端需要同時訪問數據庫中的數據,特別是他們之間的數據操作是需要通過網 絡傳輸來完成的。在這種情況下,不應該選擇SQLite。由於SQLite的數據管理機制更多的依 賴於OS的文件系統,因此在這種操作下其效率較低。
2. 數據量較大: 受限於操作系統的文件系統,在處理大數據量時,其效率較低。對於超大數據量的存儲,甚至 不能提供支持。
3. 高並發: 由於SQLite僅僅提供了粒度很粗的數據鎖,如讀寫鎖,因此在每次加鎖操作中都會有大量的 數據被鎖住,即使僅有極小部分的數據會被訪問。換句話說,我們可以認為SQLite只是提供 了表級鎖,沒有提供行級鎖。在這種同步機制下,並發性能很難高效。
C/C++接口簡介
一、概述: 在SQLite提供的C/C++接口中,其中5個APIs屬於核心接口。在這篇博客中我們將主 要介紹它們的用法,以及它們所涉及到的核心SQLite對象,如database_connection和 prepared_statement。相比於其它數據庫引擎提供的APIs,如OCI、MySQL API等, SQLite提供的接口還是非常易於理解和掌握的。
二、核心對象和接口:
1. 核心對象:
在SQLite中最主要的兩個對象是,database_connection和prepared_statement。
database_connection對象是由sqlite3_open()接口函數創建並返回的,在應用程序使用任何其 他SQLite接口函數之前,必須先調用該函數以便獲得database_connnection對象,在隨后的 其他APIs調用中,都需要該對象作為輸入參數以完成相應的工作。至於prepare_statement, 我們可以簡單的將它視為編譯后的SQL語句,因此,所有和SQL語句執行相關的函數也都需要 該對象作為輸入參數以完成指定的SQL操作。
1). sqlite3_open
上面已經提到過這個函數了,它是操作SQLite數據庫的入口函數。該函數返回的 database_connection對象是很多其他SQLite APIs的句柄參數。注意,我們通過該函 數既可以打開已經存在的數據庫文件,也可以創建新的數據庫文件。對於該函數返回 的database_connection對象,我們可以在多個線程之間共享該對象的指針,以便完成 和數據庫相關的任意操作。然而在多線程情況下,我們更為推薦的使用方式是,為每 個線程創建獨立的database_connection對象。對於該函數還有一點也需要額外說明, 我們沒有必要為了訪問多個數據庫而創建多個數據庫連接對象,因為通過SQLite自帶 的ATTACH命令可以在一個連接中方便的訪問多個數據庫。
2). sqlite3_prepare
該函數將SQL文本轉換為prepared_statement對象,並在函數執行后返回該對象的指 針。事實上,該函數並不會評估參數指定SQL語句,它僅僅是將SQL文本初始化為待 執行的狀態。最后需要指出的,對於新的應用程序我們可以使用sqlite3_prepare_v2接 口函數來替代該函數以完成相同的工作
3). sqlite3_step
該函數用於評估sqlite3_prepare函數返回的prepared_statement對象,在執行完該函 數之后,prepared_statement對象的內部指針將指向其返回的結果集的第一行。如果 打算進一步迭代其后的數據行,就需要不斷的調用該函數,直到所有的數據行都遍歷 完畢。然而對於INSERT、UPDATE和DELETE等DML語句,該函數執行一次即可完 成。
4). sqlite3_column
該函數用於獲取當前行指定列的數據,然而嚴格意義上講,此函數在SQLite的接口函 數中並不存在,而是由一組相關的接口函數來完成該功能,其中每個函數都返回不同 類型的數據
如: sqlite3_column_blob
sqlite3_column_bytes
sqlite3_column_bytes16
sqlite3_column_double
sqlite3_column_int
sqlite3_column_int64
sqlite3_column_text
sqlite3_column_text16
sqlite3_column_type
sqlite3_column_value
sqlite3_column_count
其中sqlite3_column_count函數用於獲取當前結果集中的字段數據。下面是使用 sqlite3_step和sqlite3_column函數迭代結果集中每行數據的偽代碼,注意這里作為示 例代碼簡化了對字段類型的判斷:
int fieldCount = sqlite3_column_count(...);
while (sqlite3_step(...) <> EOF)
{
for (int i = 0; i < fieldCount; ++i)
{
int v = sqlite3_column_int(...,i);
}
}
5). sqlite3_finalize
該函數用於銷毀prepared statement對象,否則將會造成內存泄露。
6). sqlite3_close
該函數用於關閉之前打開的database_connection對象,其中所有和該對象相關的 prepared_statements對象都必須在此之前先被銷毀。
三、參數綁定:
和大多數關系型數據庫一樣,SQLite的SQL文本也支持變量綁定,以便減少SQL語句被動態 解析的次數,從而提高數據查詢和數據操作的效率。要完成該操作,我們需要使用SQLite提 供的另外兩個接口APIs,sqlite3_reset和sqlite3_bind。
實例如下:
void test_parameter_binding() { //1. 不帶參數綁定的情況下插入多條數據。 char strSQL[128]; for (int i = 0; i < MAX_ROWS; ++i) { sprintf(strSQL,"insert into testtable values(%d)",i); sqlite3_prepare_v2(..., strSQL); sqlite3_step(prepared_stmt); sqlite3_finalize(prepared_stmt); } //2. 參數綁定的情況下插入多條數據。 string strSQL = "insert into testtable values(?)"; sqlite3_prepare_v2(..., strSQL); for (int i = 0; i < MAX_ROWS; ++i) { sqlite3_bind(...,i); sqlite3_step(prepared_stmt); sqlite3_reset(prepared_stmt); } sqlite3_finalize(prepared_stmt); }
這里首先需要說明的是,SQL語句"insert into testtable values(?)"中的問號(?)表示參數變量的 占位符,該規則在很多關系型數據庫中都是一致的,因此這對於數據庫移植操作還是比較方便 的。
通過上面的示例代碼可以顯而易見的看出,參數綁定寫法的執行效率要高於每次生成不同的 SQL語句的寫法,即2)在效率上要明顯優於1),下面是針對這兩種寫法的具體比較:
1. 單單從程序表面來看,前者在for循環中執行了更多的任務,比如字符串的填充、SQL語句 的prepare,以及prepared_statement對象的釋放。
2. 在SQLite的官方文檔中明確的指出,sqlite3_prepare_v2的執行效率往往要低於 sqlite3_step的效率。
3. 當插入的數據量較大時,后者帶來的效率提升還是相當可觀的。
api 具體使用
(1)在操作數據庫之前,首先要打開數據庫。這個函數打開一個sqlite數據庫文件的連接並且返回一個數據庫連接對象。這個操作同時程序中的第一個調用的sqlite函數,同時也是其他sqlite api的先決條件。許多的sqlite接口函數都需要一個數據庫連接對象的指針作為它們的第一個參數。
函數定義
int sqlite3_open(
const char *filename, /* Database filename (UTF-8) */
sqlite3 **ppDb /* OUT: SQLite db handle */
);
假如這個要被打開的數據文件不存在,則一個同名的數據庫文件將被創建。
返回值:
如果sqlite數據庫被成功打開(或創建),將會返回SQLITE_OK,否則將會返回錯誤碼。Sqlite3_errmsg()或者sqlite3_errmsg16可以用於獲得數據庫打開錯誤碼的英文描述,這兩個函數定義為:
const char *sqlite3_errmsg(sqlite3*);
const void *sqlite3_errmsg16(sqlite3*);
參數說明:
filename:需要被打開的數據庫文件的文件名,在sqlite3_open和sqlite3_open_v2中這個參數采用UTF-8編碼,而在sqlite3_open16中則采用UTF-16編碼
ppDb:一個數據庫連接句柄被返回到這個參數,即使發生錯誤。唯一的一場是如果sqlite不能分配內存來存放sqlite對象,ppDb將會被返回一個NULL值
(2)這個函數將sql文本轉換成一個准備語句(prepared statement)對象,同時返回這個對象的指針。這個接口需要一個數據庫連接指針以及一個要准備的包含SQL語句的文本。它實際上並不執行(evaluate)這個SQL語句,它僅僅為執行准備這個sql語句
int sqlite3_prepare_v2(
sqlite3 *db, /* Database handle */
const char *zSql, /* SQL statement, UTF-8 encoded */
int nByte, /* Maximum length of zSql in bytes. */
sqlite3_stmt **ppStmt, /* OUT: Statement handle */
const char **pzTail /* OUT: Pointer to unused portion of zSql */
);
參數:
db:數據指針
zSql:sql語句,使用UTF-8編碼
nByte:如果nByte小於0,則函數取出zSql中從開始到第一個0終止符的內容;如果nByte不是負的,那么它就是這個函數能從zSql中讀取的字節數的最大值。如果nBytes非負,zSql在第一次遇見’/000/或’u000’的時候終止
ppStmt:能夠使用sqlite3_step()執行的編譯好的准備語句的指針,如果錯誤發生,它被置為NULL,如假如輸入的文本不包括sql語句。調用過程必須負責在編譯好的sql語句完成使用后使用sqlite3_finalize()刪除它。
pzTail:上面提到zSql在遇見終止符或者是達到設定的nByte之后結束,假如zSql還有剩余的內容,那么這些剩余的內容被存放到pZTail中,不包括終止符
說明
如果執行成功,則返回SQLITE_OK,否則返回一個錯誤碼。推薦在現在任何的程序中都使用sqlite3_prepare_v2這個函數,sqlite3_prepare只是用於前向兼容
備注
<1>准備語句(prepared statement)對象
typedef struct sqlite3_stmt sqlite3_stmt;
准備語句(prepared statement)對象一個代表一個簡單SQL語句對象的實例,這個對象通常被稱為“准備語句”或者“編譯好的SQL語句”或者就直接稱為“語句”。
語句對象的生命周期經歷這樣的過程:
l 使用sqlite3_prepare_v2或相關的函數創建這個對象
l 使用sqlite3_bind_*()給宿主參數(host parameters)綁定值
l 通過調用sqlite3_step一次或多次來執行這個sql
l 使用sqlite3——reset()重置這個語句,然后回到第2步,這個過程做0次或多次
l 使用sqlite3_finalize()銷毀這個對象
在sqlite中並沒有定義sqlite3_stmt這個結構的具體內容,它只是一個抽象類型,在使用過程中一般以它的指針進行操作,而sqlite3_stmt類型的指針在實際上是一個指向Vdbe的結構體得指針
<2>宿主參數(host parameters)
在傳給sqlite3_prepare_v2()的sql的語句文本或者它的變量中,滿足如下模板的文字將被替換成一個參數:
l ?
l ?NNN,NNN代表數字
l :VVV,VVV代表字符
l @VVV
l $VVV
在上面這些模板中,NNN代表一個數字,VVV代表一個字母數字標記符(例如:222表示名稱為222的標記符),sql語句中的參數(變量)通過上面的幾個模板來指定,如
“select ? from ? “這個語句中指定了兩個參數,sqlite語句中的第一個參數的索引值是1,這就知道這個語句中的兩個參數的索引分別為1和2,使用”?”的話會被自動給予索引值,而使用”?NNN”則可以自己指定參數的索引值,它表示這個參數的索引值為NNN。”:VVV”表示一個名為”VVV”的參數,它也有一個索引值,被自動指定。
可以使用sqlite3_bind_*()來給這些參數綁定值
3. sqlite3_setp()
這個過程用於執行有前面sqlite3_prepare創建的准備語句。這個語句執行到結果的第一行可用的位置。繼續前進到結果的第二行的話,只需再次調用sqlite3_setp()。繼續調用sqlite3_setp()知道這個語句完成,那些不返回結果的語句(如:INSERT,UPDATE,或DELETE),sqlite3_step()只執行一次就返回
函數定義
int sqlite3_step(sqlite3_stmt*);
返回值
函數的返回值基於創建sqlite3_stmt參數所使用的函數,假如是使用老版本的接口sqlite3_prepare()和sqlite3_prepare16(),返回值會 是 SQLITE_BUSY, SQLITE_DONE, SQLITE_ROW, SQLITE_ERROR 或 SQLITE_MISUSE,而v2版本的接口sqlite3_prepare_v2()和sqlite3_prepare16_v2()則會同時返回這些結果碼和擴展結果碼。
對所有V3.6.23.1以及其前面的所有版本,需要在sqlite3_step()之后調用sqlite3_reset(),在后續的sqlite3_ step之前。如果調用sqlite3_reset重置准備語句失敗,將會導致sqlite3_ step返回SQLITE_MISUSE,但是在V3. 6.23.1以后,sqlite3_step()將會自動調用sqlite3_reset。
int sqlite3_reset(sqlite3_stmt *pStmt);
sqlite3_reset用於重置一個准備語句對象到它的初始狀態,然后准備被重新執行。所有sql語句變量使用sqlite3_bind*綁定值,使用sqlite3_clear_bindings重設這些綁定。Sqlite3_reset接口重置准備語句到它代碼開始的時候。sqlite3_reset並不改變在准備語句上的任何綁定值,那么這里猜測,可能是語句在被執行的過程中發生了其他的改變,然后這個語句將它重置到綁定值的時候的那個狀態。
sqlite3_column()
這個過程從執行sqlite3_step()執行一個准備語句得到的結果集的當前行中返回一個列。每次sqlite3_step得到一個結果集的列停下后,這個過程就可以被多次調用去查詢這個行的各列的值。對列操作是有多個函數,均以sqlite3_column為前綴
const void *sqlite3_column_blob(sqlite3_stmt*, int iCol);
int sqlite3_column_bytes(sqlite3_stmt*, int iCol);
int sqlite3_column_bytes16(sqlite3_stmt*, int iCol);
double sqlite3_column_double(sqlite3_stmt*, int iCol);
int sqlite3_column_int(sqlite3_stmt*, int iCol);
sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol);
const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol);
const void *sqlite3_column_text16(sqlite3_stmt*, int iCol);
int sqlite3_column_type(sqlite3_stmt*, int iCol);
sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol);
說明
第一個參數為從sqlite3_prepare返回來的prepared statement對象的指針,第二參數指定這一行中的想要被返回的列的索引。最左邊的一列的索引號是0,行的列數可以使用sqlite3_colum_count()獲得。
int sqlite3_column_bytes(sqlite3_stmt*, int iCol)
int sqlite3_column_bytes16(sqlite3_stmt*, int iCol)
兩個函數返回對應列的內容的字節數,這個字節數不包括后面類型轉換過程中加上的0終止符。
下面是幾個最安全和最簡單的使用策略
- 先sqlite3_column_text() ,然后 sqlite3_column_bytes()
- 先sqlite3_column_blob(),然后sqlite3_column_bytes()
- 先sqlite3_column_text16(),然后sqlite3_column_bytes16()
============
int sqlite3_finalize(sqlite3_stmt *pStmt);
這個過程銷毀前面被sqlite3_prepare創建的准備語句,每個准備語句都必須使用這個函數去銷毀以防止內存泄露。
在空指針上調用這個函數沒有什么影響,同時可以准備語句的生命周期的任一時刻調用這個函數:在語句被執行前,一次或多次調用sqlite_reset之后,或者在sqlite3_step任何調用之后不管語句是否完成執行
sqlite3_close
這個過程關閉前面使用sqlite3_open打開的數據庫連接,任何與這個連接相關的准備語句必須在調用這個關閉函數之前被釋放
實例如下
一、獲取表的Schema信息:
1). 動態創建表。
2). 根據sqlite3提供的API,獲取表字段的信息,如字段數量以及每個字段的類型。
3). 刪除該表。
#include <sqlite3.h> #include <string> using namespace std; void doTest() { sqlite3* conn = NULL; //1. 打開數據庫 int result = sqlite3_open(“./mytest.db",&conn); if (result != SQLITE_OK) { sqlite3_close(conn); return; } const char* createTableSQL ="CREATE TABLE TESTTABLE (int_col INT, float_col REAL, string_col TEXT)"; sqlite3_stmt* stmt = NULL; int len = strlen(createTableSQL); //2. 准備創建數據表,如果創建失敗,需要用sqlite3_finalize釋放sqlite3_stmt對象,以防止內存泄露。 if (sqlite3_prepare_v2(conn,createTableSQL,len,&stmt,NULL) != SQLITE_OK) { if (stmt) sqlite3_finalize(stmt); sqlite3_close(conn); return; } //3. 通過sqlite3_step命令執行創建表的語句。對於DDL和DML語句而言, sqlite3_step執行正確的返回值 //只有SQLITE_DONE,對於SELECT查詢而言,如果有數據返回SQLITE_ROW,當到達結果集末尾時則返回 //SQLITE_DONE。 if (sqlite3_step(stmt) != SQLITE_DONE) { sqlite3_finalize(stmt); sqlite3_close(conn); return; } //4. 釋放創建表語句對象的資源。 sqlite3_finalize(stmt); printf("Succeed to create test table now.\n"); //5. 構造查詢表數據的sqlite3_stmt對象。 const char* selectSQL = "SELECT * FROM TESTTABLE WHERE 1 = 0"; sqlite3_stmt* stmt2 = NULL; if (sqlite3_prepare_v2(conn,selectSQL,strlen(selectSQL),&stmt2,NULL)!= SQLITE_OK) { if (stmt2) sqlite3_finalize(stmt2); sqlite3_close(conn); return; } //6. 根據select語句的對象,獲取結果集中的字段數量。 int fieldCount = sqlite3_column_count(stmt2); printf("The column count is %d.\n",fieldCount); //7. 遍歷結果集中每個字段meta信息,並獲取其聲明時的類型。 for (int i = 0; i < fieldCount; ++i) { //由於此時Table中並不存在數據,再有就是SQLite中的數據類型本身是動態的,所以在沒有數據時 //無法通過sqlite3_column_type函數獲取,此時sqlite3_column_type只會返回SQLITE_NULL, //直到有數據時才能返回具體的類型,因此這里使用了sqlite3_column_decltype函數來獲取表聲 //明時給出的聲明類型。 string stype = sqlite3_column_decltype(stmt2,i); stype = strlwr((char*)stype.c_str());//strlwr()用於將字符串中的字符轉換為小寫,其原型為:char *strlwr(char *str)
//下面的解析規則見該系列的“數據類型-->1. 決定字段親緣性的規則”部分, if (stype.find("int") != string::npos) { printf("The type of %dth column is INTEGER.\n",i); } else if (stype.find("char") != string::npos || stype.find("text") != string::npos) { printf("The type of %dth column is TEXT.\n",i); } else if (stype.find("real") != string::npos || stype.find("floa") != string::npos || stype.find("doub") != string::npos ) { printf("The type of %dth column is DOUBLE.\n",i); } } sqlite3_finalize(stmt2); //8. 為了方便下一次測試運行,我們這里需要刪除該函數創建的數據表,否則在下次運行時將無法 //創建該表,因為它已經存在。 const char* dropSQL = "DROP TABLE TESTTABLE"; sqlite3_stmt* stmt3 = NULL; if (sqlite3_prepare_v2(conn,dropSQL,strlen(dropSQL),&stmt3,NULL) != SQLITE_OK) { if (stmt3) sqlite3_finalize(stmt3); sqlite3_close(conn); return; } if (sqlite3_step(stmt3) == SQLITE_DONE) { printf("The test table has been dropped.\n"); } sqlite3_finalize(stmt3); sqlite3_close(conn); }
int main() { doTest(); return 0; }
運行結果:
//輸出結果為:
//Succeed to create test table now
//The column count is 3.
//The type of 0th column is INTEGER.
//The type of 1th column is DOUBLE.
//The type of 2th column is TEXT.
//The test table has been dropped.
常規數據插入:
1). 創建測試數據表。
2). 通過INSERT語句插入測試數據。
3). 刪除測試表。
#include <sqlite3.h> #include <string> #include <stdio.h> using namespace std; void doTest() { sqlite3* conn = NULL; //1. 打開數據庫 int result = sqlite3_open("./mytest.db",&conn); if (result != SQLITE_OK) { sqlite3_close(conn); return; } const char* createTableSQL ="CREATE TABLE TESTTABLE (int_col INT, float_col REAL, string_col TEXT)"; sqlite3_stmt* stmt = NULL; int len = strlen(createTableSQL); //2. 准備創建數據表,如果創建失敗,需要用sqlite3_finalize釋放sqlite3_stmt對象,以防止內存泄露。 if (sqlite3_prepare_v2(conn,createTableSQL,len,&stmt,NULL) != SQLITE_OK) { if (stmt) sqlite3_finalize(stmt); sqlite3_close(conn); return; } //3. 通過sqlite3_step命令執行創建表的語句。對於DDL和DML語句而言,sqlite3_step執行正確的返回值 //只有SQLITE_DONE,對於SELECT查詢而言,如果有數據返回SQLITE_ROW,當到達結果集末尾時則返回 //SQLITE_DONE。 if (sqlite3_step(stmt) != SQLITE_DONE) { sqlite3_finalize(stmt); sqlite3_close(conn); return; } //4. 釋放創建表語句對象的資源。 sqlite3_finalize(stmt); printf("Succeed to create test table now.\n"); int insertCount = 10; //5. 構建插入數據的sqlite3_stmt對象。 const char* insertSQL = "INSERT INTO TESTTABLE VALUES(%d,%f,'%s')"; const char* testString = "this is a test."; char sql[1024];
sqlite3_stmt* stmt2 = NULL; for (int i = 0; i < insertCount; ++i) { sprintf(sql,insertSQL,i,i * 1.0,testString); if (sqlite3_prepare_v2(conn,sql,strlen(sql),&stmt2,NULL) != SQLITE_OK) { if (stmt2) sqlite3_finalize(stmt2); sqlite3_close(conn); return; } if (sqlite3_step(stmt2) != SQLITE_DONE) { sqlite3_finalize(stmt2); sqlite3_close(conn); return; } printf("Insert Succeed.\n"); } sqlite3_finalize(stmt2); //6. 為了方便下一次測試運行,我們這里需要刪除該函數創建的數據表,否則在下次運行時將無法 //創建該表,因為它已經存在。 const char* dropSQL = "DROP TABLE TESTTABLE"; sqlite3_stmt* stmt3 = NULL; if (sqlite3_prepare_v2(conn,dropSQL,strlen(dropSQL),&stmt3,NULL) != SQLITE_OK) { if (stmt3) sqlite3_finalize(stmt3); sqlite3_close(conn); return; } if (sqlite3_step(stmt3) == SQLITE_DONE) { printf("The test table has been dropped.\n"); } sqlite3_finalize(stmt3); sqlite3_close(conn); } int main() { doTest(); return 0; }
運行結果
高效的批量數據插入: 在給出操作步驟之前先簡單說明一下批量插入的概念,以幫助大家閱讀其后的示例代碼。事 實上,批量插入並不是什么新的概念,在其它關系型數據庫的C接口API中都提供了一定的 支持,只是接口的實現方式不同而已。縱觀眾多流行的數據庫接口,如OCI(Oracle API)、 MySQL API和PostgreSQL API等,OCI提供的編程接口最為方便,實現方式也最為高效。 SQLite作為一種簡單靈活的嵌入式數據庫也同樣提供了該功能,但是實現方式並不像其他數 據庫那樣方便明顯,它只是通過一種隱含的技巧來達到批量插入的目的,其邏輯如下:
1). 開始一個事務,以保證后面的數據操作語句均在該事物內完成。在SQLite中,如果沒 有手工開啟一個事務,其所有的DML語句都是在自動提交模式下工作的,既每次操作后數 據均被自動提交並寫入磁盤文件。然而在非自動提交模式下,只有當其所在的事物被手工 COMMIT之后才會將修改的數據寫入到磁盤中,之前修改的數據都是僅僅駐留在內存中。顯 而易見,這樣的批量寫入方式在效率上勢必會遠遠優於多迭代式的單次寫入操作。
2). 基於變量綁定的方式准備待插入的數據,這樣可以節省大量的sqlite3_prepare_v2函數調 用次數,從而節省了多次將同一SQL語句編譯成SQLite內部識別的字節碼所用的時間。事實 上,SQLite的官方文檔中已經明確指出,在很多時候sqlite3_prepare_v2函數的執行時間要多 於sqlite3_step函數的執行時間,因此建議使用者要盡量避免重復調用sqlite3_prepare_v2函 數。在我們的實現中,如果想避免此類開銷,只需將待插入的數據以變量的形式綁定到SQL語 句中,這樣該SQL語句僅需調用sqlite3_prepare_v2函數編譯一次即可,其后的操作只是替換 不同的變量數值。
3). 在完成所有的數據插入后顯式的提交事物。提交后,SQLite會將當前連接自動恢復為自動 提交模式。
下面是示例代碼的實現步驟:
1). 創建測試數據表。
2). 通過執行BEGIN TRANSACTION語句手工開啟一個事物。
3). 准備插入語句及相關的綁定變量。
4). 迭代式插入數據。
5). 完成后通過執行COMMIT語句提交事物。
6). 刪除測試表。
#include <sqlite3.h> #include <string> #include <stdio.h> using namespace std; void doTest() { sqlite3* conn = NULL; //1. 打開數據庫 int result = sqlite3_open("./mytest.db",&conn); if (result != SQLITE_OK) { sqlite3_close(conn); return; } const char* createTableSQL ="CREATE TABLE TESTTABLE (int_col INT,float_col REAL, string_col TEXT)"; sqlite3_stmt* stmt = NULL; int len = strlen(createTableSQL); //2. 准備創建數據表,如果創建失敗,需要用sqlite3_finalize釋放sqlite3_stmt對象以防止內存泄露。 if (sqlite3_prepare_v2(conn,createTableSQL,len,&stmt,NULL) != SQLITE_OK) { if (stmt) sqlite3_finalize(stmt); sqlite3_close(conn); return; } //3. 通過sqlite3_step命令執行創建表的語句。對於DDL和DML語句而言,sqlite3_step執行正確的返回值 //只有SQLITE_DONE,對於SELECT查詢而言,如果有數據返回SQLITE_ROW,當到達結果集末尾時則返回 //SQLITE_DONE。 if (sqlite3_step(stmt) != SQLITE_DONE) { sqlite3_finalize(stmt); sqlite3_close(conn); return; } //4. 釋放創建表語句對象的資源。 sqlite3_finalize(stmt); printf("Succeed to create test table now.\n"); //5. 顯式的開啟一個事物。 sqlite3_stmt* stmt2 = NULL; const char* beginSQL = "BEGIN TRANSACTION"; if (sqlite3_prepare_v2(conn,beginSQL,strlen(beginSQL),&stmt2,NULL) !=SQLITE_OK) { if (stmt2) sqlite3_finalize(stmt2); sqlite3_close(conn); return; } if (sqlite3_step(stmt2) != SQLITE_DONE) { sqlite3_finalize(stmt2); sqlite3_close(conn); return; } sqlite3_finalize(stmt2); //6. 構建基於綁定變量的插入數據。 const char* insertSQL = "INSERT INTO TESTTABLE VALUES(?,?,?)"; sqlite3_stmt* stmt3 = NULL; if (sqlite3_prepare_v2(conn,insertSQL,strlen(insertSQL),&stmt3,NULL) !=SQLITE_OK) { if (stmt3) sqlite3_finalize(stmt3); sqlite3_close(conn); return; } int insertCount = 10; const char* strData = "This is a test."; //7. 基於已有的SQL語句,迭代的綁定不同的變量數據 for (int i = 0; i < insertCount; ++i) { //在綁定時,最左面的變量索引值是1。 sqlite3_bind_int(stmt3,1,i); sqlite3_bind_double(stmt3,2,i * 1.0); sqlite3_bind_text(stmt3,3,strData,strlen(strData),SQLITE_TRANSIENT); if (sqlite3_step(stmt3) != SQLITE_DONE) { sqlite3_finalize(stmt3); sqlite3_close(conn); return; } //重新初始化該sqlite3_stmt對象綁定的變量。 sqlite3_reset(stmt3); printf("Insert Succeed.\n"); } sqlite3_finalize(stmt3); //8. 提交之前的事物。 const char* commitSQL = "COMMIT"; sqlite3_stmt* stmt4 = NULL; if (sqlite3_prepare_v2(conn,commitSQL,strlen(commitSQL),&stmt4,NULL) !=SQLITE_OK) { if (stmt4) sqlite3_finalize(stmt4); sqlite3_close(conn); return; } if (sqlite3_step(stmt4) != SQLITE_DONE) { sqlite3_finalize(stmt4); sqlite3_close(conn); return; } sqlite3_finalize(stmt4); //9. 為了方便下一次測試運行,我們這里需要刪除該函數創建的數據表,否則在下次運行時將無法 //創建該表,因為它已經存在。 const char* dropSQL = "DROP TABLE TESTTABLE"; sqlite3_stmt* stmt5 = NULL; if (sqlite3_prepare_v2(conn,dropSQL,strlen(dropSQL),&stmt5,NULL) != SQLITE_OK) { if (stmt5) sqlite3_finalize(stmt5); sqlite3_close(conn); return; } if (sqlite3_step(stmt5) == SQLITE_DONE) { printf("The test table has been dropped.\n"); } sqlite3_finalize(stmt5); sqlite3_close(conn); } int main() { doTest(); return 0; }
數據查詢: 數據查詢是每個關系型數據庫都會提供的最基本功能,下面的代碼示例將給出如何通過 SQLite API獲取數據。
1). 創建測試數據表。
2). 插入一條測試數據到該數據表以便於后面的查詢。
3). 執行SELECT語句檢索數據。
4). 刪除測試表。
#include <sqlite3.h> #include <string> #include <stdio.h> using namespace std; void doTest() { sqlite3* conn = NULL; //1. 打開數據庫 int result = sqlite3_open("D:/mytest.db",&conn); if (result != SQLITE_OK) { sqlite3_close(conn); return; } const char* createTableSQL ="CREATE TABLE TESTTABLE (int_col INT, float_col REAL, string_col TEXT)"; sqlite3_stmt* stmt = NULL; int len = strlen(createTableSQL); //2. 准備創建數據表,如果創建失敗,需要用sqlite3_finalize釋放sqlite3_stmt對象,以防止內存泄露。 if (sqlite3_prepare_v2(conn,createTableSQL,len,&stmt,NULL) != SQLITE_OK) { if (stmt) sqlite3_finalize(stmt); sqlite3_close(conn); return; } //3. 通過sqlite3_step命令執行創建表的語句。對於DDL和DML語句而言,sqlite3_step執行正確的返回值 //只有SQLITE_DONE,對於SELECT查詢而言,如果有數據返回SQLITE_ROW,當到達結果集末尾時則返回 //SQLITE_DONE。 if (sqlite3_step(stmt) != SQLITE_DONE) { sqlite3_finalize(stmt); sqlite3_close(conn); return; } //4. 釋放創建表語句對象的資源。 sqlite3_finalize(stmt); printf("Succeed to create test table now.\n"); //5. 為后面的查詢操作插入測試數據。 sqlite3_stmt* stmt2 = NULL; const char* insertSQL = "INSERT INTO TESTTABLE VALUES(20,21.0,'this is a test.')"; if (sqlite3_prepare_v2(conn,insertSQL,strlen(insertSQL),&stmt2,NULL) !=SQLITE_OK) { if (stmt2) sqlite3_finalize(stmt2); sqlite3_close(conn); return; } if (sqlite3_step(stmt2) != SQLITE_DONE) { sqlite3_finalize(stmt2); sqlite3_close(conn); return; } printf("Succeed to insert test data.\n"); sqlite3_finalize(stmt2); //6. 執行SELECT語句查詢數據。 const char* selectSQL = "SELECT * FROM TESTTABLE"; sqlite3_stmt* stmt3 = NULL; if (sqlite3_prepare_v2(conn,selectSQL,strlen(selectSQL),&stmt3,NULL) !=SQLITE_OK) { if (stmt3) sqlite3_finalize(stmt3); sqlite3_close(conn); return; } int fieldCount = sqlite3_column_count(stmt3); do { int r = sqlite3_step(stmt3); if (r == SQLITE_ROW) { for (int i = 0; i < fieldCount; ++i) { //這里需要先判斷當前記錄當前字段的類型,再根據返回的類型使用不同的API函數 //獲取實際的數據值。 int vtype = sqlite3_column_type(stmt3,i); if (vtype == SQLITE_INTEGER) {
int v = sqlite3_column_int(stmt3,i); printf("The INTEGER value is %d.\n",v); } else if (vtype == SQLITE_FLOAT) { double v = sqlite3_column_double(stmt3,i); printf("The DOUBLE value is %f.\n",v); } else if (vtype == SQLITE_TEXT) { const char* v = (const char*)sqlite3_column_text(stmt3,i); printf("The TEXT value is %s.\n",v); } else if (vtype == SQLITE_NULL) { printf("This value is NULL.\n"); } } } else if (r == SQLITE_DONE) { printf("Select Finished.\n"); break; } else { printf("Failed to SELECT.\n"); sqlite3_finalize(stmt3); sqlite3_close(conn); return; } } while (true); sqlite3_finalize(stmt3); //7. 為了方便下一次測試運行,我們這里需要刪除該函數創建的數據表,否則在下次運行時將無法 //創建該表,因為它已經存在。 const char* dropSQL = "DROP TABLE TESTTABLE"; sqlite3_stmt* stmt4 = NULL; if (sqlite3_prepare_v2(conn,dropSQL,strlen(dropSQL),&stmt4,NULL) != SQLITE_OK) { if (stmt4) sqlite3_finalize(stmt4); sqlite3_close(conn); return; } if (sqlite3_step(stmt4) == SQLITE_DONE) { printf("The test table has been dropped.\n"); } sqlite3_finalize(stmt4); sqlite3_close(conn); } int main() { doTest(); return 0; }
} //輸出結果如下:
//Succeed to create test table now.
//Succeed to insert test data.
//The INTEGER value is 20.
//The DOUBLE value is 21.000000.
//The TEXT value is this is a test.
.//Select Finished.
//The test table has been dropped.
更簡單一點的用法:
回調函數
int sqlite_callback(
void* pvoid, /* 由 sqlite3_exec() 的第四個參數傳遞而來 */
int argc, /* 表的列數或者記錄中包含的字段數目 */
char** argv, /* 指向查詢結果的指針數組, 可以由 sqlite3_column_text() 得到 ,包含每個字段值的指針數組*/
char** col /* 指向表頭名的指針數組, 可以由 sqlite3_column_name() 得到,包含每個字段名稱的指針數組 */
);
在所有的回掉函數中,都要加上return 0;否則失去其回調函數的意義。
注意:其實是sqlite3_exec在查詢表的時候,每查到一條記錄,就會調用一次回調函數,所以才是會顯示出所有數據
int sqlite3_get_table( sqlite3 *db, /* An open database */ const char *zSql, /* SQL to be evaluated */ char ***dbResult, /* Results of the query */ int *pnRow, /* Number of result rows written here */ int *pnColumn, /* Number of result columns written here */ char **pzErrmsg /* Error msg written here */ ); void sqlite3_free_table(char **result);
第1個參數不再多說,看前面的例子。
第2個參數是sql 語句,跟sqlite3_exec 里的sql 是一樣的。是一個很普通的以\0結尾的char*字符串。
第3個參數是查詢結果,它依然一維數組(不要以為是二維數組,更不要以為是三維數組)。它內存布局是:字段名稱,后面是緊接着是每個字段的值。下面用例子來說事。
第4個參數是查詢出多少條記錄(即查出多少行,不包括字段名那行)。
第5個參數是多少個字段(多少列)。
第6個參數是錯誤信息,跟前面一樣,這里不多說了。
pazResult返回的字符串數量實際上是(*pnRow+1)*(*pnColumn),因為前(*pnColumn)個是字段名
{ sqlite3 *db; char *errmsg=NULL; //用來存儲錯誤信息字符串 char ret=0; int my_age=0; //類型根據要提取的數據類型而定 char **dbResult; int nRow=0, nColumn=0; //nRow 查找出的總行數,nColumn 存儲列 ret = sqlite3_open("student.db",&db); if(1 == ret) //數據庫創建未成功 { fprintf(stderr, "Can't open this database: %s\n", sqlite3_errmsg(db)); //用sqlite3_errmsg()得到錯誤字符串 sqlite3_close(db); return -1; } ret=sqlite3_get_table(db, "select * from age;", &dbResult, &nRow, &nColumn, &errmsg); if(NULL!=errmsg) { sqlite3_free_table(dbResult); errmsg=NULL; return -1 } my_age = atoi(dbResult[nColumn]); sqlite3_free_table(dbResult); return 0; }
注意:
sqlite3 *db創建數據庫類型的指針,通過sqlite3_open()函數使db指針指向該數據庫。
注意:
1、 char **dbResult; 字符型的二重指針,將數據庫里sqlite3_get_table()出來的數據以字符的方式給dbResult。
2、select * from age;查詢student數據庫里的age表全部內容。
3、my_age = atoi(dbResult[nColumn]);將查詢出來給dbResult的數據(字符)通過aoti()轉換成整型交給變量my_age供程序中直接應用。
重點:(假設age表里有n個字段)
1、通過select * from age;給dbResult的字符前n個(0,n)為字段名稱(只有計算機認識),dbResult[n]以后分別代表字段的值(包括dbResult[n])。如圖
* * * * .........* (dbResult[0]~[n-1]分別代表字段名)
dbResult[n] [n+1] [n+2] [n+3].....[n+n-1] (dbResult[n]~[n+n-1]分別代表第一條記錄的值)
dbResult[2n] [2n+1] [2n+2] [2n+3]....[2n+n-1](dbResult[2n]~[2n+n-1]分別代表第二條記錄的值)
dbResult[3n] [3n+1] [3n+2] 32n+3]....[3n+n-1](dbResult[3n]~[3n+n-1]分別代表第三條記錄的值)
注:sqlite3_get_table()之后便將以上的n(字段數:簡稱列)給了nColumn這個變量,可直接應用。nRow變量代表共有多少條記錄,可直接應用。
2、通過select * from age where id=0;如果查到0條記錄的話nRow等於0,查到1條記錄的話nRow等於1,假設查到1條數據,舉例:
* * * * .........* (dbResult[0]~[n-1]分別代表字段名)
dbResult[n] [n+1] [n+2] [n+3].....[n+n-1] (dbResult[n]~[n+n-1]分別代表第一條記錄的值)
注:此時dbResult[]只有0~2n-1共2n個字符,此時如果對dbResult[2n]引用的話就會出錯。查詢兩條語句的話以此類推。
* * * * .........* (dbResult[0]~[n-1]分別代表字段名)
dbResult[n] [n+1] [n+2] [n+3].....[n+n-1] (dbResult[n]~[n+n-1]分別代表第一條記錄的值)
dbResult[2n] [2n+1] [2n+2] [2n+3]....[2n+n-1](dbResult[2n]~[2n+n-1]分別代表第二條記錄的值)
注:此時dbResult[]只有0~3n-1可引用。
具體實例:
#include <stdio.h> #include <stdlib.h> #include <sqlite3.h> int create_table(sqlite3 *db) { char *errmsg = NULL; char *sql; sql = "create table if not exists mytable(id integer primary key,name text);"; if(SQLITE_OK != sqlite3_exec(db,sql,NULL,NULL,&errmsg)) { printf("operater failed1 : %s \n",errmsg); exit(0); } } int insert_record(sqlite3 *db) { char *errmsg; char sql[100]; char name[10]; int id; int n; int i; printf("enter the number \n"); scanf("%d",&n); for(i = 0; i < n;i++) { printf("enter the id you want to insert :\n"); scanf("%d",&id); printf("enter the name you want to insert :\n"); scanf("%s",name); sprintf(sql,"insert into mytable(id,name) values(%d,'%s');",id,name); if(SQLITE_OK != sqlite3_exec(db,sql,NULL,NULL,&errmsg)) { printf("operater failed2 : %s \n",errmsg); exit(0); } } } int displaycb(void *para,int n_column,char **column_value,char **column_name) { int i; printf("total column is %d \n",n_column); for(i = 0;i < n_column;i++) { // printf("\t\t %s \t\t %s \n",column_name[i],column_value[i]); printf("column_name : %s ----> column_value : %s \n",column_name[i],column_value[i]); } return 0; } int inquire_usecb(sqlite3 *db) { char *errmsg; char *sql; sql = "select * from mytable;"; if(SQLITE_OK != sqlite3_exec(db,sql,displaycb,NULL,&errmsg)) { printf("operater failed5 : %s \n",errmsg); exit(0); } } int delete_record(sqlite3 *db) { char *errmsg; char sql[100]; int id; printf("enter the id you want to delete\n"); scanf("%d",&id); sprintf(sql,"delete from mytable where id = %d",id); if(SQLITE_OK != sqlite3_exec(db,sql,NULL,NULL,&errmsg)) { printf("operater failed6 :%s \n",errmsg); exit(0); } } int inquire_nocb(sqlite3 *db) { int nrow,ncolumn; char **azresult; char *sql; char *errmsg; int i; sql = "select * from mytable;"; if(SQLITE_OK != sqlite3_get_table(db,sql,&azresult,&nrow,&ncolumn,&errmsg)) { printf("operater failed : %s\n",errmsg); exit(0); } printf("row :%d column :%d\n",nrow,ncolumn); printf("the result of querying : \n"); for(i = 0;i < (nrow + 1) * ncolumn;i++) { printf("%10s",azresult[i]); if((i + 1) % ncolumn == 0) { printf("\n"); } } sqlite3_free_table(azresult); } int main() { sqlite3 *db = NULL; int ret; ret = sqlite3_open("mydatabase.db",&db); if(ret != SQLITE_OK) { perror("open error!\n"); exit(0); } else { printf("you have opened a qulite3 database successfully !\n"); } create_table(db); insert_record(db); inquire_usecb(db); delete_record(db); inquire_nocb(db); sqlite3_close(db); return 0; }
運行結果: