
# 通过嵌套字段实现向量检索
在处理长篇幅文档（如技术手册、法律条文）时，通常需要将文档按段落或固定长度切分为多个语义块，并分别为每个块生成特征向量。传统的扁平化存储结构会导致文档元数据（如ID、标题）与多个向量之间的对应关系难以维护，且检索时无法精准定位是哪一个段落触发了匹配。为了解决这个问题，CSS向量数据库支持了嵌套字段（Nested）中的向量检索：它允许您在单条主文档中封装多个"向量子文档"，实现"一主多从"的结构化存储。在查询时，只需一次请求即可扫描所有嵌套向量，并支持通过评分模式选取最相关的段落返回，极大简化了长文本检索的架构复杂度。
#### 功能介绍
在Elasticsearch/OpenSearch中，普通的object字段在底层会被"扁平化"处理，导致数组中不同子字段的关联关系丢失。而Nested类型将每个嵌套对象作为独立的"隐藏文档"存储，确保了每个嵌套对象中的"段落文本"与其对应的"向量"在检索时能够保持关联。
当您对Nested字段进行向量检索时，系统会经历以下过程：
1. 子文档扫描：在父文档内部遍历所有子文档的向量。
2. 局部评分：计算每个子向量与查询向量的相似度。
3. 分值聚合：根据score_mode决定父文档的最终得分。score_mode通常设为max，表示只要文档中有一个段落与查询最相关，该文档就会被排在前面。
 
#### 约束限制
- 仅Elasticsearch 7.10.2、OpenSearch 2.19.0和OpenSearch 3.4.0（镜像版本不低于3.4.0_26.6.0_xxx）版本的集群支持在Nested字段中使用向量检索。
- 嵌套字段会增加底层的文档总数，对于极大规模数据集，建议评估分片的承载压力。
- 嵌套字段不支持使用rescore语句进行重打分查询，仅支持使用query.vector语句（参见[标准向量查询](https://support.huaweicloud.com/usermanual-css/css_01_0123.html#ZH-CN_TOPIC_0000002555376597__zh-cn_topic_0000001268154489_section344710865418)）进行重打分查询。
 
#### 创建带有Nested字段的向量索引
执行以下命令，创建一个带有Nested字段的向量索引，该索引包含一个id字段，类型为keyword，包含一个embedding字段，类型为nested。embedding嵌套字段包含两个子字段chunk和emb，其中chunk为keyword类型，emb为vector类型。
```
PUT my_index
{
  "settings": {
    "index.vector": true
  },
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "embedding": {
        "type": "nested",        // 声明嵌套类型
        "properties": {
          "chunk": {
            "type": "keyword"
          },
          "emb": {               // 子字段中的向量
            "type": "vector",
            "dimension": 2,
            "indexing": true,
            "algorithm": "GRAPH",
            "metric": "euclidean"
          }
        }
      }
    }
  }
}
```
#### 导入多向量数据
执行以下命令，以数组形式一次性写入主文档及其关联的多个切片向量。每条文档包含了2条向量数据。
```
POST my_index/_bulk
{"index":{}}
{"id": 1, "embedding": [{"chunk":1,"emb": [1, 1]}, {"chunk":2,"emb": [2, 2]}]}
{"index":{}}
{"id": 2, "embedding": [{"chunk":1,"emb": [2, 2]}, {"chunk":2,"emb": [3, 3]}]}
{"index":{}}
{"id": 3, "embedding": [{"chunk":1,"emb": [3, 3]}, {"chunk":2,"emb": [4, 4]}]}
```
#### 执行嵌套向量检索
在嵌套路径中搜索最相似的段落，并按最大相关度对文档排序。Nested字段需要使用nested查询，查询时需要指定path参数以指明要查询的嵌套路径，以及必须设置score_mode为max，表示文档的得分为该文档中所有向量与查询向量相似度的最大值。
- 标准查询 查询与向量\[1, 1\]最相似的Top10文档。
  ```
  GET my_index/_search
  {
    "_source": {"excludes": ["embedding"]}, // 仅返回主文档ID，隐藏复杂的向量数组
    "query": {
      "nested": {
        "path": "embedding",                // 必须指定嵌套字段路径
        "score_mode": "max",                // 子文档分值合并到父文档的策略：取最匹配的段落得分
        "query": {
          "vector": {
            "embedding.emb": {
              "vector": [1, 1],
              "topk": 10
            }
          }
        }
      }
    }
  }
  ```
  查询结果示例如下：
  ```
  {
    "took" : 2,
    "timed_out" : false,
    "_shards" : {
      "total" : 1,
      "successful" : 1,
      "skipped" : 0,
      "failed" : 0
    },
    "hits" : {
      "total" : {
        "value" : 3,
        "relation" : "eq"
      },
      "max_score" : 1.0,
      "hits" : [
        {
          "_index" : "my_index",
          "_type" : "_doc",
          "_id" : "Hc4Vc5QBSxCnghau22AE",
          "_score" : 1.0,
          "_source" : {
            "id" : 1
          }
        },
        {
          "_index" : "my_index",
          "_type" : "_doc",
          "_id" : "Hs4Vc5QBSxCnghau22AE",
          "_score" : 0.33333334,
          "_source" : {
            "id" : 2
          }
        },
        {
          "_index" : "my_index",
          "_type" : "_doc",
          "_id" : "H84Vc5QBSxCnghau22AE",
          "_score" : 0.11111111,
          "_source" : {
            "id" : 3
          }
        }
      ]
    }
  }
  ```
  
- 前置过滤查询 先筛选出id取值为\["2", "3"\]的文档，再返回与查询向量\[1, 1\]最相似的Top10文档。
  ```
  GET my_index/_search
  {
    "query": {
      "nested": {
        "path": "embedding",                // 必须指定嵌套字段路径
        "score_mode": "max",                // 子文档分值合并到父文档的策略：取最匹配的段落得分
        "query": {
          "vector": {
            "embedding.emb": {
              "vector": [1, 1],
              "topk": 10,
              "filter": {
                "terms": {"id": ["2", "3"]}
              }
            }
          }
        }
      }
    }
  }
  ```
  查询结果示例如下：
  ```
  {
    "took" : 3,
    "timed_out" : false,
    "_shards" : {
      "total" : 1,
      "successful" : 1,
      "skipped" : 0,
      "failed" : 0
    },
    "hits" : {
      "total" : {
        "value" : 2,
        "relation" : "eq"
      },
      "max_score" : 0.33333334,
      "hits" : [
        {
          "_index" : "my_index",
          "_type" : "_doc",
          "_id" : "3t0ZypcB-Tff59gMTZO2",
          "_score" : 0.33333334,
          "_source" : {
            "id" : 2,
            "embedding" : [
              {
                "chunk" : 1,
                "emb" : [
                  2,
                  2
                ]
              },
              {
                "chunk" : 2,
                "emb" : [
                  3,
                  3
                ]
              }
            ]
          }
        },
        {
          "_index" : "my_index",
          "_type" : "_doc",
          "_id" : "390ZypcB-Tff59gMTZO2",
          "_score" : 0.11111111,
          "_source" : {
            "id" : 3,
            "embedding" : [
              {
                "chunk" : 1,
                "emb" : [
                  3,
                  3
                ]
              },
              {
                "chunk" : 2,
                "emb" : [
                  4,
                  4
                ]
              }
            ]
          }
        }
      ]
    }
  }
  ```
  
 
