您的位置:首页 > 移动开发 > Objective-C

工作二总结——objective-C中sqlite3数据库的处理(其一)

2014-11-02 17:55 239 查看
首先说明,本文基本是纯C,算是我笨手笨脚写出的第一稿,虽然最后没有使用它,但它作为理解"sqlite3.h"库API的工具,还是挺有教育意义的。

更有objective-C风格的实现版本请看(其二)。

最近有一个小工作任务,根据一组特定格式的乱码,构造sqlite3数据库的三个表。

根据格式随机生成了一组乱码,差不多就是下面这样子。

aaaaaaaaaa	500	haaaa	aaaaa	baaaa	baaaa
baaaaaaaaa	300	eaaaa	aaaaa	gaaaa	gaaaa	baaaa	faaaa	aaaaa	gaaaa
caaaaaaaaa	690	daaaa	eaaaa	haaaa
daaaaaaaaa	220	faaaa	caaaa	aaaaa
eaaaaaaaaa	380	haaaa	jaaaa	daaaa	caaaa	aaaaa	iaaaa
faaaaaaaaa	820	jaaaa	jaaaa	aaaaa	caaaa	daaaa	jaaaa
gaaaaaaaaa	70	daaaa	haaaa	eaaaa	eaaaa
haaaaaaaaa	790	caaaa	daaaa	haaaa	daaaa	eaaaa	aaaaa
iaaaaaaaaa	210	haaaa	gaaaa	jaaaa	baaaa	jaaaa
jaaaaaaaaa	580	caaaa	faaaa	baaaa	gaaaa
kaaaaaaaaa	20	faaaa	baaaa	baaaa	iaaaa	haaaa	aaaaa	eaaaa
laaaaaaaaa	510	baaaa	faaaa	gaaaa
maaaaaaaaa	370	eaaaa	faaaa	haaaa	baaaa	iaaaa	haaaa	baaaa


乱码每一行的第一个字符串是实体名称,在这里命名为entity;第二个数字是entity的分数,即score;之后数目不定的各个字符串都是属于entity的标签tag。

需要做的是建立sqlite的三个表:

entity表,列字段为id(从零开始,递增),name,score,popularity。

tag表,列字段为id(从零开始,递增),name,score(随机生成),popularity。

map表,列字段为id(从零开始,递增),entity_id,tag_id

就是这样,entity表和tag表相互独立,而map表建立了他们的映射关系。

数据库的生成是在电脑端处理的,因此只需要做一个console app就行了。

既然已经走向了IOS开发,那么这个程序我打算用objective C实现,本文是我笨手笨脚写的第一个版本。

非常惭愧,简直就是纯C语言,而且性能也相当差,数据库操作太多了。但作为一名程序员的成长足迹,它还是值得记下来的。

思维如下:

先带入sqlite3.h库

#import <Foundation/Foundation.h>
#import "sqlite3.h"


需要自行下载sqlite3.h和.c,没错不是.m,这就是一个纯C的库,objective-C是完全兼容纯C的,比C++更加兼容。

if (argc != 3) {
NSLog(@"Wrong arg number! Please type in txt path at first, and then the db path!");
return 1;
}
txtPath = argv[1]; databasePath = argv[2];


用控制台终端读入,第一个参数需要是文本文件的路径,第二个参数是输出的数据库路径,普遍情况下应该是程序自行创建数据库。

因此命令行参数个数必须是3,因为arg[0]应该是项目路径,之后才是控制台参数。

FILE *txtFile = fopen(txtPath, "r");
if (!txtFile) {
NSLog(@"File loading is failed!");
return 1;
}
ret = sqlite3_open(databasePath, &db); //参数2是db指针对象的指针
if (ret != SQLITE_OK) {
NSLog(@"Something wrong!");
return 1;
}
打开相应文件,都是纯C的方法。

用文件指针可以很方便读入文件的一行,我用了只读的方式打开文件路径。



ret = sqlite3_open(databasePath, &db);
是sqlite3.h中的一个函数,返回值表示是否成功,第一个参数是数据库路径,第二个参数用来读取数据库,在本段代码中,db是个全局变量

sqlite3 *db = 0;
sqlite3是个相当复杂的数据结构,db是指向其的指针。在sqlite3_open中,需要修改db指针指向的值,因此需要&db取指针的地址。

That is,第二参数是sqlite3 **类型。

