Typesense:通用数据类型搜索技巧
本文中,我们将讨论在 Typesense 中如何为以下数据类型创建索引和搜索:
产品型号 / 部件号码 / SKU
假设你有一个包含产品标识符(型号、零件号或 SKU)的文档(document),其中混合了字母数字字符和特殊字符:
{
"title": "Control Arm Bushing Kit",
"part_number": "K83913.39F29.59444AT"
//...
}现在,假设你希望此产品出现在以下任何搜索词的搜索结果中
K839138391339F2959444AT594449444ATK83913.39F2939F29.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,而我们希望用户通过以下任何一种格式可以拉取该记录:
8901567-8901567 89015678901234-567-8901(234) 567-8901(234)567-89011-234-567-8901+12345678901123456789012345678901+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,现在以下搜索将返回该文档:
8901567-8901567 8901234-567-8901(234) 567-8901(234)567-89011-234-567-8901+1(234)567-8901
剩余需要处理的用例如下:
5678901+12345678901123456789012345678901
要解决这个问题,你需要在文档中将这些格式作为 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-examplecontact+docs-example@contact+docs-example@typesensecontact+docscontact docsdocs examplecontact typesensecontactdocsexampletypesensetypesense.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 社区中询问。