Typesense:通用数据类型搜索技巧
本文中,我们将讨论在 Typesense 中如何为以下数据类型创建索引和搜索:
产品型号 / 部件号码 / SKU
假设你有一个包含产品标识符(型号、零件号或 SKU)的文档(document),其中混合了字母数字字符和特殊字符:
{
"title": "Control Arm Bushing Kit",
"part_number": "K83913.39F29.59444AT"
//...
}
现在,假设你希望此产品出现在以下任何搜索词的搜索结果中
K83913
83913
39F29
59444AT
59444
9444AT
K83913.39F29
39F29.59444
默认行为
默认情况下,Typesense 在索引和搜索字段时会从字段中删除特殊字符。因此,K83913.39F29.59444AT
将被索引为K8391339F2959444AT
。
默认情况下,Typesense 执行前缀搜索,这意味着它只搜索搜索条件中位于字符串开头的记录。因此,搜索出现在 K83913.39F29.59444AT
中间的 39F29
或 F29
不会调出该记录。但是搜索 K83913
或 K83913.39
或 K83913.39F29.59444
或 K83913.39
将调出该记录。
改进
我们需要做的第一个更改是告诉 Typesense 将根据 .
(点号)拆分产品标识符。这样,K83913.39F29.59444AT
将被索引为三个单独的 token(单词):K83913
、39F29
和59444AT
。现在,当搜索 39F29
或 5944
时,将返回产品 K83913.39F29.59444AT
。
你可以在创建集合时通过在 schema 中设置 token_separators
来实现这一点:
{
"name": "products",
"fields": [
{"name": "title", "type": "string"},
{"name": "part_number", "type": "string"}
],
"token_separators": ["."]
}
我们仍然会碰到搜索在字符串中间出现的 83913
或 9444AT
的情况。
要解决该问题,我们有两种方式:
使用 v0.23.0
起新增的 infix
搜索特性:
https://github.com/typesense/typesense/issues/393#issuecomment-1065367947(打开新窗口)
注意:对于长字符串,这可能是一个计算密集型的操作。如果你注意到特定用例的 CPU 使用率有所增加,则应使用下面的选项。
根据你期望用户搜索的方式预先拆分产品标识符:
{
"title": "Control Arm Bushing Kit",
"part_number": [
"K83913.39F29.59444AT",
"83913.39F29.59444AT",
"3913.39F29.59444AT",
"913.39F29.59444AT",
"13.39F29.59444AT",
"3.39F29.59444AT",
"9F29.59444AT",
"F29.59444AT",
"29.59444AT",
"9.59444AT",
"9444AT",
"444AT",
"44AT",
"4AT",
"AT"
]
//...
}
当将其与 token_separators
结合使用时,你将能够搜索我们上面讨论的所有模式。
电话号码
假设我们的电话号码格式是这样的:+1 (234) 567-8901
,而我们希望用户通过以下任何一种格式可以拉取该记录:
8901
567-8901
567 8901
5678901
234-567-8901
(234) 567-8901
(234)567-8901
1-234-567-8901
+12345678901
12345678901
2345678901
+1(234)567-8901
默认行为
默认情况下,Typesense 将移除所有特殊字符,并按照空格拆分 token(单词),因此,+1 (234) 567-8901
会被索引为 1
, 234
, 5678901
。
因此,搜索 234
或 5678901
或 234 567-8901
,将会返回结果,而其他格式不会返回预期的结果。
改进
首先告诉 Typesense 通过(
、)
来拆分,并且在创建集合时在 schema 中使进行 token_separators
设置:
{
"name": "users",
"fields": [
{"name": "first_name", "type": "string"},
{"name": "phone_number", "type": "string"}
],
"token_separators": ["(", ")", "-"]
}
这将导致 +1 (234) 567-8901
被索引为 1
、234
、567
和 8901
,现在以下搜索将返回该文档:
8901
567-8901
567 8901
234-567-8901
(234) 567-8901
(234)567-8901
1-234-567-8901
+1(234)567-8901
剩余需要处理的用例如下:
5678901
+12345678901
12345678901
2345678901
要解决这个问题,你需要在文档中将这些格式作为 string[]
数组字段添加:
{
"name": "users",
"fields": [
{"name": "first_name", "type": "string"},
{"name": "phone_number", "type": "string[]"}
],
"token_separators": ["(", ")", "-"]
}
{
"name": "Tom",
"phone_number": [
"+1 (234) 567-8901",
"12345678901", // Remove all spaces
"2345678901", // Remove all spaces and country code
"5678901" // Remove all space, country code and area code
]
}
现在,搜索上述任何模式都将调出此记录。
Email 地址
假设我们有一个类似于 contact+docs-example@typesense.org
的电子邮件地址,我们希望用户能够使用以下任何模式来提取此文档:
contact+docs-example
contact+docs-example@
contact+docs-example@typesense
contact+docs
contact docs
docs example
contact typesense
contact
docs
example
typesense
typesense.org
默认行为
默认情况下,Typesense 将在索引过程中删除所有特殊字符,并且只进行前缀搜索(搜索词应位于单词的开头),因此 contact+docs-example@typesense.org
将被索引为 contactdocsexampletypesense.org
。
因此,带有 ✅ 的搜索词将返回该记录,而带有 ❌ 的内容将 不会返回该记录:
- ✅
contact+docs-example
- ✅
contact+docs-example@
- ✅
contact+docs-example@typesense
- ✅
contact+docs
- ❌
contact docs
- ❌
docs example
- ❌
contact typesense
- ✅
contact
- ❌
docs
- ❌
example
- ❌
typesense
- ❌
typesense.org
改进
为了解决上述剩余的情况,我们可以在创建集合时使用模式中的 token_separators
设置:
{
"name": "users",
"fields": [
{"name": "first_name", "type": "string"},
{"name": "email", "type": "string"}
],
"token_separators": ["+", "-", "@", "."]
}
这将使 contact+docs-example@typesense.org
被索引为 contact
、docs
、example
、typesense
和 org
。
现在所有这些搜索条件都能拉取该文档:
- ✅
contact+docs-example
- ✅
contact+docs-example@
- ✅
contact+docs-example@typesense
- ✅
contact+docs
- ✅
contact docs
- ✅
docs example
- ✅
contact typesense
- ✅
contact
- ✅
docs
- ✅
example
- ✅
typesense
- ✅
typesense.org
如果你也希望 ample
返回此记录,可以使用 v0.23.0
中提供的 infix
中缀搜索功能: https://github.com/typesense/typesense/issues/393#issuecomment-1065367947(打开新窗口)
日期/时间
Typesense 没有原生的日期/时间(date/time)数据类型。
因此,你需要像此处描述的那样,将日期和时间转换成 Unix 时间戳。
嵌套对象
始于 Typesense v0.24.0
Typesense v0.24.0 原生支持嵌套对象及对象数组。
要启用嵌套字段,你需要在创建集合时使用 enable_nested_fields
属性,以及 object
或 object[]
数据类型:
{
"name": "docs",
"enable_nested_fields": true,
"fields": [
{"name": "person", "type": "object"},
{"name": "details", "type": "object[]"}
]
}
此处了解更多详情。
Typesense v0.23.1 及其之前的版本
Typesense v0.23.1 及更早版本仅支持对整数、浮点数、字符串、布尔值和包含以上数据类型的数组的字段值进行索引。集合中的字段只能这些数据类型,这些字段将被索引。
重要附注:你仍然可以在 schema 里未提及的字段中将嵌套对象发送到 Typesense 中。它们将不会被索引或进行类型检查。只会存储在磁盘上,而如果文档是搜索查询的热门内容,则会返回。
Typesense 特别不支持索引、搜索或过滤嵌套对象或对象数组。作为(#227)的一部分,Typesense 计划很快添加对此的支持。与此同时,在将数据发送到 Typesense 之前,你必须将对象和对象数组展开(flatten)为顶级键。
比如,这样一个包含嵌套对象的文档:
{
"nested_field": {
"field1": "value1",
"field2": ["value2", "value3", "value4"],
"field3": {
"fieldA": "valueA",
"fieldB": ["valueB", "valueC", "valueD"]
}
}
}
需要在将其索引到 Typesense 之前,将其展开为:
{
"nested_field.field1": "value1",
"nested_field.field2": ["value2", "value3", "value4"],
"nested_field.field3.fieldA": "valueA",
"nested_field.field3.fieldB": ["valueB", "valueC", "valueD"]
}
为了简化对结果中数据的遍历,你可能希望将展开后的嵌套字段的和未处理过的两个版本都发送到 Typesense 中,并且只将展开的键(flattened key)设置为集合架构中的索引,并将其用于搜索/过滤/分面。在解析结果的显示时,你可以使用嵌套版本。
地理坐标
Typesense 支持在文档中使用纬度/经度数据进行 GeoSearch 查询。你可以在 lat/lng 周围的给定半径内过滤文档,或按与给定 lat/lng 的接近程度对结果进行排序,或在边界框内返回结果。
此处有 GeoSearch 地理查询的更多信息:GeoSearch API 相关。
长篇文本
如果你有长篇文本,比如一篇长篇期刊文章、网站页面、成绩单等,我们建议你将长篇文本分解为更小的“段落”,并将每个段落存储在 Typesense 中的单独文档中。
这有助于提高搜索结果的粒度并提高相关性,因为如果文本足够长,文档之间的关键字可能会有足够的重叠,搜索常见关键字最终会匹配大多数文章。
HTML 内容
如果搜索的是 HTML 内容,你需要在文档中创建一个仅包含内容的纯文本版本而不包含 HTML 标签的字段,并在 query_by
搜索参数中使用该字段。
你仍然可以将原始 HTML 字段作为未索引字段存储在文档中(只需将其从 schema 中保留即可),因此当原始 HTML 命中时,它将在文档中返回。
此处有更多关于这方面的内容。
搜索 null
或空值
Typesense 原生无法过滤属性值为 null
或空值的文档。
但你仍然可以通过以下方法实现这一点。
假设文档中有一个名为 tags
的可选字段,该字段可以为 null
:
{
"tags": null
}
如果你想获取所有 tags
设置为 null
的文档,你需要首先在索引时在每个文档中创建一个名为 is_tags_null: true | false
的额外字段:
[
{
"tags": null,
"is_tags_null": true
},
{
"tags": ["tag1", "tag3"],
"is_tags_null": false
}
]
当你在索引时将所有文档中的该字段进行设置后,你可以使用如下方式查询该文档:
{
"filter_by": "is_tags_null:true"
}
URL 或文件路径
假设你有一组像如下这样的 URL 或文件路径的文档,你希望像在其上面进行搜索:
{"url": "https://url1.com/path1"}
{"url": "https://url2.com/path2"}
{"url": "https://url3.com/path3"}
并且你希望当用户搜索 url1
或 path1
时,Typesense 返回结果。
默认行为
默认情况下,Typesense 将删除所有特殊字符,并将第一个文档索引为 httpsurl1compath1
。此外,Typesense 进行前缀搜索(匹配应该在单词的开头),因此 url1
或 path1
不会返回任何结果,因为它们出现在索引字符串的中间。
改进
为解决此问题,使之仍然可以获取 url1
或 path1
的结果,您需要添加:
、.
和 /
到集合 schema 中的 token_separators
设置中:
{
"name": "pages",
"fields": [
{"name": "title", "type": "string"},
{"name": "url", "type": "string"}
],
"token_separators": [":", "/", "."]
}
这将导致 URL 被索引为单独的单词:https
、url1
、com
、path1
。
现在,当搜索 url1
或 path
时,它将匹配这些单独的单词并返回该文档。
其他数据类型
如果有其他特定类型的数据需要帮助在 Typesense 中进行索引,请到 GitHub issue 或在 Slack 社区中询问。