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

相关推荐
rockmelodies3 分钟前
【MongoDB + 向量搜索引擎】MongoDB Atlas 向量搜索 提供全托管解决方案
数据库·mongodb·搜索引擎
西元.1 小时前
详解 Redis repl_backlog_buffer(如何判断增量同步)
数据库·redis·缓存
老华带你飞2 小时前
木里风景文化|基于Java+vue的木里风景文化管理平台的设计与实现(源码+数据库+文档)
java·数据库·vue.js·毕业设计·论文·风景·木里风景文化管理平台
睡睡怪2 小时前
Mysql入门
数据库·mysql·oracle
java_heartLake3 小时前
PostgreSQL 16深度解析(从16.0-16.8)
数据库·postgresql
Themberfue5 小时前
SQL ②-库操作 | 数据类型
数据库·sql·mysql
li_Michael_li5 小时前
MySQL Explain 分析 SQL 执行计划
数据库·sql·mysql
tjsoft5 小时前
Nginx之https重定向为http
数据库
Yan-英杰5 小时前
【百日精通JAVA | SQL篇 | 第四篇】约束
java·服务器·开发语言·数据库·人工智能·sql·mysql