数据类型

    本文介绍了 Meilisearch 如何处理数据集中的不同数据类型。

    此处描述的行为仅与 Meilisearch 的内部流程有关,有助于理解分词器的工作原理。对于大多数与 Meilisearch 内部运作无关的实际用途,文档字段保持不变。

    字符串

    字符串是在 Meilisearch 中索引数据的主要类型。它能够创建要搜索的内容。字符串的处理方式如下详述。

    字符串分词是将字符串拆分为称为标记的各个术语列表的过程。

    字符串被传递给分词器,然后被分解为单独的字符串标记。标记是一个

    分词

    分词依赖于两个主要过程来识别词并将它们分隔成标记:分隔符和字典。

    分隔符

    分隔符是指示一个词在哪里结束,另一个词在哪里开始的字符。例如,在使用拉丁字母的语言中,词通常由空格分隔。在日语中,词界通常以其他方式指示,例如在词尾附加助词,如

    Meilisearch 中有两种分隔符:软分隔符和硬分隔符。硬分隔符表示重要的上下文切换,例如新句子或段落。软分隔符仅将一个词与另一个词分隔开,但不暗示主题的重大变化。

    下面的列表列出了使用拉丁字母的语言中最常见的一些分隔符

    有关更多分隔符,包括西里尔文和泰文等其他书写系统中使用的分隔符,请查阅此详尽列表

    字典

    对于分词过程,字典是应被视为单个术语的字符组列表。当识别日语等语言中的词时,字典特别有用,在日语中,词并不总是用分隔符标记。

    Meilisearch 配备了许多通用字典,用于其官方支持的语言。当处理包含许多特定领域术语的文档(例如法律文档或学术论文)时,提供自定义字典可能会提高搜索结果的相关性。

    距离

    距离在确定文档是否相关方面起着至关重要的作用,因为排名规则之一是邻近度规则。邻近度规则按匹配查询词之间距离的增加对结果进行排序。然后,由软空格分隔的两个词比由硬空格分隔的两个词更接近,因此被认为更相关

    在分词过程之后,每个词都被索引并存储在相应索引的全局字典中。

    示例

    为了演示字符串如何按空格拆分,假设您有以下字符串作为输入

    "Bruce Willis,Vin Diesel"
    

    在上面的示例中,BruceWillis 之间的距离等于 1VinDiesel 之间的距离也为 1。但是,WillisVin 之间的距离等于 8。相同的计算适用于 BruceDiesel (10)、BruceVin (9) 以及 WillisDiesel (9)。

    让我们看另一个例子。给定两个文档

    [
      {
        "movie_id": "001",
        "description": "Bruce.Willis"
      },
      {
        "movie_id": "002",
        "description": "Bruce super Willis"
      }
    ]
    

    当对 Bruce Willis 进行查询时,002 将是返回的第一个文档,而 001 将是第二个文档。发生这种情况是因为文档 002BruceWillis 之间的邻近度距离等于 2,而文档 001BruceWillis 之间的距离等于 8,因为句点字符 . 是一个硬空格。

    数值

    数值类型(integerfloat)被转换为人类可读的十进制数字字符串表示形式。数值类型可以被搜索,因为它们被转换为字符串。

    您可以添加自定义排序规则,以便在文档中具有数值的给定属性上创建升序或降序排序规则。

    您还可以创建过滤器>>=<<=TO 关系运算符仅适用于数值。

    布尔值

    布尔值(truefalse)会被接收并转换为小写的、人类可读的文本 (truefalse)。布尔值可以像字符串一样被搜索,因为它们会被转换为字符串。

    null

    null 类型可以被推送到 Meilisearch 中,但它不会被纳入索引

    数组

    数组是值的有序列表。这些值可以是任何类型:数字、字符串、布尔值、对象,甚至可以是其他数组。

    Meilisearch 会扁平化数组并将它们连接成字符串。非字符串值将按照本文前几节所述的方式进行转换。

    示例

    以下输入

    [
      [
        "Bruce Willis",
        "Vin Diesel"
      ],
      "Kung Fu Panda"
    ]
    

    将被处理,就好像所有元素都排列在同一级别

    "Bruce Willis. Vin Diesel. Kung Fu Panda."
    

    一旦上述数组被扁平化,它将完全按照字符串示例中解释的方式进行解析。

    对象

    当文档字段包含对象时,Meilisearch 会将其扁平化,并将对象的键和值带到文档本身的根级别。

    请记住,此处表示的扁平化对象是内部过程的中间快照。搜索时,返回的文档将保持其原始结构。

    在下面的示例中,patient_name 键包含一个对象

    {
      "id": 0,
      "patient_name": {
        "forename": "Imogen",
        "surname": "Temult"
      }
    }
    

    在索引编制期间,Meilisearch 使用点表示法来消除嵌套字段

    {
      "id": 0,
      "patient_name.forename": "Imogen",
      "patient_name.surname": "Temult"
    }
    

    使用点表示法,在扁平化嵌套对象时不会丢失任何信息,无论嵌套深度如何。

    假设上面的示例文档包含一个额外的对象 address,其中包含家庭地址和工作地址,每个地址本身都是对象。扁平化后,文档将如下所示

    {
      "id": 0,
      "patient_name.forename": "Imogen",
      "patient_name.surname": "Temult",
      "address.home.street": "Largo Isarco, 2",
      "address.home.postcode": "20139",
      "address.home.city": "Milano",
      "address.work.street": "Ca' Corner Della Regina, 2215",
      "address.work.postcode": "30135",
      "address.work.city": "Venezia"
    }
    

    Meilisearch 的内部扁平化过程也消除了对象数组中的嵌套。在这种情况下,值按键分组。考虑以下文档

    {
      "id": 0,
      "patient_name": "Imogen Temult",
      "appointments": [
        {
          "date": "2022-01-01",
          "doctor": "Jester Lavorre",
          "ward": "psychiatry"
        },
        {
          "date": "2019-01-01",
          "doctor": "Dorian Storm"
        }
      ]
    }
    

    扁平化后,它将如下所示

    {
      "id": 0,
      "patient_name": "Imogen Temult",
      "appointments.date": [
        "2022-01-01",
        "2019-01-01"
      ],
      "appointments.doctor": [
        "Jester Lavorre",
        "Dorian Storm"
      ],
      "appointments.ward": [
        "psychiatry"
      ]
    }
    

    一旦文档中的所有对象都被扁平化,Meilisearch 将继续按照前面章节中的描述处理它。例如,数组将被扁平化,数值和布尔值将被转换为字符串。

    嵌套文档查询和子文档

    Meilisearch 没有子文档的概念,并且无法执行嵌套文档查询。在前面的示例中,当扁平化 appointments 数组时,预约日期和医生之间的关系会丢失

    "appointments.date": [
        "2022-01-01",
        "2019-01-01"
      ],
      "appointments.doctor": [
        "Jester Lavorre",
        "Dorian Storm"
      ],

    这可能会导致搜索期间出现意外行为。以下数据集显示了两位患者及其各自的预约

    [
      {
        "id": 0,
        "patient_name": "Imogen Temult",
        "appointments": [
          {
            "date": "2022-01-01",
            "doctor": "Jester Lavorre"
          }
        ]
      },
      {
        "id": 1,
        "patient_name": "Caleb Widowgast",
        "appointments": [
          {
            "date": "2022-01-01",
            "doctor": "Dorian Storm"
          },
          {
            "date": "2023-01-01",
            "doctor": "Jester Lavorre"
          }
        ]
      }
    ]
    

    以下查询返回患者 01

    curl \
      -X POST 'MEILISEARCH_URL/indexes/clinic_patients/search' \
      -H 'Content-Type: application/json' \
      --data-binary '{
        "q": "",
        "filter": "(appointments.date = 2022-01-01 AND appointments.doctor = 'Jester Lavorre')"
      }'
    

    Meilisearch 无法仅返回在 2022-01-01Jester Lavorre 预约的患者。相反,它返回了与 Jester Lavorre 预约的患者,以及在 2022-01-01 预约的患者。

    解决此限制的最佳方法是重新格式化您的数据。上面的示例可以通过将预约数据合并到一个新的 appointmentsMerged 字段中来修复,从而保持预约和医生之间的关系完整

    [
      {
        "id": 0,
        "patient_name": "Imogen Temult",
        "appointmentsMerged": [
          "2022-01-01 Jester Lavorre"
        ]
      },
      {
        "id": 1,
        "patient_name": "Caleb Widowgast",
        "appointmentsMerged": [
          "2023-01-01 Jester Lavorre"
          "2022-01-01 Dorian Storm"
        ]
      }
    ]
    

    可能的分词问题

    即使它的行为完全符合预期,但在某些情况下,分词过程可能会导致违反直觉的结果,例如

    "S.O.S"
    "George R. R. Martin"
    10,3
    

    对于上面的两个字符串,句点 . 将被视为硬空格。

    10,3 将被分解为两个字符串——103——而不是作为数值类型处理。