SQLite全文检索

注:示例基于iOS平台

随App里内容的增多,查找的复杂度也越来越高,用户操作就会显得很麻烦,简单举个例子,社交App的聊天记录随时间积累到一定程度的时候,想查看历史聊天记录,就得在App里慢慢翻历史消息到之前聊过的地方,查看成本非常高,这时候在App里做全文检索就显得很必要。

那全文检索的实现复杂度怎么样呢,其实只要熟悉SQLite的使用,再了解一下SQLite FTS的特性,就能很简单的添加此功能,下面就介绍一下自己的关于全文检索的理解,不足之处望指出。

FTS

首先简单介绍一下FTS,全称Full Text Search,也就是全文检索,是Sqlite自带的功能模块,基于虚表、分词器及文件实现的全文检索方案,检索结果的理想程度跟分词器有关,分词器越智能,检索结果越理想。SQLite的版本有多个,主要使用FTS3和FTS4,FTS5也发布了,但需要自己编译才能使用,详细的FTS文档参考这,下面几条主要先介绍FTS的相关概念及语法:

1.Tokenizer(分词器)

分词器是FTS的核心,没有分词器模块,FTS就没法工作,分词器主要是将输入文本进行拆分,以便SQLite进行Match,例如一段文本“全文检索”,可能被拆分为“全文检索”,也可能本拆分为“”,最终检索结果也完全取决于分词器的拆分。假设上面两种拆分分别为AB,那全文检索的时候检索“”和“全文”,“”在A方案中是没法检索出文的,而B方案可以,“全文“在A、B方案中都能检索出来,这就是分词器的大概作用。
SQLite也提供了相关分词器,比如simple、icu、unicode61等,只有icu、unicode61支持中文,但unicode61按标点拆分,不可用,而icu是按字拆分的,可以用,只是检索结果会比较乱。

2.MATCH

MATCH是FTS的检索操作,MATCH会比LIKE快上很多倍,MATCH是全匹配,MATCH还可以执行MATCH * 操作,类似于LIKE 的后%操作,就是前缀匹配,下面会有使用实例,继续。

3.offsets

offset类似于SQLite里的count,是一个功能函数,获取匹配结果在文本中的位置偏移,最终会获取到匹配文本在数据表中的第几行、第几列、已经匹配文本的Range,详细参考官方文档

4.snippet

snippet也类似于offsets,都是用于获取匹配文本的位置,方便查询结果的高亮处理,语法如下:snippet(table name, [, ], …, 1, 10) as text;参数分别是table name、关键字左括弧、关键字右括弧、省略符,生效列,关键字前后字符限制,例如在文本“xxxxxxxxxxxxxx位置xxxxxxxxxxxx”中Match“位置”,得到的结果为:“…xxxxxxxxxx[位置]xxxxxxxxxx…”。简单说就是将匹配到的文本括起来,这里用的[]括号,匹配位置距文本首尾过长都会以指定的省略。

FMDB(FTS)

iOS平台的FMDB在最新版本中拓展了FTS3的支持,其拓展简单理解就是自定义分词器的接入,FMDB基于CFStringTransform实现了以词为单位做拆分的分词器,基本能满足检索需要。下面是FMDB加载自定义分词器的示例代码:

1
2
3
4
5
6
7
8
// 创建分词器
FMSimpleTokenizer *simpleTok = [[FMSimpleTokenizer alloc] initWithLocale:NULL];
// 安装分词器模块 默认为fmdb
[db installTokenizerModule];
// 注册具体的分词器
[FMDatabase registerTokenizer:simpleTok withKey:@"simple"];
// 使用
CREATE VRITUAL TABLE doc USING FTS3(subject,body,tokeniz=fmdb simple);

不使用FMDB的话也可以按官方文档实现分词器模块,并以SQL语句直接注册自定义分词器,参考如下FMDB代码:
注册自定义分词器

全文检索

首先创建FTS虚拟表,指定分词器为(fmdb simple),分词器的注册如上文介绍,创建语法如下:

1
CREATE VRITUAL TABLE doc USING FTS3(subject,body,tokeniz=fmdb simple);

更新数据到FTS表:

1
INSERT INTO doc (subject,body) VALUES ('tokenizer test','这是关于SQLite分词器的测试');

群文检索语法:

1
2
3
4
5
6
7
8
9
10
11
-- 普通检索
SELECT * FROM doc WHERE doc MATCH '测试';
-- MATCH * 检索
SELECT * FROM doc WHERE doc MATCH '测*';
-- MATCH 指定字段 检索
SELECT * FROM doc WHERE doc MATCH 'body:测';
SELECT * FROM doc WHERE doc.body MATCH '测*';
-- offsets 语法
SELECT subject,body,offsets(doc) as offset FROM doc WHERE doc MATCH '测试';
-- snippet
SELECT subject,body,snippet(doc,[,],...,2,10) FROM doc WHERE doc MATCH '测试';

高亮显示:有上面offsets和snippet两种方式获取关键字range(一般使用snippet),文本高亮的显示就很简单了。

博客推荐:

  1. iOS全文检索:使用SQLite FTS4扩展
  2. SQLite FTS3 and FTS4 Extensions:https://www.sqlite.org/fts3.html