SQlite数据库的C编程接口(三) 预处理语句(Prepared Statements)

sqlite3数据库连接完成之后,就可以执行sql命令了。下面将要介绍的preparestep函数都是用来操作和执行sql命令的。

典型的函数操作流程(伪代码):

  1. /*createastatementfromansqlstring*/
  2. sqlite3_stmt*stmt=NULL;
  3. sqlite3_prepare_v2(db,sql_str,sql_str_len,&stmt,NULL);
  4. /*usethestatementasmanytimesasrequired*/
  5. while(...)
  6. {
  7. /*bindanyparametervalues*/
  8. sqlite3_bind_xxx(stmt,param_idx,param_value...);
  9. ...
  10. /*executestatementandstepovereachrowoftheresultset*/
  11. while(sqlite3_step(stmt)==sqlITE_ROW)
  12. /*extractcolumnvaluesfromthecurrentresultrow*/
  13. col_val=sqlite3_column_xxx(stmt,col_index);
  14. }
  15. /*resetthestatementsoitmaybeusedagain*/
  16. sqlite3_reset(stmt);
  17. sqlite3_clear_bindings(stmt);/*optional*/
  18. }
  19. /*destroyandreleasethestatement*/
  20. sqlite3_finalize(stmt);
  21. stmt=NULL;

这段程序首先调用sqlite3_prepare_v2函数,将一个sql命令字符串转换成一条prepared语句,存储在sqlite3_stmt类型结构体中。随后调用sqlite3_bind_xxx函数给这条prepared语句绑定参数。然后不停的调用sqlite3_step函数执行这条prepared语句,获取结果集中的每一行数据,从每一行数据中调用qlite3_column_xxx函数获取有用的列数据,直到结果集中所有的行都被处理完毕。

prepared语句可以被重置(调用sqlite3_reset函数),然后可以重新绑定参数之后重新执行。sqlite3_prepare_v2函数代价昂贵,所以通常尽可能的重用prepared语句。最后,这条prepared语句确实不在使用时,调用sqlite3_finalize函数释放所有的内部资源和sqlite3_stmt数据结构,有效删除prepared语句。

