一、獲取表數據方法:
1.使用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個參數是查詢結果,它依然一維數組(不要以為是二維數組,更不要以為是三維數組)。它內存布局是:resultp的字段值是連續的,第一行是字段名稱,后面是緊接着是每個字段的值。從第0索引到第 nColumn - 1索引都是字段名稱,從第nColumn 索引開始,后面都是字段值,它把一個二維的表(傳統的行列表示法)用一個扁平的形式來表示。
//第n列的名稱,存放於resultp [nrow]
//第n行第m列的數據,存放於resultp [(nrow+ 1) * nColumn + m]
下面用例子來說事。
第4個參數是查詢出多少條記錄(即查出多少行)。
第5個參數是多少個字段(多少列)。
第6個參數是錯誤信息。
2.使用回調函數,直接執行sql語句:
int sqlite3_exec(sqlite3*, const char *sql,sqlite3_callback, void *,char **errmsg);
第1個參數不再說了,是前面open函數得到的指針。說了是關鍵數據結構。
第2個參數constchar *sql 是一條 sql 語句,以/0結尾。
第3個參數sqlite3_callback 是回調,當這條語句執行之后,sqlite3會去調用你提供的這個函數。(若是封裝成類的,必須將回調函數設置為static,否則this指針不對應無法調用)
第4個參數void* 是你所提供的指針,你可以傳遞任何一個指針參數到這里,這個參數最終會傳到回調函數里面,如果不需要傳遞指針給回調函數,可以填NULL。等下我們再看回調函數的寫法,以及這個參數的使用。
第5個參數char** errmsg 是錯誤信息。注意是指針的指針。sqlite3里面有很多固定的錯誤信息。執行 sqlite3_exec 之后,執行失敗時可以查閱這個指針(直接 printf(“%s/n”,errmsg))得到一串字符串信息,這串信息告訴你錯在什么地方。sqlite3_exec函數通過修改你傳入的指針的指針,把你提供的指針指向錯誤提示信息,這樣sqlite3_exec函數外面就可以通過這個 char*得到具體錯誤提示。
其中callback函數可以這樣寫:
int callback(void*para , int nCount, char** pValue, char** pName) { /***************************************************************************** sqlite 每查到一條記錄,就調用一次這個回調 para是你在 sqlite3_exec 里傳入的 void * 參數, 通過para參數,你可以傳入一些特殊的指針(比如類指 針、結構指針),然后在這里面強制轉換成對應的類型(這里面是void*類型,必須強制轉換成你的類型才可用)。然后操作這些數據 nCount是這一條(行)記錄有多少個字段 (即這條記錄有多少列) char ** pValue 是個關鍵值,查出來的數據都保存在這里,它實際上是個1維數組(不要以為是2維數組),每一個元素都是一個 char* 值,是一個字段內容(用字符串來表示,以/0結尾) char ** pName 跟pValue是對應的,表示這個字段的字段名稱, 也是個1維數組 *****************************************************************************/
string s; for(int i=0;i<nCount;i++){ s+=pName[i]; s+=":"; s+=pValue[i]; s+="\n"; } cout<<s<<endl; return 0; }
3.通過sqlite3_prepare、sqlite3_step一系列操作
雖然回調顯得代碼整齊,又想避免sqlite3_get_table之后麻煩的一維數組遍歷,那么利用sqlite3_prepare_v2執行sql select語句,讓后sqlite3_step遍歷select執行的返回結果是一個非常方便的solution. 當然,你必須要明白sqlite3_prepare_v2不僅僅能夠執行table的query selection,也能方便地進行sql Delete, Insert, Update等其他一些操作。它能幫你把sql語句的執行操作變的更加優雅。
相關函數分析:
1. int sqlite3_prepare_v2(
sqlite3*db, /* Database handle(數據庫指針) */
const char*zSql, /* SQL statement, UTF-8encoded(使用UTF-8編碼的SQL語句) */
int nByte, /* Maximum length of zSql inbytes.(如果nByte小於0,則函數取出zSql中從開始到第一個0終止符的內容;如果nByte為非負數,那么它就是這個函數能從zSql中讀取的最大字節數。zSql在第一次遇見/000或u000的時候終止) */
sqlite3_stmt *ppStmt, / OUT:Statement handle(能夠使用sqlite3_step()執行的編譯好的准備語句的指針,如果錯誤發生,它被置為NULL,例如輸入一個不正確的sql語句。調用過程必須負責在編譯好的sql語句完成使用后,調用sqlite3_finalize()刪除它。 */
const char**pzTail /* OUT: Pointer to unusedportion of zSql(當zSql在遇見終止符或者達到設定的nByte結束后,如果還有剩余的內容,那么這些剩余的內容將被存放到pzTail中,不包含終止符) */
);
2. int sqlite3_step(sqlite3_stmt*);
sqlite3_step的返回值取決於創建sqlite3_stmt參數所使用的函數,假如使用老版本的接口sqlite3_prepare()或sqlite3_prepare16(),返回值會是SQLITE_BUSY、SQLITE_DONE, SQLITE_ROW, SQLITE_ERROR 或 SQLITE_MISUSE;而v2版本的接口sqlite3_prepare_v2()和sqlite3_prepare16_v2()除了這些值以外,還可能返回擴展狀態碼。
3 . int sqlite3_reset(sqlite3_stmt *pStmt);
sqlite3_reset 重置一個准備語句(prepared statement)對象到它的初始狀態,准備被重新執行。在V3.6.23.1以后,sqlite3_step()將會自動調用sqlite3_reset。
int sqlite3_finalize(sqlite3_stmt *pStmt);
sqlite3_finalize — 銷毀一個准備語句(prepared statement)對象,在需要時執行這個銷毀函數以防止內存泄露。在准備語句對象為空指針時調用這個函數也沒有什么影響。
4 . sqlite3_column —並不存在sqlite3_column這個函數,它只是一個前綴,下面是相關的具體函數:
const void*sqlite3_column_blob(sqlite3_stmt*, int
intsqlite3_column_bytes(sqlite3_stmt*, int iCol);
intsqlite3_column_bytes16(sqlite3_stmt*, int iCol);
doublesqlite3_column_double(sqlite3_stmt*, int iCol);
intsqlite3_column_int(sqlite3_stmt*, int iCol);
sqlite3_int64sqlite3_column_int64(sqlite3_stmt*, int
constunsigned char sqlite3_column_text(sqlite3_stmt,
const void*sqlite3_column_text16(sqlite3_stmt*, int
intsqlite3_column_type(sqlite3_stmt*, int iCol);
sqlite3_value*sqlite3_column_value(sqlite3_stmt*, int iCol);
上面的函數用來得到結果集當前行中某一列的值,列索引從0開始。
注意點:
1. 調用sqlite3_prepare()將SQL語句編譯為sqlite內部一個結構體(sqlite3_stmt).該結構體中包含了將要執行的的SQL語句的信息.
2. 如果需要傳入參數,在SQL語句中用’?’作為占位符,再調用sqlite3_bind_XXX()函數將對應的參數傳入.
3. 調用sqlite3_step(),這時候SQL語句才真正執行.注意該函數的返回值,SQLITE_DONE和SQLITE_ROW都是表示執行成功,不同的是SQLITE_DONE表示沒有查詢結果,像UPDATE,INSERT這些SQL語句都是返回SQLITE_DONE,SELECT查詢語句在查詢結果不為空的時候返回SQLITE_ROW,在查詢結果為空的時候返回SQLITE_DONE.(SQLITE_DONE盡管被歸結為error code,但是不代表着執行錯誤,而是意味着查詢結果為空。SQLITE_OK表示執行結束並有查詢結果。)
4. 每次調用sqlite3_step()的時候,只返回一行數據,使用sqlite3_column_XXX()函數來取出這些數據.要取出全部的數據需要反復調用sqlite3_step(). (注意,在bind參數的時候,參數列表的index從1開始,而取出數據的時候,列的index是從0開始).
5. 在SQL語句使用完了之后要調用sqlite3_finalize()來釋放stmt占用的內存.該內存是在sqlite3_prepare()時分配的.
6. 如果SQL語句要重復使用,可以調用sqlite3_reset()來清楚已經綁定的參數.
7. 對於新的應用不建議使用sqlite3_prepare(),而應使用架構更新的程序sqlite3_prepare_v2()代替。
8. 對於很多SQL語句來說,執行sqlite3_prepare()的時間等於或者超過執行sqlite3_step()的時間。所以避免頻繁調用sqlite3_prepare()可以顯著提升性能。
4.利用事務(BEGIN;COMMIT;)批量操作
如果要進行大量的操作,比如要插入10000條數據,如果逐條執行SQL語句,則消耗的時間非常長。采用事務的方式批量處理,可以極大程度提升操作速度(我用1000條記錄實驗了一下,速度提高了500倍以上)。sqlite3_exec可以執行任何sql語句,包括事務(“BEGIN TRANSACTION”)、回滾(“ROLLBACK”)和提交(“COMMIT”)等等
//插入條數據(在Begin和Commit之間批量操作,可以大幅度提高效率)
result =sqlite3_exec(db, "BEGIN;",0, 0, 0); for (int i=0; i<10000; i++) { //插入一條數據
result = sqlite3_exec(db, "INSERT INTOMyTable (MyText, MyDate, MyTime, MyFloat) VALUES ('---上班好遠!', '2012-03-23', '9:00:00', 1000);", 0, 0, 0); } result =sqlite3_exec(db, "COMMIT;",0, 0, 0); .
string strSql; strSql+="begin;\n"; for (int i=0;i<100;i++) { strSql+="insert into MyTable values(null,'heh');\n"; } strSql+="commit;"; //cout<<strSql<<endl;
nResult = sqlite3_exec(db,strSql.c_str(),NULL,NULL,&errmsg);
總結:sqlite3_exec()接口是一個方便的包裝器,調用一個方法便可執行上面的四個步驟。傳遞到sqlite3_exec()中的回調函數將用於處理每行結果集。 sqlite3_get_table()是另一個方便的包裝器,同樣可以用上述的四個步驟。與 sqlite3_exec()不同的是,sqlite3_get_table()將結果存儲在堆存儲器里而非調用回調函數。
二、SQLite錯誤碼
在SQLite中,執行SQL語句的sqlite3_exec()和sqlite3_prepare()兩個核心方法的返回值都是一個整型數據,因此,當程序執行出現錯誤時,我們可以根據執行返回的整型數據來判斷錯誤發生的原因。以下就是SQLite的錯誤碼:
1 #define SQLITE_OK 0 /* 成功 | Successful result */
2 /* 錯誤碼開始 */
3 #define SQLITE_ERROR 1 /* SQL錯誤 或 丟失數據庫 | SQL error or missing database */
4 #define SQLITE_INTERNAL 2 /* SQLite 內部邏輯錯誤 | Internal logic error in SQLite */
5 #define SQLITE_PERM 3 /* 拒絕訪問 | Access permission denied */
6 #define SQLITE_ABORT 4 /* 回調函數請求取消操作 | Callback routine requested an abort */
7 #define SQLITE_BUSY 5 /* 數據庫文件被鎖定 | The database file is locked */
8 #define SQLITE_LOCKED 6 /* 數據庫中的一個表被鎖定 | A table in the database is locked */
9 #define SQLITE_NOMEM 7 /* 某次 malloc() 函數調用失敗 | A malloc() failed */
10 #define SQLITE_READONLY 8 /* 嘗試寫入一個只讀數據庫 | Attempt to write a readonly database */
11 #define SQLITE_INTERRUPT 9 /* 操作被 sqlite3_interupt() 函數中斷 | Operation terminated by sqlite3_interrupt() */
12 #define SQLITE_IOERR 10 /* 發生某些磁盤 I/O 錯誤 | Some kind of disk I/O error occurred */
13 #define SQLITE_CORRUPT 11 /* 數據庫磁盤映像不正確 | The database disk image is malformed */
14 #define SQLITE_NOTFOUND 12 /* sqlite3_file_control() 中出現未知操作數 | Unknown opcode in sqlite3_file_control() */
15 #define SQLITE_FULL 13 /* 因為數據庫滿導致插入失敗 | Insertion failed because database is full */
16 #define SQLITE_CANTOPEN 14 /* 無法打開數據庫文件 | Unable to open the database file */
17 #define SQLITE_PROTOCOL 15 /* 數據庫鎖定協議錯誤 | Database lock protocol error */
18 #define SQLITE_EMPTY 16 /* 數據庫為空 | Database is empty */
19 #define SQLITE_SCHEMA 17 /* 數據結構發生改變 | The database schema changed */
20 #define SQLITE_TOOBIG 18 /* 字符串或二進制數據超過大小限制 | String or BLOB exceeds size limit */
21 #define SQLITE_CONSTRAINT 19 /* 由於約束違例而取消 | Abort due to constraint violation */
22 #define SQLITE_MISMATCH 20 /* 數據類型不匹配 | Data type mismatch */
23 #define SQLITE_MISUSE 21 /* 不正確的庫使用 | Library used incorrectly */
24 #define SQLITE_NOLFS 22 /* 使用了操作系統不支持的功能 | Uses OS features not supported on host */
25 #define SQLITE_AUTH 23 /* 授權失敗 | Authorization denied */
26 #define SQLITE_FORMAT 24 /* 附加數據庫格式錯誤 | Auxiliary database format error */
27 #define SQLITE_RANGE 25 /* 傳遞給sqlite3_bind()的第二個參數超出范圍 | 2nd parameter to sqlite3_bind out of range */
28 #define SQLITE_NOTADB 26 /* 被打開的文件不是一個數據庫文件 | File opened that is not a database file */
29 #define SQLITE_ROW 100 /* sqlite3_step() 已經產生一個行結果 | sqlite3_step() has another row ready */
30 #define SQLITE_DONE 101 /* sqlite3_step() 完成執行操作 | sqlite3_step() has finished executing */
加載數據庫文件若路徑不正確,程序並不會報錯,所以這個地方需要特別注意。若出現SQLITE_ERROR 錯誤可能為數據庫路徑加載不正確,dll路徑或exe路徑可以用下面方法獲取:
HMODULE hMod = GetModuleHandle(_T("mydll.dll")); if(hMod != NULL) { TCHAR szBuffer[MAX_PATH] = {0}; GetModuleFileName(hMod, szBuffer, sizeof(szBuffer) / sizeof(TCHAR) - 1); }
通用的辦法為:
TCHAR szDLLFolder[MAX_PATH + 1]; GetModuleFileName(AfxGetApp()->m_hInstance, szDLLFolder, MAX_PATH); //或GetModuleFileName(::AfxGetResourceHandle(), szDLLFullPath, MAX_PATH); //或GetModuleFileName(::AfxGetInstanceHandle(), szDLLFullPath, MAX_PATH);
三、中文出現亂碼如何解決:
我參考的是這篇文章:https://www.2cto.com/database/201411/354891.html
理論是:sqlite使用的是UTF-8,C++中用的字符串是ascii或unicode編碼。
所以使用時候要進行轉化。插入中文時候要轉化為UTF-8,讀取時候再轉化回來。
下面是轉化函數,參考文章:https://www.2cto.com/database/201411/354891.html
//UTF-8轉Unicode
std::wstring Utf82Unicode(const std::string& utf8string) { int widesize = ::MultiByteToWideChar(CP_UTF8, 0, utf8string.c_str(), -1, NULL, 0); if (widesize == ERROR_NO_UNICODE_TRANSLATION) { throw std::exception("Invalid UTF-8 sequence."); } if (widesize == 0) { throw std::exception("Error in conversion."); } std::vector<wchar_t> resultstring(widesize); int convresult = ::MultiByteToWideChar(CP_UTF8, 0, utf8string.c_str(), -1, &resultstring[0], widesize); if (convresult != widesize) { throw std::exception("La falla!"); } return std::wstring(&resultstring[0]); } //unicode 轉為 ascii
std::string WideByte2Acsi(std::wstring& wstrcode){ int asciisize = ::WideCharToMultiByte(CP_OEMCP, 0, wstrcode.c_str(), -1, NULL, 0, NULL, NULL); if (asciisize == ERROR_NO_UNICODE_TRANSLATION) { throw std::exception("Invalid UTF-8 sequence."); } if (asciisize == 0) { throw std::exception("Error in conversion."); } std::vector<char> resultstring(asciisize); int convresult = ::WideCharToMultiByte(CP_OEMCP, 0, wstrcode.c_str(), -1, &resultstring[0], asciisize, NULL, NULL); if (convresult != asciisize) { throw std::exception("La falla!"); } return std::string(&resultstring[0]); } //utf-8 轉 ascii
std::string UTF_82ASCII(std::string& strUtf8Code){ using namespace std; string strRet = ""; //先把 utf8 轉為 unicode
wstring wstr = Utf82Unicode(strUtf8Code); //最后把 unicode 轉為 ascii
strRet = WideByte2Acsi(wstr); return strRet; } //ascii 轉 Unicode
std::wstring Acsi2WideByte(std::string& strascii){ using namespace std; int widesize = MultiByteToWideChar(CP_ACP, 0, (char*)strascii.c_str(), -1, NULL, 0); if (widesize == ERROR_NO_UNICODE_TRANSLATION) { throw std::exception("Invalid UTF-8 sequence."); } if (widesize == 0) { throw std::exception("Error in conversion."); } std::vector<wchar_t> resultstring(widesize); int convresult = MultiByteToWideChar(CP_ACP, 0, (char*)strascii.c_str(), -1, &resultstring[0], widesize); if (convresult != widesize) { throw std::exception("La falla!"); } return std::wstring(&resultstring[0]); } //Unicode 轉 Utf8
std::string Unicode2Utf8(const std::wstring& widestring){ using namespace std; int utf8size = ::WideCharToMultiByte(CP_UTF8, 0, widestring.c_str(), -1, NULL, 0, NULL, NULL); if (utf8size == 0) { throw std::exception("Error in conversion."); } std::vector<char> resultstring(utf8size); int convresult = ::WideCharToMultiByte(CP_UTF8, 0, widestring.c_str(), -1, &resultstring[0], utf8size, NULL, NULL); if (convresult != utf8size) { throw std::exception("La falla!"); } return std::string(&resultstring[0]); } //ascii 轉 Utf8
std::string ASCII2UTF_8(std::string& strAsciiCode) { using namespace std; string strRet(""); //先把 ascii 轉為 unicode
wstring wstr = Acsi2WideByte(strAsciiCode); //最后把 unicode 轉為 utf8
strRet = Unicode2Utf8(wstr); return strRet; }