//为了防止表格式有问题,因此先删去同名表,再创建相应表
memset(delete, 0, sizeof(delete));sprintf(delete, "drop table entity;");
ret = sqlite3_exec(db, delete, 0, 0, 0);
memset(delete, 0, sizeof(delete));sprintf(delete, "create table entity(id integer not null, name TEXT not null unique, score integer, popularity integer);");
ret = sqlite3_exec(db, delete, 0, 0, &errMsg1);
if (ret != SQLITE_OK) {NSLog(@"Creating failed!, %s", errMsg1); exit(1);}
有SQL语句的代码行一般都好长。首先说说SQL语句作用。

有可能控制台制定的数据库路径就有格式错误的同名相应表,比如只有个name列的entity表。作为程序员需要考虑所有异常情况,因此应该先删掉目标数据库的相应表,我们本来就是想创建一个新表,至于数据库中有没有其他表,与我们无关。

DROP TABLE entity;
这就删除了entity表.。

我们还需要创建新的entity表,用我们自定义的方式:列字段为id(从零开始,递增),name,score,popularity。

CREATE TABLE entity(id integer not null, name TEXT not null unique, score integer, popularity integer);

这样就行了,not null保证有值,unique保证唯一,TEXT是字符串,integer是整数。

已经想好了SQL语言,现在要在C语言(或者objective-C,惭愧。。。)中将其输入数据库,怎么办?

memset(delete, 0, sizeof(delete)); sprintf(delete, "drop table entity;");
memset是我为了保险而为之,也许可以不用,但我还是用吧,被bug虐怕了。

用sprintf将SQL语言输入到字符串delete中。

ret = sqlite3_exec(db, delete, 0, 0, &errMsg1);
if (ret != SQLITE_OK) {NSLog(@"Creating failed!, %s", errMsg1); exit(1);}
sqlite3_exec函数用来执行SQL语句,第一参数是数据库sqlite3的指针,也就是要输入的数据库;第二参数是SQL语言字符串;三参是回调函数,四参是传入回调函数的数据,这俩参数麻烦一些,之后用select的时候再说,第五参数是char **类型,将错误信息输出至某字符串。

fgets(stringFromTxt, 500, txtFile);
while (1) {
char *errorMsg, SQL[100];
if (strcmp(last, stringFromTxt) == 0 || stringFromTxt == NULL)//到达最后一个就break
break;
entityName = strtok(stringFromTxt," \t");
score = strtok(NULL, " \t");

memset(SQL, 0, sizeof(SQL));//防止意外,全部置零
sprintf(SQL, "insert into entity(id,name,score,popularity) values(%d,'%s',%s, %d);", entity_cnt, entityName, score, 0);
ret = sqlite3_exec(db, SQL, 0, 0, &errorMsg);
if (ret != SQLITE_OK) NSLog(@"插入数据失败,第%d次, %s",entity_cnt, errorMsg);

从文件中读取一行放在字符串stringFromTxt中,之后无限循环,循环遇到最后一行就break。

经过测试,最后一行有两种出现方式,第一种是stringFromTxt一直保持不变,始终是最后一行的值,就是

strcmp(last, stringFromTxt) == 0
或者最后一行之后,就只能得到

stringFromTxt == NULL
我用strtok函数将一行切成一个一个的小段,用法如下:

entityName = strtok(stringFromTxt," \t");
score = strtok(NULL, " \t");


参数1是需要切割的字符串,只需要第一次的时候放入,之后如果字符串未切割完,放入NULL就行了。

参数二也是个字符串,里面每个字符都成为了切割参数1字符串的分隔符,我定义的是空格和制表符。

sprintf(SQL, "insert into entity(id,name,score,popularity) values(%d,'%s',%s, %d);", entity_cnt, entityName, score, 0);
之后可以插入entity表了,插入格式在上面,需要注意的是,values中如果有TEXT字符串类型,记得加上单引号。

这里entity_cnt是个从0开始的全局变量,因为一行只有一个entity,所以换到下一行时entity_cnt增加,用它来表示entity的id。

我数据库没学好,应该有让数据库直接自增id的办法。

tag = strtok(NULL, " \n\t");
while (tag != NULL) {
tagScore = arc4rand()%101;
memset(SQL, 0, sizeof(SQL)); sprintf(SQL, "insert into tag(id,name,score,popularity) values(%d, '%s', %d, %d);", tag_cnt++, tag, tagScore, 0);
ret = sqlite3_exec(db, SQL, 0, 0, &errorMsg);
if (ret != SQLITE_OK){ tag_cnt--; }//插入不成功
memset(SQL, 0, sizeof(SQL)); sprintf(SQL, "select id from tag where name = '%s';", tag);//查询tagID
ret = sqlite3_exec(db, SQL, sqlite_callback, (void*)db, &errorMsg);

tag = strtok(NULL, " \t\n");
}

strcpy(last, stringFromTxt);
entity_cnt ++;
fgets(stringFromTxt, 500, txtFile);
}
return 0;
}
之后插入tag,注意分隔符多了一个‘\t’,没加上'\t'时的bug恶心了我好久。数据库中tag表的name字段是唯一的,但老出现name值相同的tag,后来调试时发现看似name相同的两个tag是“laaaa”和“laaaa\t”这样的情况,每行最后一个tag显然会是这样。

