# 常见数据类型搜索技巧

本文将介绍如何索引和搜索以下类型的数据:

# 型号/零件编号/SKU

假设您有一个包含产品标识符(型号、零件编号或SKU)的文档,其中混合了字母数字字符和特殊字符:

{
  "title": "控制臂衬套套件",
  "part_number": "K83913.39F29.59444AT"
  //...
}

现在您希望该产品在以下任何搜索词中都能显示在搜索结果中:

  • K83913
  • 83913
  • 39F29
  • 59444AT
  • 59444
  • 9444AT
  • K83913.39F29
  • 39F29.59444

# 默认行为

默认情况下,Typesense在索引和搜索字段时会移除特殊字符。 因此K83913.39F29.59444AT会被索引为K8391339F2959444AT

默认情况下,Typesense执行前缀搜索(Prefix Search),这意味着它只搜索字符串_开头_与搜索词匹配的记录。 因此搜索出现在K83913.39F29.59444AT中间的39F29F29不会返回该记录。 但搜索K83913K83913.39K83913.39F29.59444K83913.39会返回该记录。

# 精细调优

我们需要做的第一个调整是告诉 Typesense 使用 .(句点)作为产品标识符的分隔符。 这样 K83913.39F29.59444AT 就会被索引为三个独立的词元(单词):K8391339F2959444AT。 现在当你搜索 39F295944 时,就能返回产品 K83913.39F29.59444AT

你可以通过在 schema 参数 中设置 token_separators,在 创建集合 时实现:







 


{
  "name": "products",
  "fields": [
    {"name":  "title", "type":  "string"},
    {"name":  "part_number", "type":  "string"}
  ],
  "token_separators": ["."]
}

字段级控制

从 Typesense v28.0 开始,你可以在字段级别配置 token_separatorssymbols_to_index,这些设置会覆盖集合级别的配置。

示例:

{
  "name": "products",
  "fields": [
    {
      "name": "part_number",
      "type": "string",
      "token_separators": ["-", "."],
      "symbols_to_index": ["_"]
    }
  ]
}

但我们仍然需要处理搜索 839139444AT 这类出现在字符串中间的情况。

针对这个问题,我们有两个解决方案:

  1. 使用 v0.23.0 版本新增的 infix 搜索功能:

    https://github.com/typesense/typesense/issues/393#issuecomment-1065367947 (opens new window)

    注意:对于长字符串,这可能是计算密集型的操作。 如果你发现特定用例中 CPU 使用率升高,可以考虑使用下面的方案。

  2. 根据用户的预期搜索方式预先拆分产品标识符:

    {
      "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 会移除所有特殊字符,并通过空格分割标记(单词),因此 +1 (234) 567-8901 会被索引为 12345678901

所以搜索 2345678901234 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 将被索引为 12345678901,现在以下搜索都能返回该文档:

  • 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", // 移除所有空格
    "2345678901", // 移除所有空格和国家代码
    "5678901" // 移除所有空格、国家代码和区号
  ]
}

现在,搜索以上任意一种模式都能找到这条记录。

# 电子邮件地址处理

假设我们有一个电子邮件地址 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 被索引为 contactdocsexampletypesenseorg

现在所有搜索词都能匹配到这条记录:

  • 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 (opens new window)

# 日期/时间处理

Typesense 没有原生的日期/时间数据类型

因此你需要按照这里的说明将日期和时间转换为 Unix 时间戳。

# 嵌套对象

# 从 Typesense v0.24.0 开始

Typesense v0.24.0 原生支持嵌套对象和对象数组。

要启用嵌套字段,您需要在创建集合时使用 enable_nested_fields 属性,并配合 数据类型 objectobject[]



 






{
  "name": "docs", 
  "enable_nested_fields": true,
  "fields": [
    {"name": "person", "type": "object"},
    {"name": "details", "type": "object[]"}
  ]
}

了解更多信息请点击 这里

# Typesense v0.23.1 及更早版本

Typesense v0.23.1 及更早版本仅支持索引整型、浮点型、字符串、布尔值以及包含这些数据类型的数组字段值。 集合中的字段只能指定这些将被索引的数据类型。

