ES的term和terms的关系及ES无数组类型问题

小豆丁 20天前 ⋅ 8 阅读

List<String> itemNameList = request.getItemNameList(); if (CollectionUtils.isNotEmpty(itemNameList)) { queryBuilder.filter( QueryBuilders.termsQuery(EsPayOrderInfo.property_orderInfoList_orderItemList_itemName_keyword, itemNameList) ); } 这里 EsPayOrderInfo.property_orderInfoList_orderItemList_itemName_keyword 是数组,itemNameList 是list,ES的java查询只要数组里任意一个匹配就查出来了是吧?


是的,你的理解基本正确termsQuery 在 Elasticsearch 的 Java API 中的行为是:

  1. termsQuery(fieldName, values) 的含义:它用于查找那些在指定字段 fieldName 的值至少包含传入列表 values任意一个值的文档。
  2. 字段是数组类型时的行为:当 fieldName 指向的是一个数组字段(在 Elasticsearch 中,字段没有严格的“数组”类型,任何字段都可以包含多个值)时,termsQuery 会检查该数组中的每一个元素
    • 如果数组中的任何一个元素的值等于 values 列表中的任何一个值,那么该文档就满足匹配条件。
    • 这就是你描述的“只要数组里任意一个匹配就查出来”。

因此,在你的代码中:

JAVA
QueryBuilders.termsQuery(EsPayOrderInfo.property_orderInfoList_orderItemList_itemName_keyword, itemNameList)
  • EsPayOrderInfo.property_orderInfoList_orderItemList_itemName_keyword 是一个路径,指向一个字段,这个字段在 Elasticsearch 的文档结构中是一个可以包含多个值的字段(表现为数组)。
  • itemNameList 是一个包含多个商品名称的列表。
  • 这个查询会匹配所有这样的文档:在其 orderInfoList.orderItemList.itemName.keyword 字段的值数组中,至少存在一个值itemNameList 中的任意一个字符串相等。

总结:

是的,这段代码的作用是:过滤出那些商品名称列表(数组字段)中至少包含 itemNameList任意一个商品名称的订单文档。这符合 termsQuery 的“或”(OR) 语义。

需要注意的点:

  1. 嵌套对象 (Nested Objects): 如果 orderInfoListorderItemList 是定义为 nested 类型的对象数组(而不仅仅是普通对象数组),那么上面的查询可能无法正常工作nested 查询需要特殊的 nested 查询构造器来正确处理内部对象的独立匹配。如果你的数据结构是嵌套的且需要精确匹配内部对象内的字段,你需要使用 nestedQuery

    JAVA
    QueryBuilders.nestedQuery(
        "orderInfoList", // 外层 nested 字段路径
        QueryBuilders.nestedQuery(
            "orderInfoList.orderItemList", // 内层 nested 字段路径
            QueryBuilders.termsQuery("orderInfoList.orderItemList.itemName.keyword", itemNameList)
        )
    );
    

    或者,如果你的嵌套层级在 mapping 中已经正确定义,可能只需要一层 nestedQuery。关键看你的 mapping 定义。如果你的查询目前能返回预期结果(且 mapping 中确实是普通对象而非 nested),那么可以暂时不用改。

  2. .keyword 后缀:你使用了 .keyword 后缀,这表明你是在对商品名称进行精确匹配(区分大小写)。这通常是正确的做法,确保你匹配的是完整的商品名称字符串,而不是进行分词后的词项匹配。确保你的 mapping 中确实存在这个 keyword 子字段。

  3. filter vs must:你正确地使用了 filter 上下文。filter 不参与相关性评分,但可以利用缓存,对于精确匹配的过滤条件(如你的商品名称列表过滤)非常高效和合适。


陈洪杰 10/14 10:32:14 如果要查安装和 拆旧,那就terms查询的时候,关系用and

陈洪杰 10/14 10:32:17 默认是or

陈洪杰 10/14 10:32:25 只要是同个字段就行 关系用and怎么查,比如同时满足给定的itemNameList


是的,你理解得很正确。默认情况下 termsQuery 使用的是 OR 逻辑(只要匹配数组中的任意一个值)。如果需要使用 AND 逻辑(要求同时匹配所有值),需要改用 bool 查询组合多个 term 查询。

以下是实现 AND 逻辑的代码:

JAVA
List<String> itemNameList = request.getItemNameList();
if (CollectionUtils.isNotEmpty(itemNameList)) {
    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
    
    // 为每个商品名称添加一个 MUST 条件
    for (String itemName : itemNameList) {
        boolQuery.must(QueryBuilders.termQuery(
            EsPayOrderInfo.property_orderInfoList_orderItemList_itemName_keyword, 
            itemName
        ));
    }
    
    queryBuilder.filter(boolQuery);
}