预处理(Prepare

copy
    intsqlite3_prepare(
  1. sqlite3*db,/*Databasehandle*/
  2. constchar*zsql,/*sqlstatement,UTF-8encoded*/
  3. intnByte,0); background-color:inherit">/*Maximumlengthofzsqlinbytes.*/
  4. sqlite3_stmt**ppStmt,0); background-color:inherit">/*OUT:Statementhandle*/
  5. char**pzTail/*OUT:Pointertounusedportionofzsql*/
  6. );
  7. intsqlite3_prepare_v2(
  8. intsqlite3_prepare16(
  9. constvoid*zsql,UTF-16encoded*/
  10. void**pzTailintsqlite3_prepare16_v2(
  11. );

这些函数的作用是将sql命令字符串转换为prepared语句。参数db是由sqlite3_open函数返回的指向数据库连接的指针。参数zsqlUTF-8或者UTF-16编码的sql命令字符串,参数nBytezsql字节长度。如果nByte为负值,则prepare函数自动计算出zsql的字节长度,不过要确保zsql传入的是以NULL结尾的字符串。如果sql命令字符串中只包含一条sql语句,那么它没有必要以“;”结尾。参数ppStmt是一个指向指针的指针,用来传回一个指向新建的sqlite3_stmt结构体的指针,sqlite3_stmt结构体里面保存有转换好的sql语句。如果sql命令字符串包含多条sql语句,同时参数pzTail不为NULL,那么它将指向sql命令字符串中的下一条sql语句。上面4个函数中的v2版本是加强版,与原始版函数参数相同,不同的是函数内部对于sqlite3_stmt结构体的表现上。细节不去理会,尽量使用v2版本。

sqlite3_prepare函数转换一条语句完毕之后,可以给这条语句绑定参数。语句参数允许我们插入一个特殊的占位符,然后给这个参数占位符绑定指定的值,然后再执行它。执行完成之后,还可以重置语句,绑定新的参数值,再重新执行该语句。像INSERT操作,每次执行INSERT命令,绑定不同的值,插入不同的数据。参数绑定是一个有点复杂的深入话题,在下一节中学习。

步进(Step

copy @H_179_403@
    intsqlite3_step(sqlite3_stmt*);

sqlite3_prepare函数sql命令字符串解析并转换为一系列的命令字节码,这些字节码最终被传送到sqlite3的虚拟数据库引擎(VDBE: Virtual Database Engine)中执行,完成这项工作的是sqlite3_step函数。比如一个SELECT查询操作,sqlite3_step函数的每次调用都会返回结果集中的其中一行,直到再没有有效数据行了。每次调用sqlite3_step函数如果返回sqlITE_ROW,代表获得了有效数据行,可以通过sqlite3_column函数提取某列的值。如果调用sqlite3_step函数返回sqlITE_DONE,则代表prepared语句已经执行到终点了,没有有效数据了。很多命令第一次调用sqlite3_step函数就会返回sqlITE_DONE,因为这些sql命令不会返回数据。对于INSERT,UPDATE,DELETE命令,会返回它们所修改的行号——一个单行单列的值。

结果列(Result Columns

copy
    intsqlite3_column_count(sqlite3_stmt*pStmt);

返回结果集的列数。

copy
    char*sqlite3_column_name(sqlite3_stmt*,intN);
  1. void*sqlite3_column_name16(sqlite3_stmt*,87); background-color:inherit; font-weight:bold">intN);

返回结果集中指定列的列名,列的序号以0开始。比如一条sql语句:SELECT pid AS person_id...,那么调用sqlite3_column_name函数返回结果集中第0列的列名就是person_id。返回的字符串指针将一直有效,直到再次调用sqlite3_column_name函数并再次读取该列的列名时失效。

copy
    intsqlite3_column_type(sqlite3_stmt*,87); background-color:inherit; font-weight:bold">intiCol);

函数返回结果集中指定列的本地存储类型,如sqlITE_INTEGER,sqlITE_FLOAT,sqlITE_TEXT,sqlITE_BLOB,sqlITE_NULL。为了获取正确的类型,该函数应该在任何试图提取数据的函数调用之前被调用sqlite3数据库允许不同类型的数据存储在同一列中,所以对于不同行的相同索引的列调用函数获取的列类型可能会不同。

copy
    void*sqlite3_column_blob(sqlite3_stmt*,87); background-color:inherit; font-weight:bold">intiCol);

返回一个指针,指向给定列的BLOB类型值。

copy
    doublesqlite3_column_double(sqlite3_stmt*,87); background-color:inherit; font-weight:bold">intiCol);

从给定列返回一个64位浮点值。

copy
    intsqlite3_column_int(sqlite3_stmt*,87); background-color:inherit; font-weight:bold">intiCol);

从给定列返回一个32位有符号整数,如果该列中包含的整型值无法用32位数值表示,那它将会在没有任何警告的情况下被截断。

copy
    sqlite3_int64sqlite3_column_int64(sqlite3_stmt*,87); background-color:inherit; font-weight:bold">intiCol);

从给定列返回一个64位有符号整数。

copy
    constunsignedchar*sqlite3_column_text(sqlite3_stmt*,87); background-color:inherit; font-weight:bold">intiCol);
  1. void*sqlite3_column_text16(sqlite3_stmt*,87); background-color:inherit; font-weight:bold">intiCol);

返回一个指针,指向给定列的UTF-8或者UTF-16编码的字符串,该字符串以NULL结尾。

copy
    sqlite3_value*sqlite3_column_value(sqlite3_stmt*,87); background-color:inherit; font-weight:bold">intiCol);

返回一个指针,指向一个无保护的sqlite3_value结构,该结构无法进行安全的数据类型转换,所以无法调用sqlite3_value_xxx函数从这个结构体中提取原始数值。如果想提取原始数值,只能调用其它的sqlite3_column_xxx函数。对于该函数返回的指针,安全的用法是以它为参数调用sqlite3_bind_value函数给一个prepared语句绑定参数,或者以它为参数调用sqlite3_result_value函数得到一个用户自定义sql函数的返回值。

对于这些sqlite3_column_xxx函数返回的指针,当再次调用sqlite3_column_xxx函数并操作相同的列的时失效,或者在sqlite3_step、sqlite3_reset、sqlite3_finalize函数调用之后失效。

如果提取列值时使用的sqlite3_column_xxx函数版本与原始值的本地数据类型不同,sqlite数据库将进行转换。转换原则:

copy
    intsqlite3_value_bytes(sqlite3_value*);
  1. intsqlite3_value_bytes16(sqlite3_value*);

对于BLOBtext类型,sqlite3_column_blobsqlite3_column_text函数将会返回一个buffer指针。通过sqlite3_value_bytes函数可以得到buffer字节长度,对于text类型,这个字节长度将包括一个字符串结尾符。

需要注意的是:假如先调用sqlite3_column_text函数获取一个指向UTF-8编码的字符串指针,之后又调用sqlite3_column_bytes16在相同的列上获取buffer大小,那么该列的字符串将会从UTF-8编码转换为UTF-16编码,导致之前由sqlite3_column_text函数返回的指针失效。

正确做法是提取值时的函数获取buffer大小的函数,以相同类型匹配使用,如:

copy
    /*correctlyextractablob*/
  1. buf_ptr=sqlite3_column_blob(stmt,n);
  2. buf_len=sqlite3_column_bytes(stmt,n);
  3. /*correctlyextractaUTF-8encodedstring*/
  4. buf_ptr=sqlite3_column_text(stmt,0); background-color:inherit">/*correctlyextractaUTF-16encodedstring*/
  5. buf_ptr=sqlite3_column_text16(stmt,255); color:inherit; line-height:18px"> buf_len=sqlite3_column_bytes16(stmt,n);

重置与完成(Reset and Finalize

copy
    intsqlite3_reset(sqlite3_stmt*pStmt);

sqlite3_step函数调用返回sqlITE_DONE时,则代表这条语句已经完成执行,这时如果还想重用这条prepared语句,就需要调用sqlite3_reset函数进行重置。或者,比如我们只想提取结果集的前六行数据,那么我们就可以连续调用6次sqlite3_step函数,之后调用sqlite3_reset函数重置prepared语句,以备下一次使用。

copy
    intsqlite3_finalize(sqlite3_stmt*pStmt);

销毁prepared语句,释放资源。在关闭数据库连接之前,对于不再使用的prepared语句,一定要调用sqlite3_finalize函数进行销毁,

语句(状态)转换(Statement Transitions

一条语句可以处于不同状态,对于一条新的或者刚刚被reset的语句,它们处于“ready”状态,代表它们已经准备好执行,但还没有开始执行。一条语句也有可能处于“running”状态,表明这条语句已经开始执行,但还没有完成。还有一种状态叫做“done”,表明一条语句已经执行完成。

对于有些API函数,只能在某条语句处于特定状态下才可以执行,比如sqlite3_bind_xxx函数,只有在一条语句处于“ready”状态时才可以被调用,否则函数将会返回sqlITE_MISUSE错误码。下图展示了一条语句所处于的不同状态,以及不同状态之间是如何转换的。

示例代码

(1)

copy
    sqlite3_stmt*stmt=NULL;
  1. /*...opendatabase...*/
copy
    rc=sqlite3_prepare_v2(db,"CREATETABLEtbl(strTEXT)",-1,NULL);
  1. if(rc!=sqlITE_OK)exit(-1);
copy
    rc=sqlite3_step(stmt);
  1. if(rc!=sqlITE_DONE)exit(-1);
  2. sqlite3_finalize(stmt);
  3. /*...closedatabase...*/

CREATE TABLE语句没有返回值,调用sqlite3_step函数执行这条语句,最后在关闭数据库之前调用sqlite3_finalize销毁这条语句。

(2)

copy
    char*data=NULL;
  1. copy
      "SELECTstrFROMtblORDERBY1",153); background-color:inherit; font-weight:bold">if(rc!=sqlITE_OK)exit(-1);
    1. while(sqlite3_step(stmt)==sqlITE_ROW){
    2. data=(char*)sqlite3_column_text(stmt,0);
    3. printf("%s\n",data?data:"[NULL]");
    4. /*...closedatabase...*/

    这段代码循环提取tbl的所有行,并把每一行第0列值打印出来。

    相关文章

    安装 在Windows上安装SQLite。 访问官网下载下Precompliled Binaries for Windows的两个压缩包。 创建s...
    一、安装 下载地址:http://www.sqlite.org/download.html 将Precompiled Binaries for Windows下的包下...
    实例: 会员信息管理 功能:1.查看数据库 2.清空数据库 3.增加会员 4.删除会员 5.更新会员 6.查找会员  ...
    关于SQLite SQLite是一个轻量的、跨平台的、开源的数据库引擎,它的在读写效率、消耗总量、延迟时间和整...