重要补充说明: 您仍然可以向 Typesense 发送包含嵌套对象的字段(只要这些字段未在 schema 中声明)。这些字段不会被索引或类型检查,只会被存储在磁盘上,当文档匹配搜索查询时原样返回。

Typesense 目前明确不支持对嵌套对象或对象数组进行 索引搜索过滤 操作。我们计划在短期内添加对此功能的支持(参见 #227 (opens new window))。在此之前,您需要将对象和对象数组展平为顶级键,然后再将数据发送到 Typesense。

例如,包含嵌套对象的文档如下:

{
  "nested_field": {
    "field1": "value1",
    "field2": ["value2", "value3", "value4"],
    "field3": {
      "fieldA": "valueA",
      "fieldB": ["valueB", "valueC", "valueD"]
    }
  }
}  

需要展平为:

{
  "nested_field.field1": "value1",
  "nested_field.field2":  ["value2", "value3", "value4"],
  "nested_field.field3.fieldA": "valueA",
  "nested_field.field3.fieldB": ["valueB", "valueC", "valueD"]
}

然后才能索引到 Typesense 中。

为了简化结果数据的遍历,您可以同时向 Typesense 发送嵌套字段的展平版本和原始版本,在集合 schema 中仅将展平键设置为可索引,并用于搜索/过滤/分面操作。在显示结果时解析数据时,则可以使用嵌套版本。

# 地理坐标

Typesense 支持使用文档中的经纬度数据进行地理搜索(GeoSearch)查询。您可以:

  • 过滤给定经纬度半径范围内的文档
  • 按与指定经纬度的接近程度排序结果
  • 返回边界框内的结果

有关地理搜索查询的更多信息,请参阅:GeoSearch API 参考

# 长文本处理

如果您需要处理长文本内容,比如长篇期刊文章、网页内容或文字记录等,我们建议您将长文本拆分成较小的"段落",并将每个段落作为单独的文档存储在Typesense中。

这种方式可以提高搜索结果的粒度并增强相关性。因为如果文本过长,文档之间可能会出现大量关键词重叠,导致搜索常见关键词时匹配到过多文章。

# HTML内容处理

如果要搜索HTML内容,您需要在文档中创建一个仅包含纯文本内容(不含HTML标签)的字段,并在query_by搜索参数中使用该字段。

您仍然可以将原始HTML字段作为未索引字段存储在文档中(只需在schema中不声明该字段),这样当文档匹配时,原始HTML内容仍会被返回。

这里 (opens new window)提供了更多相关背景信息。

# 搜索null或空值

Typesense原生不支持直接筛选属性值为null或空的文档。

但您可以通过以下方法实现这一需求。假设您的文档中有一个可选字段tags可能为null

{
  "tags": null
}

如果要查找所有tagsnull的文档,您需要在索引时为每个文档创建一个额外的字段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"}

您希望当用户搜索 url1path1 等内容时,Typesense 能够返回相应结果。

# 默认行为

默认情况下,Typesense 会移除所有特殊字符并将第一个文档索引为 httpsurl1compath1。 此外,Typesense 执行的是前缀搜索(匹配必须出现在单词开头),因此 url1path1 不会返回任何结果,因为它们出现在索引字符串的中间位置。

# 优化方案

为了解决这个问题并仍然能检索到 url1path1 的结果,您需要在集合模式中的 token_separators 设置里添加 :./







 


{
  "name": "pages",
  "fields": [
    {"name":  "title", "type":  "string"},
    {"name":  "url", "type":  "string"}
  ],
  "token_separators": [":", "/", "."]
}

这样配置后,URL 将被索引为独立的单词:httpsurl1compath1

现在当您搜索 url1path 时,系统会匹配这些独立单词并返回对应文档。

# 其他数据类型

如果您有其他特定类型的数据需要帮助在 Typesense 中进行索引, 请提交 GitHub issue (opens new window) 或加入我们的 Slack 社区 (opens new window) 咨询。