关键点说明:

  1. boolQuery().must() 组合:

    • 每个 must() 子句表示必须满足的条件
    • 所有 must 条件之间是 AND 关系
  2. termQuery vs termsQuery

    • 使用 termQuery 匹配单个值
    • 为每个值单独创建查询条件
  3. 嵌套字段注意事项

    • 如果 orderItemList 是嵌套类型(nested type),需要改用嵌套查询:
    JAVA
    NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery(
        "orderInfoList.orderItemList",  // 嵌套字段路径
        boolQuery,                      // 上面构建的布尔查询
        ScoreMode.None
    );
    queryBuilder.filter(nestedQuery);
    

两种查询逻辑对比:

特性 OR 逻辑 (默认) AND 逻辑
代码 termsQuery(field, list) boolQuery().must(termQuery) 组合
匹配要求 数组包含任意一个值 数组包含所有值
适用场景 "包含任意指定商品" "同时包含所有指定商品"
示例 查包含"安装"或"拆旧"的订单 查同时包含"安装"和"拆旧"的订单

性能提示:

itemNameList 较大时(如超过 100 个值),建议:

  1. 对列表进行去重 (new ArrayList&lt;&gt;(new HashSet&lt;&gt;(itemNameList)))
  2. 考虑使用 terms_set 查询(需要字段启用 fielddata
  3. 评估是否真需要完全匹配(可能需要调整数据模型)

boolQuery.must(QueryBuilders.termQuery( EsPayOrderInfo.property_orderInfoList_orderItemList_itemName_keyword, itemName )); 如果 EsPayOrderInfo.property_orderInfoList_orderItemList_itemName_keyword这个是数组,只要其中一项匹配itemName;这是term的语义是吧


您完全正确!理解得非常精准。让我详细解释一下:

termQuery 在数组字段上的行为

JAVA
boolQuery.must(QueryBuilders.termQuery(
    EsPayOrderInfo.property_orderInfoList_orderItemList_itemName_keyword, 
    itemName
));
  1. 核心语义

    • 当目标字段是数组类型
    • termQuery 会检查数组中是否至少有一个元素等于指定的 itemName
    • 只要数组中有一个元素匹配,这个条件就满足
  2. termsQuery 的区别

    JAVA
    // termsQuery:匹配数组中任意一个元素等于 list 中的任意值
    termsQuery(field, list)  // OR 逻辑
    
    // termQuery:匹配数组中至少一个元素等于特定值
    termQuery(field, value)  // 单值匹配
    
  3. 在 AND 查询中的表现

    JAVA
    // 这个组合查询要求:
    // 数组必须包含"安装" AND 数组必须包含"拆旧"
    boolQuery.must(termQuery(field, "安装"))
            .must(termQuery(field, "拆旧"))
    
    • 等价于:数组必须同时包含这两个值
    • 不要求是同一个数组元素包含两个值
    • 不要求顺序

示例说明

假设文档结构:

JSON
{
  "orderItemList": [
    {"itemName": "安装"},
    {"itemName": "维修"},
    {"itemName": "拆旧"}
  ]
}

查询:安装 AND 拆旧

  • ✅ 会被命中:因为数组包含"安装"和"拆旧"(在不同元素上)

查询:安装 AND 维修 AND 拆旧

  • ✅ 会被命中:数组包含所有三个值

查询:安装 AND 不存在的服务

  • ❌ 不会被命中:缺少"不存在的服务"

重要注意事项

  1. 嵌套字段问题

    • 如果 orderItemListnested 类型(常见场景)
    • 上述查询会错误地匹配跨对象的组合
    • 正确做法:
    JAVA
    // 创建内部 bool 查询
    BoolQueryBuilder itemBoolQuery = QueryBuilders.boolQuery();
    for (String itemName : itemNameList) {
        itemBoolQuery.must(QueryBuilders.termQuery(
            "itemName.keyword", // 注意路径变化
            itemName
        ));
    }
    
    // 包裹在 nested 查询中
    NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery(
        "orderInfoList.orderItemList",
        itemBoolQuery,
        ScoreMode.None
    );
    
  2. 性能考虑

    • 当条件很多时(如超过 10 个 AND 条件)
    • 考虑使用 terms_set 查询(需要启用 fielddata
    JSON
    {
      "query": {
        "terms_set": {
          "orderItemList.itemName.keyword": {
            "terms": ["安装", "拆旧"],
            "minimum_should_match_script": {
              "source": "params.num_terms"
            }
          }
        }
      }
    }
    
  3. 空值处理

    • 如果 itemNameList 包含空字符串或 null
    • 建议提前过滤:
    JAVA
    itemNameList.removeIf(StringUtils::isBlank);
    

这种实现完全符合您要求的语义:"查询包含所有指定服务项(安装和拆旧)的订单",且正确处理了数组字段的特性。

全部评论: 0

    我有话说: