热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Sqlite3瞎捣鼓笔记

目录目录Sqlite3数据库SELECTINSERTUPDATE

目录

      • 目录
      • Sqlite3 数据库SELECT INSERT UPDATE
      • Sqlite3 数据库UPDATE OF 触发器INSERT 触发器
      • Sqlite3 sum函数求某记录的比例
      • Sqlite3 substr字符串截取函数获取未知长度的前n-1个字符
      • Sqlite3 创建内存数据库
      • Sqlite3 导出内存数据库到文件

首先我要安利一个免费开源的工具软件,sqlitestudio.用来查看sql数据库的db文件的.
界面是Qt做的,漂亮,而且功能丰富实用.中文化程度高.
里面的sql编辑器可以直接写sql操作数据库.带语法检查.
这样你执行之后得到了什么结果就能一眼看出来.
(记得点击刷新按钮,不然看到的是上次的结果)
这年头,没个可视化还算什么数据库工具.有图:
sqlitestudio

##################正文#####################

一个朋友的一个课后作业,从文本文件依次读n阶字符串,写入数据库,统计每个字符串出现的次数与条件概率和联合概率 只统计文件中的英文字符,并统一转成大写字母

假如样本为:abcd9abcdefg

则统计的字符串为
0阶 a b c d a b c d e f g
1阶 ab bc cd da ab bc cd de ef fg
2阶 abc bcd cda dab abc bcd cde def efg
….
联合概率: 该字符串在文件中出现的次数/所有字符串的统计次数
条件概率: 该字符串在本阶的联合概率/ 该字符串前n-1个字符组成的字符串,在上一阶的联合概率

另一个用于概率计算演示的样本: aB2cA4b9789

由样本生成的0阶表

key count joinprobility conprobility
A 2 0.4 NULL(0阶没有)
B 2 0.4 NULL(0阶没有)
C 1 0.2 NULL(0阶没有)

由样本生成的1阶表

key count joinprobility conprobility
AB 2 0.5 (2/(2+1+1)) 1.25 (0.5/0.4)
BC 1 0.25 (1/(2+1+1)) 0.625 (0.25/0.4)
CA 1 0.25 (同上) 1.25 (0.25/0.2)

* 括号里的内容是不在数据库里的

Sqlite3 数据库SELECT INSERT UPDATE

初步方案是,先将文本文件解析并写到数据库里,只写key(主键)和count字段.
然后通过sql语句,由数据库计算联合概率和条件概率.
首先想到的是先 SELECT 查找该记录是否已存在,如果已存在则执行 UPDATE 否则执行 INSERT.

const char* table_name = "mytable"; // 表名前缀

/*查找记录回调函数 *有查找结果时才会触发该回调函数,用于显示查询结果 *这里没有使用具体的查询结果,只要该函数被调用就说明该记录已存在.直接置1 */
int find_key_callback(void* data, int count, char** value, char** name)
{
    *(int*)data = 1;
    return 0;
}

/*查找记录是否已经存在 *db 数据库句柄 *order 当前阶数 *str 当前阶数长度的字符串 */
int find_key(sqlite3* db, int order, char* str)
{
    int is_exist = 0;   // 该字符串是否存在数据库中(1,存在. 0,不存在)
    char* err = NULL;
    char sql[1024] = { 0 };
    sprintf(sql, "SELECT key FROM %s%d WHERE key='%s'", table_name, order, str);
    int res = sqlite3_exec(db, sql, find_key_callback, &is_exist, &err);
    if (res != SQLITE_OK)
    {
        ERROR_MESSAGE(sqlite3_errstr(res));
        return -1;
    }
    return is_exist;
}

