Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >干货 | Elasticsearch Nested 数组大小求解,一网打尽!

干货 | Elasticsearch Nested 数组大小求解,一网打尽!

作者头像
铭毅天下
发布于 2022-04-06 09:48:11
发布于 2022-04-06 09:48:11
1.7K30
代码可运行
举报
文章被收录于专栏:铭毅天下铭毅天下
运行总次数:0
代码可运行

1、实战线上 Nested 问题

如何查询所有 objectList (Nested 类型)里面的 lossStatus="ENABLE" 且 objectList 的数组大小大于2的数据? ——问题来源:死磕Elasticsearch 知识星球

2、数据模型

索引导入和样例数据批量写入如下所示。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
PUT appweb
{
  "mappings": {
    "properties": {
      "name": {
        "type": "text"
      },
      "orderTime": {
        "type": "date",
        "format": "yyyy-MM-dd HH:mm:ss"
      },
      "objectList": {
        "type": "nested",
        "properties": {
          "addTime": {
            "type": "date",
            "format": "yyyy-MM-dd HH:mm:ss"
          },
          "customerPersonId": {
            "type": "long"
          },
          "lossStatus": {
            "type": "text"
          }
        }
      }
    }
  }
}


POST appweb/_bulk
{"index":{"_id":1}}
{"name":"111","orderTime":"2022-02-02 02:02:02","objectList":[{"addTime":"2022-02-02 02:02:02","customerPersonId":101,"lossStatus":"ENABLE"},{"addTime":"2022-02-02 02:02:02","customerPersonId":102,"lossStatus":"ENABLE"}]}
{"index":{"_id":2}}
{"name":"222","orderTime":"2022-02-02 02:02:02","objectList":[{"addTime":"2022-02-02 02:02:02","customerPersonId":201,"lossStatus":"2222"},{"addTime":"2022-02-02 02:02:02","customerPersonId":202,"lossStatus":"2222"},{"addTime":"2022-02-02 02:02:02","customerPersonId":203,"lossStatus":"3333"}]}
{"index":{"_id":3}}
{"name":"111","orderTime":"2022-02-02 02:02:02","objectList":[{"addTime":"2022-02-02 02:02:02","customerPersonId":101,"lossStatus":"ENABLE"}]}
{"index":{"_id":4}}
{"name":"111","orderTime":"2022-02-02 02:02:02","objectList":[{"addTime":"2022-02-02 02:02:02","customerPersonId":101,"lossStatus":"ENABLE"},{"addTime":"2022-02-02 02:02:02","customerPersonId":102,"lossStatus":"ENABLE"},{"addTime":"2022-02-02 02:02:02","customerPersonId":103,"lossStatus":"ENABLE"}]}

开搞,方案逐步展开讨论。

3、问题拆解

涉及三个核心知识点:

  • 其一:检索数据涉及 Nested 类型。
  • 其二:检索条件1:objectList (Nested 类型)下的 lossStatus="ENABLE"。

这个在检索的时候要注意指定 path,否则会报错。

  • 其三:检索条件2:获取 objectList 的数组大小大于 2 的数据?

问题转化为:检索条件1、检索条件2的组合实现。

3.1 检索条件 1 实现

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
POST appweb/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "nested": {
            "path": "objectList",
            "query": {
              "match_phrase": {
                "objectList.lossStatus": "ENABLE"
              }
            }
          }
        }
      ]
    }
  }
}

中规中矩的 Nested 语法,无需过多解释。唯一强调的是:path的用法。

如果 Nested 语法不熟悉,可以参考官方文档:

https://www.elastic.co/guide/en/elasticsearch/reference/8.0/query-dsl-nested-query.html

3.2 检索条件 2 实现

本质是获取 objectList 的数组大小大于 2 的数据。再进一步缩小范围是:获取 objectList 数组的大小。

问题转化为如何获取 Nested 嵌套类型数组大小?

这里的确没有非常现成的实现,我总结了如下几种方案。

