You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Amazon OpenSearch Serverless中实现带空值检查的嵌套标签过滤

问题描述

我正在使用Amazon OpenSearch Serverless开展项目,需基于标签存储和搜索内容。过滤标签与内容标签均可为多个,部分文档可能不含tags字段。需求为:仅对比过滤标签与内容标签中group_id相同的条目,若group_id相同则id和src_code必须一致,以此筛选文档。

示例结构

内容文档的tags字段示例

"_source": {
          "tags": [
            {
              "group_id": 5,
              "id": 1,
              "src_code": "2324"
            },
            {
              "group_id": 6,
              "id": 2,
              "src_code": "91"
            },
            {
              "group_id": 7,
              "id": 3,
              "src_code": "3124"
            },
            {
              "group_id": 8,
              "id": 4,
              "src_code": "5543"
            }
          ]
        }

过滤标签示例

{
  "filter_tags": [
    {"id": 123, "group_id": 1, "src_code": "2"},
    {"id": 124, "group_id": 2, "src_code": "23"},
    {"id": 1, "group_id": 5, "src_code": "2324"},
    {"id": 4, "group_id": 6, "src_code": "2325"},
  ]
}

目标:返回所有符合条件的文档,即过滤标签中与内容标签group_id相同的条目,其id和src_code均需一致;过滤标签中group_id不存在于内容的条目无需对比。以上示例中,仅需对比第3、4个过滤标签,因第4个标签id和src_code不匹配,故该文档不应被返回。

已尝试方案及问题

  1. 未检查tags字段存在性的查询执行报错,错误提示无法访问doc['tags']。
  2. 添加tags字段存在性检查后,所有文档(含tags字段的)均返回0.8分,不符合预期。

解决方案

1. 正确的空值检查

在Painless脚本中,针对嵌套类型的tags字段,正确的存在性和非空性检查需注意:

  • 嵌套字段无法通过doc对象直接以数组形式访问,需通过ctx._source获取原始数据;
  • 需覆盖三种情况:文档无tags字段、tags字段为null、tags字段为空数组。

正确的检查代码:

// 检查tags字段是否存在且非空
if (!ctx._source.containsKey('tags') || ctx._source.tags == null || ctx._source.tags.isEmpty()) {
  // 无tags的文档,根据需求处理:允许通过则返回1.0,否则返回0.0
  return 1.0;
}

2. 正确的过滤逻辑实现

核心思路:先将内容标签按group_id构建映射,快速匹配过滤标签中存在对应group_id的条目,只要有一个匹配项的id或src_code不一致,直接排除文档;所有需检查的过滤标签都匹配时,返回有效分数。

完整查询脚本:

POST /bedrock-knowledge-base-default-index/_search
{
  "query": {
    "function_score": {
      "query": {
        "match_all": {}
      },
      "functions": [
        {
          "script_score": {
            "script": {
              "source": """
                // 处理无tags的文档,可根据需求调整返回值
                if (!ctx._source.containsKey('tags') || ctx._source.tags == null || ctx._source.tags.isEmpty()) {
                  return 1.0;
                }

                // 将内容标签按group_id构建映射,便于快速查找
                Map<Integer, Map<String, Object>> contentTagMap = new HashMap();
                for (def contentTag : ctx._source.tags) {
                  Integer groupId = contentTag.group_id;
                  contentTagMap.put(groupId, contentTag);
                }

                // 遍历过滤标签,仅检查内容中存在对应group_id的条目
                for (def filterTag : params.filter_tags) {
                  Integer filterGroupId = filterTag.group_id;
                  if (contentTagMap.containsKey(filterGroupId)) {
                    Map<String, Object> matchedContentTag = contentTagMap.get(filterGroupId);
                    // 对比id和src_code,不匹配则返回0排除文档
                    if (matchedContentTag.id != filterTag.id || !matchedContentTag.src_code.equals(filterTag.src_code)) {
                      return 0.0;
                    }
                  }
                }

                // 所有需检查的标签都匹配,返回1.0
                return 1.0;
              """,
              "params": {
                "filter_tags": [
                  {"id": 1, "group_id": 5, "src_code": "2324"},
                  {"id": 2, "group_id": 6, "src_code": "91"}
                ]
              }
            }
          }
        }
      ],
      "boost_mode": "replace",
      "min_score": 1.0 // 只返回分数≥1.0的符合条件的文档
    }
  },
  "_source": ["tags"]
}

关键说明

  • 不再使用nested查询,直接操作整个文档的tags数组;
  • 通过min_score过滤不符合条件的文档;
  • 无tags文档的处理逻辑可按需调整:若需排除,将返回值改为0.0即可。

3. 文档结构优化建议

若当前查询复杂度或性能不达标,可调整文档结构简化查询:

方案1:扁平化标签结构

将tags数组转换为以group_id为键、id_src_code为值的对象,示例:

"_source": {
  "tags_map": {
    "5": "1_2324",
    "6": "2_91",
    "7": "3_3124",
    "8": "4_5543"
  }
}

查询时直接用term匹配对应group_id的键值对,无需脚本遍历,性能更优。例如检查group_id=5的匹配情况,只需查询tags_map.5: "1_2324"。

方案2:嵌套字段+原生查询

若需保留数组结构,确保tags字段设为nested类型,并为group_id、id、src_code单独建索引,通过布尔嵌套查询组合实现过滤:

POST /bedrock-knowledge-base-default-index/_search
{
  "query": {
    "bool": {
      "must_not": [
        // 排除存在group_id匹配但id或src_code不匹配的文档
        {
          "nested": {
            "path": "tags",
            "query": {
              "bool": {
                "should": [
                  {
                    "bool": {
                      "must": [
                        {"term": {"tags.group_id": 5}},
                        {"bool": {"should": [{"term": {"tags.id": {"value": 1, "boost": 0}}}, {"term": {"tags.src_code": {"value": "2324", "boost": 0}}}]}}
                      ]
                    }
                  },
                  {
                    "bool": {
                      "must": [
                        {"term": {"tags.group_id": 6}},
                        {"bool": {"should": [{"term": {"tags.id": {"value": 2, "boost": 0}}}, {"term": {"tags.src_code": {"value": "91", "boost": 0}}}]}}
                      ]
                    }
                  }
                ]
              }
            }
          }
        }
      ],
      // 可选:包含无tags的文档
      "should": [
        {"bool": {"must_not": {"exists": {"field": "tags"}}}}
      ],
      "minimum_should_match": 0
    }
  }
}

该方式利用OpenSearch原生查询能力,避免脚本执行的性能开销,但需根据过滤标签动态生成查询条件。


内容的提问来源于stack exchange,提问作者Shanazar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 15:19:55