# 如何让 xt850 匹配 xt 850

一份实用指南，解决类似 xt850 vs xt 850 的粘连与分隔搜索查询问题，清晰解释 bigram_delimiter、second_numeric、second_has_digit 以及可复现的示例。

## TL;DR

从版本 `23.0.0` 开始，Manticore 可以通过 [bigram_delimiter](https://manual.manticoresearch.com/dev/Creating_a_table/NLP_and_tokenization/Low-level_tokenization#bigram_delimiter) 和数字感知的 [bigram_index](https://manual.manticoresearch.com/dev/Creating_a_table/NLP_and_tokenization/Low-level_tokenization#bigram_index) 模式，使搜索如 `xt850` 匹配 `xt 850`。

这解决了产品搜索中常见的分词不匹配问题，即用户从型号名称中删除空格，但源数据将其存储为单独的分词。

## 假设与验证

本文假设：

- 使用 SQL 示例创建的 RT 表完全按照所示方式创建
- 默认分词，除非示例明确更改了设置
- 型号名称中使用 ASCII 数字，因为 `second_numeric` 和 `second_has_digit` 是围绕 `0-9` 构建的数字感知模式

本文中所有 SQL 示例和预期输出在发布前都已通过真实的 Manticore `23.0.0` 实例验证，每个场景都使用从头开始创建的新表进行测试。

## 更广泛搜索问题

想象一个包含以下内容的目录：

- `xt 850 action camera`
- `iphone 5se battery case`
- `canon eos 80d body`
- `thinkpad x1 carbon`

现在想象用户搜索：

- `xt850`
- `iphone5se`
- `eos80d`
- `thinkpadx1`

从用户的角度来看，这些显然应该匹配。但从引擎的角度来看，它们通常不会匹配，因为索引文本被分词为单独的术语。

搜索系统通常通过以下四种方式之一解决这种不匹配：

- 索引前缀或中缀
- 添加自定义规范化规则
- 将内容复制到备用规范化字段
- 索引相邻分词对，并可选地存储粘连变体

Manticore 的新 bigram 功能是一种结构化的方法，可以在不进行笨拙字段复制的情况下实现第四种选项。

## 基线：为什么 `xt850` 默认失败

这是最简单形式的问题：

```sql
DROP TABLE IF EXISTS bi_default_demo;

CREATE TABLE bi_default_demo(title text);

INSERT INTO bi_default_demo VALUES
  (1,'xt 850 action camera');

SELECT id, title FROM bi_default_demo WHERE MATCH('xt850');
```

预期结果：

```sql
Empty set
```

为什么这会失败？

因为文档被索引为两个单独的分词，`xt` 和 `850`，而查询是一个单独的分词，`xt850`。

默认情况下，Manticore 不会假设：

- `xt850` 应该拆分为 `xt` + `850`
- 或 `xt` + `850` 也应可搜索为 `xt850`

因此，这并不是一个拼写容错问题或短语问题。这是一个分词不匹配问题：索引看到两个分词，而查询提供了一个。

这就是新 bigram 设置旨在解决的差距。它们让 Manticore 以可以匹配粘连查询的形式索引选定的相邻分词对。

## 为什么 bigrams 在这里有帮助

[bigram_index](https://manual.manticoresearch.com/dev/Creating_a_table/NLP_and_tokenization/Low-level_tokenization#bigram_index) 可以帮助[短语加速](/blog/how-to-speed-up-phrase-search-with-bigram-index/)和型号名称匹配，在本文中我们专注于 `xt 850` vs `xt850` 的问题。

关键思想很简单：

- 检测看起来像型号名称的相邻分词对
- 同时以粘连形式存储这些对
- 让查询如 `xt850`、`iphone5se` 或 `thinkpadx1` 匹配空格文本

这就是 [bigram_delimiter](https://manual.manticoresearch.com/dev/Creating_a_table/NLP_and_tokenization/Low-level_tokenization#bigram_delimiter) 的作用所在。

## 关于 [bigram_delimiter](https://manual.manticoresearch.com/dev/Creating_a_table/NLP_and_tokenization/Low-level_tokenization#bigram_delimiter) 的说明

`bigram_index` 决定哪些相邻对有资格。

`bigram_delimiter` 决定有资格的 bigrams 如何存储：

- `true`：仅内部分隔的分词
- `none`：仅粘连分词，如 `galaxy24`
- `both`：两种形式都保留

从查询的角度来看，实际差异最容易理解：

- 使用 `true` 时，Manticore 保留用于短语优化的内部 bigram 形式，但不保留用户面向的粘连形式，因此像 `xt850` 这样的查询将无法匹配 `xt 850`
- 使用 `none` 时，Manticore 仅保留粘连形式，因此 `xt850` 可以匹配 `xt 850`，但你完全依赖这些对的粘连表示
- 使用 `both` 时，Manticore 保留内部 bigram 表示和粘连形式，因此 `xt850` 可以匹配 `xt 850` 而不会牺牲普通短语查询和混合工作负载的行为

对于此用例，`both` 通常是更安全的默认值，因为它直接解决了用户可见的问题，同时保持对普通短语查询和混合工作负载的行为更少令人意外。

## 模式 1：`second_numeric`

```ini
bigram_index = second_numeric
bigram_delimiter = both
```

此模式针对第二个分词纯粹是数字的型号名称。

这在产品目录中很常见：

- `xt 850`
- `galaxy 24`
- `playstation 5`
- `pixel 8`

这个想法很简单：用户经常将这些搜索为粘连术语，如 `xt850`、`galaxy24` 或 `playstation5`，即使源文本中存储的是带空格的形式。

`second_numeric` 仅在第二个分词是纯 ASCII 数字时存储该对。

使用它时：

- 你有产品世代和编号型号
- 用户经常在搜索中删除空格
- 第二个分词通常是数字

### 示例

```sql
DROP TABLE IF EXISTS bi_second_numeric_demo;

CREATE TABLE bi_second_numeric_demo(title text)
  bigram_index='second_numeric'
  bigram_delimiter='both';

INSERT INTO bi_second_numeric_demo VALUES
  (1,'xt 850 action camera'),
  (2,'galaxy 24 ultra'),
  (3,'playstation 5 slim'),
  (4,'iphone 5se case'),
  (5,'canon eos 80d body'),
  (6,'thinkpad x1 carbon');
```

然后逐个测试查询：

```sql
SELECT id, title FROM bi_second_numeric_demo WHERE MATCH('xt850');

+------+----------------------+
| id   | title                |
+------+----------------------+
|    1 | xt 850 action camera |
+------+----------------------+
```

```sql
SELECT id, title FROM bi_second_numeric_demo WHERE MATCH('galaxy24');

+------+-----------------+
| id   | title           |
+------+-----------------+
|    2 | galaxy 24 ultra |
+------+-----------------+
```

```sql
SELECT id, title FROM bi_second_numeric_demo WHERE MATCH('playstation5');

+------+--------------------+
| id   | title              |
+------+--------------------+
|    3 | playstation 5 slim |
+------+--------------------+
```

```sql
SELECT id, title FROM bi_second_numeric_demo WHERE MATCH('iphone5se');

Empty set
```

```sql
SELECT id, title FROM bi_second_numeric_demo WHERE MATCH('eos80d');

Empty set
```

```sql
SELECT id, title FROM bi_second_numeric_demo WHERE MATCH('thinkpadx1');

Empty set
```

这个边界就是该模式的全部重点：

- `24` 和 `5` 符合条件
- `5se`、`80d` 和 `x1` 不符合条件

## 模式 2：`second_has_digit`

```ini
bigram_index = second_has_digit
bigram_delimiter = both
```

此模式是 `second_numeric` 的更灵活的兄弟模式。

当第二个分词包含至少一个 ASCII 数字时，它会存储该对。这使其更适配实际产品目录，其中型号标识符通常是混合的字母数字字符串：

- `xt 850`
- `iphone 5se`
- `eos 80d`
- `thinkpad x1`

使用它时：

- 您的型号名称混合了字母和数字  
- 用户经常在搜索中删除空格  
- 您希望实现目录友好的匹配，而无需为表中的每一对进行索引  

### 示例  

```sql
DROP TABLE IF EXISTS bi_second_has_digit_demo;

CREATE TABLE bi_second_has_digit_demo(title text)
  bigram_index='second_has_digit'
  bigram_delimiter='both';

INSERT INTO bi_second_has_digit_demo VALUES
  (1,'xt 850 action camera'),
  (2,'galaxy 24 ultra'),
  (3,'playstation 5 slim'),
  (4,'iphone 5se case'),
  (5,'canon eos 80d body'),
  (6,'thinkpad x1 carbon'),
  (7,'kindle paperwhite signature');
```

然后逐个测试查询：  

```sql
SELECT id, title FROM bi_second_has_digit_demo WHERE MATCH('xt850');

+------+----------------------+
| id   | title                |
+------+----------------------+
|    1 | xt 850 action camera |
+------+----------------------+
```

```sql
SELECT id, title FROM bi_second_has_digit_demo WHERE MATCH('galaxy24');

+------+-----------------+
| id   | title           |
+------+-----------------+
|    2 | galaxy 24 ultra |
+------+-----------------+
```

```sql
SELECT id, title FROM bi_second_has_digit_demo WHERE MATCH('iphone5se');

+------+---------------------+
| id   | title               |
+------+---------------------+
|    4 | iphone 5se case     |
+------+---------------------+
```

```sql
SELECT id, title FROM bi_second_has_digit_demo WHERE MATCH('eos80d');

+------+---------------------+
| id   | title               |
+------+---------------------+
|    5 | canon eos 80d body  |
+------+---------------------+
```

```sql
SELECT id, title FROM bi_second_has_digit_demo WHERE MATCH('thinkpadx1');

+------+---------------------+
| id   | title               |
+------+---------------------+
|    6 | thinkpad x1 carbon  |
+------+---------------------+
```

```sql
SELECT id, title FROM bi_second_has_digit_demo WHERE MATCH('kindlesignature');

Empty set
```

这通常更适合混合型号标识符，因为实际目录数据经常包含类似 `5se`、`80d` 或 `x1` 的形式，而不仅仅是像 `24` 这样的干净数字后缀。  

## 如何在两者之间选择  

如果您的搜索问题具体是“如何让 `xt850` 找到 `xt 850`？”，实用规则是：  

- 当第二个标记仅为数字时，使用 `second_numeric`  
- 当第二个标记可能是混合形式（如 `5se`、`80d` 或 `x1`）时，使用 `second_has_digit`  

有一个实用的注意事项：在简单情况下，这与其它常见的文本处理设置兼容。启用 `morphology='stem_en'` 并启用词形规则时，`xt 850` 仍能匹配 `xt850`。  

但这并不意味着这些设置会为您重写拼接的查询。在测试中，`iphones 5` 匹配了 `iphones5`，但未匹配 `iphone5`，即使启用了词干提取或词形规则将 `iphones` 映射到 `iphone`。因此简短的结论是：基本的 `xt 850` 与 `xt850` 匹配仍与词形和词形规则兼容，但如果您依赖这些功能，请测试您关心的确切查询形式。  

## 最终要点  

`xt850` 问题实际上并不只是关于一个产品名称。它涉及用户输入型号名称的方式与搜索引擎分词方式之间的更广泛不匹配。  

从版本 `23.0.0` 开始，Manticore 提供了一种内置方法来处理这种不匹配，即通过 `bigram_delimiter` 加上针对数字的 `bigram_index` 模式，这比复制字段或发明自定义预处理管道要干净得多。  

如果您的主要问题是短语搜索性能而非粘连型号名称匹配，请参阅 [如何通过 bigram_index 提高短语搜索速度](/blog/how-to-speed-up-phrase-search-with-bigram-index/)。