tag_cnt也是从0开始的全局变量,用来表示tag的id,如果出现了同名的tag,插入数据库必然会失败,所以如果失败tag_cnt需要自减

if (ret != SQLITE_OK){ tag_cnt--; }//插入不成功
接下来两句可以好好说一下,为了能更好理解回调函数,我特地将全局变量db放在第四参数,其实完全没这个必要。

memset(SQL, 0, sizeof(SQL)); sprintf(SQL, "select id from tag where name = '%s';", tag);//查询tagID
ret = sqlite3_exec(db, SQL, sqlite_callback, (void*)db, &errorMsg);
tag表和entity表都有了,那么如何建立两个表之间的id映射?

首先每一行只会处理一个entity,因此全局变量entity_cnt肯定就是entity的id,而tag可能有很多重复的情况,tag_cnt不一定就是其id,所以我用select语句根据tag名在数据库中查询,也是sprintf构造字符串,问题不大。

关键问题是,执行了select语句之后,我们的查询结果到底去了哪里?

这就引入了一个新概念,回调函数。

上面sqlite3_exec语句的第三参数sqlite_callback不是系统的东西,而是我自己定义的全局类型回调函数,第四参数db是传入回调函数的参数,必须转为无类型指针void*。

那么先看看我自己定义的回调函数:

int sqlite_callback(void* pv, int argc, char** colValue, char** colName){
//colValue[0]必定是找到的id
int ret;
char SQL[100], *errorMsg;
sqlite3 *db = (sqlite3*)pv;
memset(SQL, 0, sizeof(SQL)); sprintf(SQL, "insert into map(id,entity_id,tag_id) values(%d, %d, %s);", map_cnt++, entity_cnt, colValue[0]);
ret = sqlite3_exec(db, SQL, 0, 0, &errorMsg);
return 0;
}


回调函数名字可以乱起,但参数必须是固定的四个。

在sqlite3_exec参数中放入回调函数指针以后,回调函数可能执行多次,总之,select结果有几行,回调函数就执行多少次,按顺序每次处理一行。

回调函数第一参数是个传入的无类型指针,也就是sqlite3_exec函数的第四参数,在这里,我们传入了数据库指针db。

第二参数是结果列的个数,我们只查询了id,因此只会有一列,此参数必然是1。

第三参数请理解为字符串数组,表示列中数据的值,colValue[0]表示第一列的值,在这里就是我们唯一的一列,id的值。

第四参数也是个字符串数组,表示列名,我们查询的是id,因此colName[0]就是"id"。

因为我们执行的是SELECT id FROM tag WHERE name = %s; 一个name只会对应一个id,所以select结果只会有一行,回调函数只会执行一次,我们根据name查询到的tag_id就是colValue[0]。

sqlite3 *db = (sqlite3*)pv;
这句话演示了怎么使用传进来的参数,先把无类型指针转化为相应的指针,然后再用,总之这个参数挺有用。

我再强调一次,因为db是个全局函数,在这里这么用简直是多此一举,我这么用只是为了说明这个参数使用方式。我用局部变量db隐藏了全局变量db,但他们的值都是一样的。

之后向map中输入SQL就不说明了。

随后

tag = strtok(NULL, " \t\n");
取下一个tag,如果字符串已经分解完毕,那么tag会取到NULL,就这么设置循环条件就好。

整体程序如下:

#import <Foundation/Foundation.h>
#import "sqlite3.h"

const char *databasePath;
const char *txtPath;
sqlite3 *db = 0;

int entity_cnt, tag_cnt, map_cnt;

int sqlite_callback(void* pv, int argc, char** colValue, char** colName);