/*更新对应阶数(order)的表中的数据(str)*/
int update_key(sqlite3* db, int order, char* str)
{
    char* err = NULL;
    char sql[1024] = { 0 };
    sprintf(sql, "UPDATE %s%d SET count=count+1 WHERE key='%s'", table_name, order, str);
    int res = sqlite3_exec(db, sql, NULL, NULL, &err);
    if (res != SQLITE_OK)
    {
        ERROR_MESSAGE(sqlite3_errstr(res));
        return -1;
    }
    return 0;
}
/*向对应阶数(order)的表里插入新数据(str)*/
int insert_key(sqlite3* db, int order, char* str)
{
    char* err = NULL;
    char sql[1024] = { 0 };
    sprintf(sql, "INSERT INTO %s%d (key,count) VALUES ('%s',1)", table_name, order, str);
    int res = sqlite3_exec(db, sql, NULL, NULL, &err);
    if (res != SQLITE_OK)
    {
        ERROR_MESSAGE(sqlite3_errstr(res));
        return -6;
    }
    return 0;
}

因此我们向数据库写入一条记录的程序就变成了这样

int input_record(sqlite3* db, size_t order, char* str)
{
    if (find_key(db, order, str))    // 该记录是否已经存在数据库中
    {
        update_key(db, order, str);  // 已存在,则更新count = count+1
    }
    else
    {
        insert_key(db, order, str);  // 不存在,则插入str,同时将count设为1
    }
    return 0;
}

这个方法有个弊端,无论记录存不存在,都执行了两条sql语句.
下面针对sqlite3的C语言接口函数进行优化:
直接 INSERT 如果记录已存在,则sqlite3_exec函数会返回一个错误码,那我们只要在执行完 INSERT之后,根据sqlite3_exec的返回值来决定是否执行 UPDATE
如此一来就不需要 SELECT 了,新的代码为

int input_record(sqlite3* db, size_t order, char* str)
{
    char* err = NULL;
    char sql[256] = { 0 };
    sprintf(sql, "INSERT INTO %s%d (key,count) VALUES ('%s',1)", table_name, order, str);
    int res = sqlite3_exec(db, sql, NULL, NULL, &err);
    if (res == SQLITE_CONSTRAINT)  // 已有记录,则 INSERT 失败,改为执行 UPDATE 操作
    {
        sprintf(sql, "UPDATE %s%d SET count=count+1 WHERE key ='%s'", table_name, order, str);
        res = sqlite3_exec(db, sql, NULL, NULL, &err);
    }
    if (res != SQLITE_OK)
    {
        ERROR_MESSAGE(sqlite3_errstr(res));
        return -3;
    }
    return 0;
}

如此一来,在最佳情况下,只会执行一条 INSERT 语句.

Sqlite3 数据库UPDATE OF 触发器,INSERT 触发器

Sqlite3 sum函数求某记录的比例

联合概率的计算可以通过sqlite的触发器来完成

/* 更新触发器 * 检测到 count字段更新动作,自动计算联合概率,更新joinprobility字段 */
int update_trigger(sqlite3* db, int order)
{
    char* err = NULL;
    char sql[1024] = { 0 };
    char table[256] = { 0 };
    // 需要注意一点,更新触发器有两种模式,一种是对任何记录的更新都会触发
    // 另一种是 只有指定的字段被更新才会触发
    // 这里使用第二种,只有count被更新才会触发 触发器
    // 因为我们在更新触发器里执行了更新动作,
    // 使用第一种触发方式会导致触发器自触发,sqlite对此会提供相关的报错信息.
    sprintf(table, "%s%d", table_name, order);
    sprintf(sql, "CREATE TRIGGER update_trigger%d "\
            "AFTER UPDATE OF count "\
            "ON %s "\
            "BEGIN "\
            "UPDATE %s "\
            "SET joinprobility = (count * 1.0 / (SELECT sum(count)FROM %s)); "\
            "END;", order, table, table, table);
    int res = sqlite3_exec(db, sql, NULL, NULL, &err);
    if (res != SQLITE_OK)
    {
        ERROR_MESSAGE(sqlite3_errstr(res));
        return -6;
    }
    return 0;
}

/* 插入触发器*/
int insert_trigger(sqlite3* db, int order)
{
    char* err = NULL;
    char sql[1024] = { 0 };
    char table[256] = { 0 };

    sprintf(table, "%s%d", table_name, order);
    sprintf(sql, "CREATE TRIGGER insert_trigger%d "\
            "AFTER INSERT "\
            "ON %s "\
            "BEGIN "\
            "UPDATE %s "\
            "SET joinprobility = (count * 1.0 / (SELECT sum(count)FROM %s)); "\
            "END;", order, table, table, table);
    int res = sqlite3_exec(db, sql, NULL, NULL, &err);
    if (res != SQLITE_OK)
    {
        ERROR_MESSAGE(sqlite3_errstr(res));
        return -6;
    }
    return 0;
}