方案1:function_score 检索实现

该方案包含了:3.1 小节 检索条件 1 的实现,完整实现如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
POST appweb/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "nested": {
            "path": "objectList",
            "query": {
              "match_phrase": {
                "objectList.lossStatus": "ENABLE"
              }
            }
          }
        },
        {
          "function_score": {
            "query": {
              "match_all": {}
            },
            "functions": [
              {
                "script_score": {
                  "script": {
                    "source": "params._source.containsKey('objectList') && params._source['objectList'] != null && params._source.objectList.size() > 2 ? 2 : 0"
                  }
                }
              }
            ],
            "min_score": 1
          }
        }
      ]
    }
  }
}

注意在 script_score 下做了多条件判断:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
params._source.containsKey('objectList') 
params._source['objectList'] != null
params._source.objectList.size() > 2

官方语法参考:

https://www.elastic.co/guide/en/elasticsearch/reference/8.0/query-dsl-function-score-query.html

https://www.elastic.co/guide/en/elasticsearch/painless/8.0/painless-score-context.html

方案2:funciton_score 检索实现2
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
POST appweb/_search
{
  "query": {
    "function_score": {
      "query": {
        "bool": {
          "must": [
            {
              "nested": {
                "path": "objectList",
                "query": {
                  "exists": {
                    "field": "objectList.customerPersonId"
                  }
                },
                "score_mode": "sum"
              }
            },
            {
              "nested": {
                "path": "objectList",
                "query": {
                  "match_phrase": {
                    "objectList.lossStatus": "ENABLE"
                  }
                }
              }
            }
          ]
        }
      },
      "functions": [
        {
          "script_score": {
            "script": {
              "source": "_score >= 3 ? 1 : 0"
            }
          }
        }
      ],
      "boost_mode": "replace"
    }
  },
  "min_score": 1
}

该方式本质是曲线救国,借助:sum 求和累加评分实现。

实现条件是:存在字段“objectList.customerPersonId”,评分就高。该方式不太容易想到,“可遇而不可求”

方案3:runtime_field 运行时字段实现
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
POST appweb/_search
{
  "runtime_mappings": {
    "objectList_tmp": {
      "type": "keyword",
      "script": """
        int genre = params['_source']['objectList'].size();
        emit(genre.toString());
      """
    }
  },
  "query": {
    "bool": {
      "must": [
        {
          "nested": {
            "path": "objectList",
            "query": {
              "match_phrase": {
                "objectList.lossStatus": "ENABLE"
              }
            }
          }
        },
        {
          "range": {
            "objectList_tmp": {
              "gte": 3
            }
          }
        }
      ]
    }
  }
}

这是我整合了聚合 + runtime_field 实现的结果,召回结果达到预期且令人满意。

最后发现聚合部分是多余的,删除之。

解读如下:

  • 第一:新加了运行时字段——objectList_tmp,目的:获取 Nested 数组大小。
  • 第二:结合已有 nested 检索组合 bool 检索实现即可。

综合对比看,它比下面的方案4更简洁,如果线上环境想不修改数据的前提下使用,推荐此方案。

方案4:聚合实现
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
GET appweb/_search
{
  "size": 0,
  "query": {
    "nested": {
      "path": "objectList",
      "query": {
        "match_phrase": {
          "objectList.lossStatus": "ENABLE"
        }
      }
    }
  },
  "aggs": {
    "counts_aggs": {
      "terms": {
        "script": "params['_source']['objectList'].size()"
      },
      "aggs": {
        "top_hits_aggs": {
          "top_hits": {
            "size": 10
          }
        }
      }
    }
  }
}

对比方案 3,方案 4相对鸡肋和繁冗、复杂。

也更进一步体会:runtime_field 的妙处。

4、换个思路?轻装上阵!

什么思路?之前文章有过解读——空间换时间

具体实现如下:

