Issar 搜索

全文检索

全文检索是一种从数据库中搜索文本的强大功能。你现在应该已经熟悉索引的工作原理了,但还是让我们先了解一些基本知识。

索引就像一张查询表,允许快速地根据给定值查找数据。例如,如果你的对象含有一个 title 字段,你可以以该字段创建一张索引表,以此根据给定的标题来快速查询。

为什么全文检索很有用?

你本可以轻松通过 Filter 来搜索文本。Isar 为你提供了许多字符串查询方法,例如 .startsWith().contains().matches()。但问题在于 Filter 的复杂度是 O(n),其中 n 是 Collection 中对象的个数,像 .matches() 这样的字符串操作就格外消耗性能。

:::tip 全文检索比 Filter 快多了,但是索引也有局限的地方。在本专题中,我们将探寻如何解决这些局限性。 :::

基本示例

想法依然不变:我们对文本中的单词进行索引,而不是对整个文本索引,这样我们可以对单个单词进行搜索。

让我们先创建一个基本的全文检索索引:

dart 复制代码
class Message {
  Id? id;

  late String content;

  @Index()
  List<String> get contentWords => content.split(' ');
}

现在我们可以通过内容中某些指定词汇来搜索讯息:

dart 复制代码
final posts = await isar.messages
  .where()
  .contentWordsAnyEqualTo('hello')
  .findAll();

这条查询非常快,但是有几个问题:

  1. 我们只能搜索整个词汇
  2. 我们没考虑标点符号
  3. 我们不支持其他空白字符

正确分割文本

让我们完善上述例子。我们可以用一个复杂的正则来正确分割文本,但是在某些少数情况下它很可能会出错且导致查询变得很慢。

Unicode Annex #29 为几乎所有人类语言定义了如何正确分割文本。它很复杂,但是幸运的是,Isar 内部已经帮我们实现了:

dart 复制代码
Isar.splitWords('hello world'); // -> ['hello', 'world']

Isar.splitWords('The quick ("brown") fox can't jump 32.3 feet, right?');
// -> ['The', 'quick', 'brown', 'fox', 'can't', 'jump', '32.3', 'feet', 'right']

我想要更多控制

很简单!我们可以修改索引配置,让它支持前缀匹配和大小写匹配:

dart 复制代码
class Post {
  Id? id;

  late String title;

  @Index(type: IndexType.value, caseSensitive: false)
  List<String> get titleWords => title.split(' ');
}

默认情况下,Isar 会将单词散列化,这么做性能很快且节省存储空间。但是这样就无法使用前缀匹配查询。我们改变了索引类型,使用 IndexType.value 而不是 IndexType.hash,来直接使用那些单词。借此我们就可以使用 .titleWordsAnyStartsWith() 的 Where 子句:

dart 复制代码
final posts = await isar.posts
  .where()
  .titleWordsAnyStartsWith('hel')
  .or()
  .titleWordsAnyStartsWith('welco')
  .or()
  .titleWordsAnyStartsWith('howd')
  .findAll();

我也需要 .endsWith() 方法

没问题!我们会用一个小技巧来实现 .endsWith() 匹配:

dart 复制代码
class Post {
    Id? id;

    late String title;

    @Index(type: IndexType.value, caseSensitive: false)
    List<String> get revTitleWords {
        return Isar.splitWords(title).map(
          (word) => word.reversed).toList()
        );
    }
}

不要忘记倒序排列查询的结果:

dart 复制代码
final posts = await isar.posts
  .where()
  .revTitleWordsAnyStartsWith('lcome'.reversed)
  .findAll();

词干提取算法

不幸的是,索引不支持 .contains() 匹配(其他数据库也如此)。但是还有几个备选方案值得我们研究一番。选择何种方式完全取决于你的使用场景。举个例子,你可以对词干进行索引,而不是对整个单词索引。

词干提取算法指的是自然语言处理领域里去除词缀得到词根的过程,即得到单词最一般的写法:

arduino 复制代码
connection
connections
connective          --->   connect
connected
connecting

常见的算法有 Porter 词干提取算法Snowball 词干提取算法

还有将单词复杂形态转变成最基础形态的词形还原

语音算法

语音算法 是指根据发音来检索单词的算法。也就是说,它可以根据发音接近程度来帮你查询结果。

:::warning 大部分语音算法通常只支持单一语言,一般是英语。 :::

Soundex

Soundex 是一种语音算法,它通过英文发音来检索名字。它的目的是将同音词用同一编码表示,虽然发音略有差异,但可达到模糊匹配的效果。这是个非常直接明了的算法,也有若干改进版本。

若是用这个算法,那么单词 "Robert""Rupert" 都会返回编码 "R163",而单词 "Rubin" 则返回 "R150"。 同音词 "Ashcraft""Ashcroft" 则都会返回 "A261"

Double Metaphone

Double Metaphone 也是一种语音算法,是 Metaphone 的二代版本。它在前代基础上改进了不少基本设计。

Double Metaphone 加入了对大量来自外来语如斯拉夫语、德语、凯尔特语、希腊语、法语、意大利语、西班牙语、中文等的不规则英文单词发音的支持。

相关推荐
Leon-Ning Liu10 小时前
【真实经验分享】OGG抽取进程报错 ORA-07445 [kgherrordmp()+986] ORA-00600 [17114]分析步骤
运维·数据库
CCPC不拿奖不改名10 小时前
Redis 工程化部署深度解析
linux·服务器·数据库·redis·深度学习·缓存·rag
吴声子夜歌11 小时前
SQL进阶——自连接
数据库·sql
云贝教育-郑老师11 小时前
TDSQL(MySQL版)分布式事务实现机制深度解析:从两阶段提交到全局一致性读
数据库·sql
gb448oww511 小时前
Redis分布式锁进阶第三十五篇
数据库·redis·分布式
Full Stack Developme12 小时前
正则表达式设计及工作原理
数据库·mysql·正则表达式
云飞云共享云桌面12 小时前
搭建10人SolidWorks云设计环境:云飞云在非标自动化工厂的实测方案
运维·服务器·网络·数据库·自动化·电脑
A-刘晨阳12 小时前
关键基础设施安全底座:自主可控时序大模型TimechoAI的国产化实践与深度时序分析能力
大数据·数据库·安全·时序数据库
深盾科技_Virbox12 小时前
Virbox Protector 从何而来:深盾科技的软件保护演进
运维·数据库·科技
程序员讲BPM工作流15 小时前
BPM工作流平台多租户独立数据库轻量级革新方案
数据库