Sqlite3 substr字符串截取函数,获取未知长度的前n-1个字符

最后计算条件概率,条件概率需要一个sql字符串截取函数,截取一串长度为n的字符的前n-1位,n未知.而且涉及两个表的数据

int update_conprobility(sqlite3* db, int order)
{
    char* err = NULL;
    char sql[256] = { 0 };

    if (order != 0) // 不计算第0阶的联合概率
    {
        sprintf(sql, "UPDATE %s%d SET cOnprobility="\
                "(joinprobility*1.0/"\
                "(SELECT joinprobility FROM %s%d WHERE"\
                "key = substr(%s%d.key,1,length(%s%d.key)-1)))", \
                table_name, order, table_name, order - 1, \
                table_name, order, table_name, order);
        int res = sqlite3_exec(db, sql, NULL, NULL, &err);
        if (res != SQLITE_OK)
        {
            ERROR_MESSAGE(sqlite3_errstr(res));
            return -1;
        }

        sprintf(sql, "UPDATE %s%d SET cOnprobility= 1.0 "\
                    "WHERE conprobility > 1", table_name, order);// 数据修复,大于1的置1 
        res = sqlite3_exec(db, sql, NULL, NULL, &err);
        if (res != SQLITE_OK)
        {
            ERROR_MESSAGE(sqlite3_errstr(res));
            return -1;
        }
    }
    return 0;
}

至此基本功能完成.
结果一个388KB的txt文件, 获取0~10阶的数据足足跑了一晚上.
这让我很不开心

于是我去掉了触发器,因为每一个插入,更新记录,都会执行触发器计算,速度肯定会有影响.
同时改成了多线程,
首先通过fopen打开同一个文件10次,获取10个只读的文件句柄
然后建立10个线程同时操作一个数据库句柄,负责向该数据库中的10个表同时写数据
结果处理速度并没有明显提高,因为数据库都是线程安全的,内部肯定是有加锁处理的.
就在苦恼之时,发现了sqlite3有内存数据库

Sqlite3 创建内存数据库

获取内存数据库句柄可以按照和文件数据库一样的方法来操作.

sqlite3_open(":memory:",db);

而且内存数据库是没有锁的,而我这个需求每个线程操作一个表,表与表之间基本没有关联性,而且线程只负责写,因次,不需要线程锁.内存数据库换上之后,速度飞快.
原来需要一晚上的数据 现在50分钟就跑完了.
需要注意的一点是,内存数据库在内存里,退出就没了.

Sqlite3 导出内存数据库到文件

需要将sqlite3的内存数据库导出到文件数据库,
在sqlite3的官网有一段例程,去看网页右下角的 loadOrSaveDb 函数,
网上有人中文翻译过,可以去搜一下
第一个参数是内存数据库句柄,第二个参数是文件地址,第三个参数用来设置是导出还是导入.


推荐阅读
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 本文介绍了UVALive6575题目Odd and Even Zeroes的解法,使用了数位dp和找规律的方法。阶乘的定义和性质被介绍,并给出了一些例子。其中,部分阶乘的尾零个数为奇数,部分为偶数。 ... [详细]
  • HDFS2.x新特性
    一、集群间数据拷贝scp实现两个远程主机之间的文件复制scp-rhello.txtroothadoop103:useratguiguhello.txt推pushscp-rr ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • 本文介绍了一个题目的解法,通过二分答案来解决问题,但困难在于如何进行检查。文章提供了一种逃逸方式,通过移动最慢的宿管来锁门时跑到更居中的位置,从而使所有合格的寝室都居中。文章还提到可以分开判断两边的情况,并使用前缀和的方式来求出在任意时刻能够到达宿管即将锁门的寝室的人数。最后,文章提到可以改成O(n)的直接枚举来解决问题。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
author-avatar
忧之灵_435
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有