int main(int argc, const char * argv[]) {
int ret, tagScore;
char stringFromTxt[100], last[100], *entityName, *score, *tag, delete[100], *errMsg1;
if (argc != 3) {
NSLog(@"Wrong arg number! Please type in txt path at first, and then the db path!");
return 1;
}
txtPath = argv[1]; databasePath = argv[2];
FILE *txtFile = fopen(txtPath, "r");
if (!txtFile) {
NSLog(@"File loading is failed!");
return 1;
}
ret = sqlite3_open(databasePath, &db); //参数2是db指针对象的指针
if (ret != SQLITE_OK) {
NSLog(@"Something wrong!");
return 1;
}

//为了防止表格式有问题,因此先删去同名表,再创建相应表
memset(delete, 0, sizeof(delete));sprintf(delete, "drop table entity;");
ret = sqlite3_exec(db, delete, 0, 0, 0);
memset(delete, 0, sizeof(delete));sprintf(delete, "create table entity(id integer not null, name TEXT not null unique, score integer, popularity integer);");
ret = sqlite3_exec(db, delete, 0, 0, &errMsg1);
if (ret != SQLITE_OK) {NSLog(@"Creating failed!, %s", errMsg1); exit(1);}

memset(delete, 0, sizeof(delete));sprintf(delete, "drop table tag;");
ret = sqlite3_exec(db, delete, 0, 0, 0);
memset(delete, 0, sizeof(delete));sprintf(delete, "create table tag(id integer not null, name TEXT not null unique, score integer, popularity integer);");
ret = sqlite3_exec(db, delete, 0, 0, &errMsg1);
if (ret != SQLITE_OK) {NSLog(@"Creating failed!, %s", errMsg1); exit(1);}

memset(delete, 0, sizeof(delete));sprintf(delete, "drop table map;");
ret = sqlite3_exec(db, delete, 0, 0, 0);
memset(delete, 0, sizeof(delete));sprintf(delete, "create table map(id integer not null, entity_id not null, tag_id integer);");
ret = sqlite3_exec(db, delete, 0, 0, &errMsg1);
if (ret != SQLITE_OK) {NSLog(@"Creating failed!, %s", errMsg1); exit(1);}

fgets(stringFromTxt, 500, txtFile);
while (1) {
char *errorMsg, SQL[100];
if (strcmp(last, stringFromTxt) == 0 || stringFromTxt == NULL)//到达最后一个就break
break;
entityName = strtok(stringFromTxt," \t");
score = strtok(NULL, " \t");

memset(SQL, 0, sizeof(SQL));//防止意外,全部置零
sprintf(SQL, "insert into entity(id,name,score,popularity) values(%d,'%s',%s, %d);", entity_cnt, entityName, score, 0);
ret = sqlite3_exec(db, SQL, 0, 0, &errorMsg);
if (ret != SQLITE_OK) NSLog(@"插入数据失败,第%d次, %s",entity_cnt, errorMsg);

tag = strtok(NULL, " \n\t");
while (tag != NULL) {
tagScore = arc4rand()%101;
memset(SQL, 0, sizeof(SQL)); sprintf(SQL, "insert into tag(id,name,score,popularity) values(%d, '%s', %d, %d);", tag_cnt++, tag, tagScore, 0);
ret = sqlite3_exec(db, SQL, 0, 0, &errorMsg);
if (ret != SQLITE_OK){ tag_cnt--; }//插入不成功
memset(SQL, 0, sizeof(SQL)); sprintf(SQL, "select id from tag where name = '%s';", tag);//查询tagID
ret = sqlite3_exec(db, SQL, sqlite_callback, (void*)db, &errorMsg);

tag = strtok(NULL, " \t\n");
}

strcpy(last, stringFromTxt);
entity_cnt ++;
fgets(stringFromTxt, 500, txtFile);
}
return 0;
}

int sqlite_callback(void* pv, int argc, char** colValue, char** colName){ //colValue[0]必定是找到的id int ret; char SQL[100], *errorMsg; sqlite3 *db = (sqlite3*)pv; memset(SQL, 0, sizeof(SQL)); sprintf(SQL, "insert into map(id,entity_id,tag_id) values(%d, %d, %s);", map_cnt++, entity_cnt, colValue[0]); ret = sqlite3_exec(db, SQL, 0, 0, &errorMsg); return 0; }


那么这个小程序就到此为止了,这个程序没有作为我最后提交的版本,而提交版本在(其二)中,有兴趣可以看看。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: