一片云雾

写博客挺浪费时间的……
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

C/C++中Sqlite使用简介

Posted on 2012-03-23 16:01  一片云雾  阅读(13218)  评论(0编辑  收藏  举报

一、简介

SQLite是一个基于文件的轻量级数据库,但功能还挺强,速度也很快,对于小型数据库应用开发绝对够用了。使用起来也非常方便,下面的介绍可以看出来,使用起来真的非常简单。

 

二、官方网站

 

Sqlite的官方网站www.sqlite.org,在该网站可以下载最新的sqlite版本。

 

 

三、辅助工具

     Sqlite数据库的管理工具有SQLiteManager、SqliteAdmin等。

SqliteManager只有英文版(需要注册或者破解方可使用)。其自带的帮助文档有SQL语句的详细介绍,对于不熟悉Sql语句的人来说很方便。而且它的很多操作都有自动的SQL语句提示,对于不常使用数据库的人来说也很好用。

SqliteAdmin有绿色中文版,功能相对于SqliteManager略少,对于熟悉Sql语句的人,该版本够用了。

另外说明一下,在我们的程序中以UTF-8方式(大部分人应该都是以这种方式打开的)打开数据库进行操作时,两种软件的表现如下:

如果插入数据库的数据,汉字是以ANSI字符存储的话,那么在SqliteAdmin中打开时,会显示正常汉字,而在SqliteManager中,会显示为乱码。

如果插入数据库的数据,汉字是以UTF-8字符存储的话,那么在SqliteManager中打开时,会显示正常汉字,而在SqliteAdmin中,会显示为乱码。

很明显,SqliteManager的表现,要正常一些。SqliteManager更加值得推荐,不过需要安装和破解。SqliteAdmin是绿色的,但面对ANSI汉字,可能在某些时候力不从心,而且功能没有SqliteManager强大。

 

四、C/C++使用前准备

直接将sqlite3.h和sqlite3.c加入自己的C/C++工程中,即可使用sqlite3。

使用VC的用户特别注意,大多数VC工程,默认都是使用预编译头的,需要对sqlite3.c这个文件,去掉预编译头,方可编译通过。去掉的方法是在工程中的文件视图中,右键点击sqlite3.c这个文件,打开“配置属性”中的“C/C++”,找到“预编译头”,设置为不使用预编译头即可。

 

五、打开关闭数据库

Sqlite支持UTF-8和UTF-16,不过它居然不支持C/C++程序中最常用的ANSI。因此数据库路径中如果包含中文字符的话,需要将路径转换成相应的字符格式。

1、以UTF-8方式打开(如果数据库路径中包含中文字符,需要进行ANSI到UTF8的转换)

示例中的路径是全英文字符,ANSI和UTF8是相同的,因此省去转化。

//打开数据库
sqlite3 *db = NULL;
int result = sqlite3_open("c:\\abc.db", &db);
if (SQLITE_OK != result)
{
    return;
}
 
//关闭数据库
sqlite3_close(db);

 

2、以UTF-16方式打开

如要以UTF-16方式打开,把打开数据库的语句改为"sqlite3_open16(L"c:\\abc.db", &db)"即可。

注意:如果以UTF-8方式打开数据库,那么以后任何数据库操作,都必须以UTF-8方式进行。UTF-16也同理。

一个比较常见的错误是,以UTF-8打开了数据库,但却插入了ANSI的汉字。犯下这种错误是很不经意的,因为对于英文字符,UTF-8和ANSI是一模一样的嘛!一个我所知的非常典型的后果就是使用模糊查找语句进行检索时,可能查询的结果,并不是你想要的。

 

六、执行SQL语句

执行SQL语句可以调用sqlite3_exec函数,一般来说,如果不需要返回的数据和错误信息,执行方式如下:

sqlite3_exec(db, strSQL, 0, 0, 0);

 

 1、创建表

假设创建如下图所示的数据库:

其SQL语句为:

CREATE TABLE [MyTable] ([ID] INTEGER PRIMARY KEY NOT NULL,[MyText] TEXT NULL, [MyDate] DATE NOT NULL, [MyTime] TIME NULL,[MyFloat] FLOAT NULL)
 
2、插入记录

下面是插入一条记录的示例SQL语句。

INSERT INTO MyTable (MyText, MyDate, MyTime, MyFloat) VALUES ('---上班好远!', '2012-03-23', '9:00:00', 1000)
 
3、更新记录

下面是更新若干条记录的示例SQL语句。

UPDATE MyTable SET MyText='真的吗?', MyTime='10:00:00' WHERE ID >=0 AND ID <=20
 
4、删除记录

下面是删除若干条记录的示例SQL语句。

DELETE FROM MyTable WHERE ID >=3 AND ID <=5
 
5、批量操作

如果要进行大量的操作,比如要插入10000条数据,如果逐条执行SQL语句,则消耗的时间非常长。采用事务的方式批量处理,可以极大程度提升操作速度(我用1000条记录实验了一下,速度提高了500倍以上)。

下面是一个批量插入10000条数据的代码示例:

 

//插入条数据(在Begin和Commit之间批量操作,可以大幅度提高效率)
result = sqlite3_exec(db, "BEGIN;", 0, 0, 0);
for (int i=0; i<10000; i++)
{
    //插入一条数据
    result = sqlite3_exec(db,
        "INSERT INTO MyTable (MyText, MyDate, MyTime, MyFloat) VALUES ('---上班好远!', '2012-03-23', '9:00:00', 1000);",
        0, 0, 0);
}
result = sqlite3_exec(db, "COMMIT;", 0, 0, 0); 
 
七、查询并返回结果 
 
1、以表单形式返回

下面是以表单形式获取数据的示例代码:

//查询记录(返回数据表的方式)
char **pazResult;
int nRow, nCol;
sqlite3_get_table(db, "SELECT * FROM MyTable LIMIT 1000 OFFSET 2000", &pazResult, &nRow, &nCol, 0);
 
//nRow指示出有多少行
//nCol指示出有多少列
//从pazResult中可以解析出所有记录,记录以字符串形式返回
//第n列的名称,存放于pazResult[n]
//第n行第m列的数据,存放与paszResult[(n + 1) * nCol + m]

//使用完后,务必释放为记录分配的内存
sqlite3_free_table(pazResult);
上面的查询代码,也演示了如何查询指定位置指定条数的记录,这种查询方法在记录集非常庞大的时候很有用。

 

2、以回调形式返回

下面以代码形式示例如何获得查询语句返回的记录。

首先需要定义一个回调函数如下(参数意义待会再说):

int Result(void* pContext, int nCol, char** azValue, char** azName);

 

 

然后调用sqlite函数执行查询语句,在回调函数一栏输入上面定义的回调函数:

sqlite3_exec(db, "SELECT * FROM MyTable LIMIT 10 OFFSET 20", Result, 0, 0);

 

 

执行该SQL语句时,每返回一条记录都会触发一次上面的回调函数,在该回调函数的实现中,即可得到查询结果。

在上面的Result函数中,各个参数意义如下:

pContext

这个参数是调用sqlite3_exec函数是输入的第4个参数。它通常作为环境变量用于指示出当前执行代码的主体。示例代码中我忽略了这个参数,但正式使用时一般不会忽略它。

nCol

表示该条记录有多少列。

azValue

返回的数据都蕴含在该变量中,它也是一个字符串数组。azValue[n]即为第n列的数据。

azName

存放列的名称,azName[n]就是第n列的名称。

 

 

八、存取二进制数据

考虑到sqlite作为轻量级数据库我很少用它存储二进制数据,因此本文之前并未详细解决二进制数据的存取。

但后来我发现实际使用时候,有时候存储一些二进制数据也是很必要的,比如一些简单的图片。另外一些可能包含一些sql语句的保留字符的文本时,如果想避免进行字符串检查,也可以考虑当作二进制数据存取。

在sqlite中,二进制数据的数据类型为blob,本文还是以代码来示范其应用,包括创建表格、存入二进制数据,读出二进制数据。

向数据库中存入二进制数据过程大体如下:

1.使用sqlite3_prepare进行sql语句准备,其中二进制内容以"?"替代;

2.以sqlite3_bind_blob将"?"的内容以二进制数据填充。

3.调用sqlite3_step执行sql语句完成存入操作。

从数据库中读取二进制数据过程大体如下:

1.使用sqlite3_prepare语句进行查询行为;

2.调用sqlite3_step获得查询到的结果,第一次调用时,返回查询到的第一行数据;

3.调用sqlite3_column_blob获得二进制数据指针,调用sqlite3_column_bytes获得二进制数据长度,两者结合可以获得完整二进制数据。

4.继续执行第二步,获得查询到的下一条结果。

其余详情,可以参看代码示例:

//打开数据库(不存在则自动创建)
    sqlite3* db = NULL;
    int result = sqlite3_open("C:\\TestBlob.db", &db);
    if(SQLITE_OK != result)
    {
        return;
    }

    //创建数据表(本次不检查返回结果,由于数据表不允许重复创建,因此只有首次执行才会成功)
    result = sqlite3_exec(db, "CREATE TABLE tb"                        //创建的表名为tb
        "(id integer NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE"        //第一列使用一个自动增长的integer数据
        ", b1 blob"                                                    //第二列为blob类型,列名为b1
        ", b2 blob);"                                                //第三列为blob类型,列名为b2
        , 0, 0, 0);

    //此处我不再精心构造二进制数据了,随便用两个字符串替代用作演示
    //blob1我随便加了几个经常会导致sql语句出问题的字符
    std::string blob1 = "abc'<?%", blob2 = "bcd";

    //准备一个statement用于后续进行二进制操作
    sqlite3_stmt* stmt = NULL;

    //准备执行包含二进制数据的sql操作中,注意下面的两个“?”表示的是二进制数据
    sqlite3_prepare(db, "insert into tb (b1, b2) values (?, ?);", -1, &stmt, NULL);

    //下面以二进制数据分别填充这两个"?",注意序号从1开始的
    sqlite3_bind_blob(stmt, 1, blob1.data(), blob1.length(), NULL);
    sqlite3_bind_blob(stmt, 2, blob2.data(), blob2.length(), NULL);

    //真正开始执行前面的sql语句
    sqlite3_step(stmt);

    //释放前面为stmt分配的内存,必须要有,否则会有内存泄露
    sqlite3_finalize(stmt);

    //
    //**接下来,演示一下如何读取插入的数据
    //

    //读取二进制数据
    result = sqlite3_prepare(db, "select * from tb", -1, &stmt, 0);
    while (SQLITE_ROW == sqlite3_step(stmt))
    {
        //借助两个string来读取反馈结果(仅供演示)
        std::string blobRet1, blobRet2;

        //示范一下如何读取整形数据
        int id = sqlite3_column_int(stmt, 0);

        //示范一下如何读取二进制数据
        blobRet1.append(
            (char *)sqlite3_column_blob(stmt, 1)
            , sqlite3_column_bytes(stmt, 1)
            );

        //读取blob2的内容
        blobRet2.append(
            (char *)sqlite3_column_blob(stmt, 2)
            , sqlite3_column_bytes(stmt, 2)
            );
    }

    //**如果希望从头遍历一次反馈的结果,调用sqlite3_reset(stmt);可以达到此效果
    
    //同样需要记得释放资源
    sqlite3_finalize(stmt);

    //关闭数据库
    sqlite3_close(db);