4.1 步骤1:预处理新增字段 nested_size。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
PUT  _ingest/pipeline/add_nested_size_pipeline
{
  "processors": [
    {
      "script": {
        "lang": "painless",
        "source": "ctx.nested_size = ctx.objectList.size();"
      }
    }
  ]
}

4.2 步骤2:创建索引且导入数据。

创建索引同时指定步骤 1 的 pipeline 预处理管道。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
PUT appweb_ext
{
  "settings": {
    "index": {
      "default_pipeline": "add_nested_size_pipeline"
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text"
      },
      "orderTime": {
        "type": "date",
        "format": "yyyy-MM-dd HH:mm:ss"
      },
      "objectList": {
        "type": "nested",
        "properties": {
          "addTime": {
            "type": "date",
            "format": "yyyy-MM-dd HH:mm:ss"
          },
          "customerPersonId": {
            "type": "long"
          },
          "lossStatus": {
            "type": "text"
          }
        }
      }
    }
  }
}



POST appweb_ext/_bulk
{"index":{"_id":1}}
{"name":"111","orderTime":"2022-02-02 02:02:02","objectList":[{"addTime":"2022-02-02 02:02:02","customerPersonId":101,"lossStatus":"ENABLE"},{"addTime":"2022-02-02 02:02:02","customerPersonId":102,"lossStatus":"ENABLE"}]}
{"index":{"_id":2}}
{"name":"222","orderTime":"2022-02-02 02:02:02","objectList":[{"addTime":"2022-02-02 02:02:02","customerPersonId":201,"lossStatus":"2222"},{"addTime":"2022-02-02 02:02:02","customerPersonId":202,"lossStatus":"2222"},{"addTime":"2022-02-02 02:02:02","customerPersonId":203,"lossStatus":"3333"}]}
{"index":{"_id":3}}
{"name":"111","orderTime":"2022-02-02 02:02:02","objectList":[{"addTime":"2022-02-02 02:02:02","customerPersonId":101,"lossStatus":"ENABLE"}]}
{"index":{"_id":4}}
{"name":"111","orderTime":"2022-02-02 02:02:02","objectList":[{"addTime":"2022-02-02 02:02:02","customerPersonId":101,"lossStatus":"ENABLE"},{"addTime":"2022-02-02 02:02:02","customerPersonId":102,"lossStatus":"ENABLE"},{"addTime":"2022-02-02 02:02:02","customerPersonId":103,"lossStatus":"ENABLE"}]}

4.3 步骤3:复杂脚本检索变成简单检索实现。

bool 组合条件,一个 nested 检索 + 一个 range query,轻松搞定!

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
POST appweb_ext/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "nested": {
            "path": "objectList",
            "query": {
              "match_phrase": {
                "objectList.lossStatus": "ENABLE"
              }
            }
          }
        },
        {
          "range": {
            "nested_size": {
              "gt": 2
            }
          }
        }
      ]
    }
  }
}

此方案是我极力推广的方案,需要我们多结合业务实际,多在数据写入前的设计阶段、数据建模阶段做“文章”。而不是快速导入数据,后面丢给复杂的检索脚本实现。

一般项目实战阶段,很多人会说,“工期要紧,我管不了那么多”。项目后期复盘会发现,“看似快了,实则慢了”,最终感叹:“预处理的工作不要省也不能省”!

5、小结

看似简单的几个方案,我从入手到梳理完毕耗时大于 6 个小时+。主要是painless 脚本没有固定的章法可循,需要摸索和反复验证。

意外收获是方案3,基于方案 4 的创新方案,比较灵活好用。

但,我更推荐空间换时间的方案。能预处理搞定的事情,就不要留到检索阶段实现。

欢迎留言说下您的方案和思考!

6、参考

https://stackoverflow.com/questions/64447956

https://stackoverflow.com/questions/54022283

https://stackoverflow.com/questions/57144172

https://t.zsxq.com/FAQ7mUN

https://www.ru-rocker.com/2020/11/03/filtering-nested-array-objects-in-elasticsearch-document-with-painless-scripting/

https://medium.com/@felipegirotti/elasticsearch-filter-field-array-more-than-zero-8d52d067d3a0

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-03-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 铭毅天下Elasticsearch 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
3 条评论
热度
最新
目前想到的办法是script里面循环统计
目前想到的办法是script里面循环统计
回复回复点赞举报
如何算出过滤筛选条件后的Nested数组大小?比如:objectList数组大小为4, lossStatus="ENABLE"的数据只有2。你现在的解法过滤 lossStatus="ENABLE"后,objectList算出来都为4,如何得到2?大神有解决方法没有?
如何算出过滤筛选条件后的Nested数组大小?比如:objectList数组大小为4, lossStatus="ENABLE"的数据只有2。你现在的解法过滤 lossStatus="ENABLE"后,objectList算出来都为4,如何得到2?大神有解决方法没有?
11点赞举报
我觉得还是可以用作者最后推荐的方式:预处理。建立一个nested字段,里面定一个lossStatus和其分组的值,然后走nested查询就可以了。当然这种比较适合在应用侧同步数据,非要写pipeline processor感觉比较麻烦
我觉得还是可以用作者最后推荐的方式:预处理。建立一个nested字段,里面定一个lossStatus和其分组的值,然后走nested查询就可以了。当然这种比较适合在应用侧同步数据,非要写pipeline processor感觉比较麻烦
回复回复点赞举报
推荐阅读
编辑精选文章
换一批
干货 | 拆解一个 Elasticsearch Nested 类型复杂查询问题
前置说明:本文是线上环境的实战问题拆解,涉及复杂 DSL,看着会很长,但强烈建议您耐心读完。
铭毅天下
2021/07/22
3.2K0
Elasticsearch学习笔记 -- 1
ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。
earthchen
2020/09/24
4430
干货 | Elasticsearch7.X Scripting脚本使用详解
除了官方文档,其他能找到的介绍Elasticsearch脚本(Scripting)的资料少之又少。
铭毅天下
2019/09/17
15.6K1
elasticsearch压力测试工具之ESrally使用说明
esrally是elastic search官方用于对ES集群进行压力测试的工具,使用esrally可以为我们构建不同版本集群,构造不同的参数和数据来进行压力测试,并且可以对产生的压测结果进行比较,rally顾名思义是拉力赛的意思,esrally的一些名词也都与拉力赛有关。 github地址:https://github.com/elastic/rally
没有故事的陈师傅
2020/08/31
2.3K0
Elasticsearch 8.X:这个复杂的检索需求如何实现?
如上图所示,index中有这样四个字段:title content question answer。要查询这四个字段,支持最多输入5个关键词模糊查询,多关键词以空格隔开。
铭毅天下
2023/09/26
6170
Elasticsearch 8.X:这个复杂的检索需求如何实现?
Elasticsearch - 聚合获取原始数据并分页&排序&模糊查询
在 Elasticsearch 中,cardinality 算法用来计算字段的基数(不重复的值的个数).
小小工匠
2023/05/12
1.5K0
Elasticsearch - 聚合获取原始数据并分页&排序&模糊查询
用 Elasticsearch 统计做了几次核酸检测?怎么破?
这两个问题本质是一类问题,这类问题涉及技术选型、方案选型、实现细节等问题,本篇文章我们一并讨论一下。
铭毅天下
2022/04/06
8140
用 Elasticsearch 统计做了几次核酸检测?怎么破?
干货 | Elasticsearch Nested类型深入详解
本文通过一个例子将Nested类型适合解决的问题、应用场景、使用方法串起来, 文中所有的DSL都在Elasticsearch6.X+验证通过。
铭毅天下
2018/10/24
4.6K0
干货 | Elasticsearch Nested类型深入详解
ElasticSearch基础入门学习笔记
本笔记的内容主要是在从0开始学习ElasticSearch中,按照官方文档以及自己的一些测试的过程。
Ryan-Miao
2020/02/24
5900
【Elasticsearch】Nested嵌套结构数据操作及聚合查询
ES的Nested数据类型允许我们存储一对多的数据,例如一个文章可以对应多个评论等,在正式开始之前,我们先生成一个用于测试的索引:
明月AI
2022/02/23
6.9K0
【Elasticsearch】Nested嵌套结构数据操作及聚合查询
PB数据毫秒级搜索之Elasticsearch(二)基础了解
ES在创建索引时,默认是创建5个分片,一个备份,这个数量是可以修改的,分片是只能创建时修改,备份可以动态修改。在索引中,还存在几个概念:
憧憬博客
2020/07/20
8220
Elasticsearch 8.X 如何依据 Nested 嵌套类型的某个字段进行排序?
如下所示, 希望在查出的结果后, 对结果进行后处理,对tags列表,根据depth进行排序。
铭毅天下
2024/01/11
9430
Elasticsearch 8.X 如何依据 Nested 嵌套类型的某个字段进行排序?
Elasticsearch初检索及高级
PUT customer/external/1 :在 customer 索引下的 external 类型下保存 1号数据
乐心湖
2021/01/18
1.2K0
Elasticsearch初检索及高级
【elasticsearch】进阶检索
HTTP客户端工具(POSTMAN),get请求不能携带请求体,我们变为post也是一样的 我们 POST 一个 JSON 风格的查询请求体到 _search API。 需要了解,一旦搜索的结果被返回,Elasticsearch 就完成了这次请求,并且不会维护任何服务端的资源或者结果的 cursor(游标)
周杰伦本人
2022/10/25
5760
【elasticsearch】进阶检索
Elasticsearch使用:Scripting API(一)
官方7.9版本:https://www.elastic.co/guide/en/elasticsearch/reference/7.9/modules-scripting.html
HLee
2020/12/09
3.1K0
Elasticsearch使用:Scripting API(一)
ES查询常见问题
1 must嵌套should条件查询 curl -XGET 'xxx/xxx/_search?pretty' -H 'Content-Type: application/json' -d'{
YG
2018/05/23
8050
干货 |《从Lucene到Elasticsearch全文检索实战》拆解实践
1、题记 2018年3月初,萌生了一个想法:对Elasticsearch相关的技术书籍做拆解阅读,该想法源自非计算机领域红火已久的【樊登读书会】、得到的每天听本书、XX拆书帮等。 目前市面上Elasticsearch的中文书籍就那么基本,针对ES5.X以上的三本左右;国外翻译有几本,都是针对ES1.X,2.X版本,其中《深入理解Elasticsearch》还算比较经典。 拆书的目的: 1)梳理已有的Elasticsearch知识体系; 2)拾遗拉在角落的Elasticsearch知识点; 3)通过手敲动代码
铭毅天下
2018/04/24
3.5K0
ES 查询优化(一)
1、能用term就不用match_phrase The Lucene nightly benchmarks show that a simple term query is about 10 times as fast as a phrase query, and about 20 times as fast as a proximity query (a phrase query with slop). term查询比match_phrase性能要快10倍,比带slop的match_phrase快2
YG
2018/05/23
5K1
ElasticSearch 权威指南笔记
使用 DSL(Domain Specific Language)特定领域语言**)**查询
operator开发工程师
2023/11/16
3530
ElasticSearch 权威指南笔记
Elasticsearch学习-嵌套文档
最近一段时间都在搞Elasticsearch搜索相关的工作,总结一下搜索知识点供大家参考。
dalaoyang
2020/05/17
1.2K0
相关推荐
干货 | 拆解一个 Elasticsearch Nested 类型复杂查询问题
更多 >
交个朋友
加入[数据] 腾讯云技术交流站
获取数据实战干货 共享技术经验心得
加入数据技术趋势交流群
大数据技术前瞻 数据驱动业务实践
加入[数据库] 腾讯云官方技术交流站
数据库问题秒解答 分享实践经验
换一批
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档