Elasticsearch:从实例中学习 nested 数据类型的 CRUD 及搜索

news2024/10/6 6:48:38

nested 数据类型是一个比较高级的话题。在本文中,将介绍 Elasticsearch 中针对嵌套对象的一些高级 CRUD 和搜索查询。 如果你想了解有关 Elasticsearch 基础知识的更多信息,可以查看这些文章以快速入门或复习:

  • Elasticsearch:关于在 Python 中使用 Elasticsearch 你需要知道的一切 - 8.x

  • Elasticsearch:通过例子快速入门

在进行下面的练习之前,建议阅读上面的两篇文章中的任何一篇以建立好 laptops-demo 这个索引。在完成之后,我们可以通过如下的命令来查看 laptops-demo 的 mappings 及 settings:

GET laptops-demo

上面的命令返回结果:

{
  "laptops-demo": {
    "aliases": {},
    "mappings": {
      "properties": {
        "attributes": {
          "type": "nested",
          "properties": {
            "attribute_name": {
              "type": "text"
            },
            "attribute_value": {
              "type": "text"
            }
          }
        },
        "brand": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword"
            }
          }
        },
        "id": {
          "type": "long"
        },
        "name": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword"
            },
            "ngrams": {
              "type": "text",
              "analyzer": "ngram_analyzer"
            }
          },
          "analyzer": "standard"
        },
        "price": {
          "type": "float"
        }
      }
    },
    "settings": {
      "index": {
        "routing": {
          "allocation": {
            "include": {
              "_tier_preference": "data_content"
            }
          }
        },
        "number_of_shards": "1",
        "provided_name": "laptops-demo",
        "creation_date": "1673943360770",
        "analysis": {
          "filter": {
            "ngram_filter": {
              "type": "edge_ngram",
              "min_gram": "2",
              "max_gram": "15"
            }
          },
          "analyzer": {
            "ngram_analyzer": {
              "filter": [
                "lowercase",
                "ngram_filter"
              ],
              "type": "custom",
              "tokenizer": "standard"
            }
          }
        },
        "number_of_replicas": "1",
        "uuid": "kWCIC0tUTJKrL3gUE1fGcA",
        "version": {
          "created": "8060099"
        }
      }
    }
  }
}

从返回的 mappings 的结果中,我们可以看出来:

      "properties": {
        "attributes": {
          "type": "nested",
          "properties": {
            "attribute_name": {
              "type": "text"
            },
            "attribute_value": {
              "type": "text"
            }
          }
        }

在此示例中,attributes 字段是一个 nested 字段,包含 attribute_name 和 attribure_value 字段作为子字段。 nested 类型是 object 数据类型的特殊版本,它允许对象数组以一种可以彼此独立查询的方式进行索引。 我们应该始终为 nested 字段创建一个映射,如本示例所示,因为 Elasticsearch 没有内部对象的概念,并且会将对象层次结构扁平化为字段名称和值的简单列表,这通常不是我们想要的。更多关于 nested 数据类型的介绍,可以参考文章 “Elasticsearch: object 及 nested 数据类型”。

要创建包含 nested 对象的文档,请运行以下命令:

PUT laptops-demo/_doc/1000
{
  "id": 1,
  "name": "HP EliteBook Model 1",
  "price": 38842,
  "brand": "HP",
  "attributes": [
    {
      "attribute_name": "cpu",
      "attribute_value": "Intel Core i7"
    },
    {
      "attribute_name": "memory",
      "attribute_value": "8GB"
    },
    {
      "attribute_name": "storage",
      "attribute_value": "256GB"
    }
  ]
}

如上所示,我们针对 attributes 字段写入我们想要的数组。

查看刚刚创建的文档:

GET laptops-demo/_doc/1000
{
  "_index": "laptops-demo",
  "_id": "1000",
  "_version": 1,
  "_seq_no": 201,
  "_primary_term": 2,
  "found": true,
  "_source": {
    "id": 1,
    "name": "HP EliteBook Model 1",
    "price": 38842,
    "brand": "HP",
    "attributes": [
      {
        "attribute_name": "cpu",
        "attribute_value": "Intel Core i7"
      },
      {
        "attribute_name": "memory",
        "attribute_value": "8GB"
      },
      {
        "attribute_name": "storage",
        "attribute_value": "256GB"
      }
    ]
  }
}

有些开发者可能要问为啥需要 nested 这个数据类型呢?假如我们有另外一个文档:

PUT laptops-demo/_doc/1001
{
  "id": 1,
  "name": "HP EliteBook Model 2",
  "price": 40000,
  "brand": "Apple",
  "attributes": [
    {
      "attribute_name": "cpu",
      "attribute_value": "Intel Core i7"
    },
    {
      "attribute_name": "memory",
      "attribute_value": "256GB"
    },
    {
      "attribute_name": "storage",
      "attribute_value": "8GB"
    }
  ]
}

在这里,我们把 id = 1001 里的 memory 和 storage 里的内存值和之前的文档 id = 1000 的对调了一下。

如果在没有定义 nested 数据类型的情况下,写入上面的两个文档,并做如下的查询:

GET laptops-demo/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "attributes.attribute_name": "memory"
          }
        },
        {
          "match": {
            "attributes.attribute_value": "8GB"
          }
        }
      ]
    }
  }
}

那么返回的结果将是两个文档被同时搜索到。显然这个不是我们所需要的结果,因为我们需要的结果是 memory 的内存值为 8GB,而不是 256GB 的电脑。这个是因为当 JSON 对象被 Lucene 扁平化后,我们失去了 attribute_name 和 attribute_value 之间的对应关系。取而代之的是如下的这种关系:

{
  ...
  "attributes.attribute_name :["memory", "storage"],
  "attributes.attribute_value": ["8GB", "256GB"]
}

很显然,对上面的两个文档来说,他们的搜索结果都是一样的。只有我们使用 nested 数据类型,我们可以让它们的关系变得一一对应起来。

假设我们要在attributes 字段中添加一个新的属性,应该怎么做呢? 事实证明,nested 字段属性实际上是一个数组,我们可以像这样向其中添加更多对象:

POST laptops-demo/_update/1000
{
  "script": {
    "source": "ctx._source.attributes.add(params.attribute)",
    "params": {
      "attribute": {
        "attribute_name": "screen_size",
        "attribute_value": "13 inch"
      }
    }
  }
}

正如演示的那样,nested 字段可以通过 ctx._source.attributes 访问,它作为数组返回。 我们可以通过 add 方法向这个数组中添加一个新对象。

查看文档,会发现在文档的attributes字段中增加了一个 id 为 1000 的新属性。

GET laptops-demo/_doc/1000
{
  "_index": "laptops-demo",
  "_id": "1000",
  "_version": 3,
  "_seq_no": 205,
  "_primary_term": 2,
  "found": true,
  "_source": {
    "id": 1,
    "name": "HP EliteBook Model 1",
    "price": 38842,
    "brand": "HP",
    "attributes": [
      {
        "attribute_name": "cpu",
        "attribute_value": "Intel Core i7"
      },
      {
        "attribute_name": "memory",
        "attribute_value": "8GB"
      },
      {
        "attribute_name": "storage",
        "attribute_value": "256GB"
      },
      {
        "attribute_name": "screen_size",
        "attribute_value": "13 inch"
      }
    ]
  }
}

如果我们想更新多个文档的 nested 字段,我们可以使用 _update_by_query 端点:

POST laptops-demo/_update_by_query
{
  "script": {
    "source": "ctx._source.attributes.add(params.attribute)",
    "params": {
      "attribute": {
        "attribute_name": "screen_size",
        "attribute_value": "13 inch"
      }
    }
  },
  "query": {
    "term": {
      "brand.keyword": {
        "value": "Apple"
      }
    }
  }
}

要找出所有刚刚更新的 Apple MacBooks:

GET laptops-demo/_search
{
  "query": {
    "term": {
      "brand.keyword": {
        "value": "Apple"
      }
    }
  }
}

如果想从 nested 字段中删除对象怎么办? 例如,如果我们要删除文档 1 的 screen_size 属性怎么办? 在这种情况下,我们需要一些编程逻辑。 我们需要先找到要移除的具体对象,然后将其从数组中移除。

POST laptops-demo/_update/1
{
  "script": {
    "source": "ctx._source.attributes.removeIf(attr -> attr.attribute_name == params.attribute_name)",
    "params": {
      "attribute_name": "screen_size"
    }
  }
}

removeIf 方法删除匿名函数找到的对象。

另一个用例是如何更新 nested 对象中的特定字段。 例如,当 attribute_name 为 “memory” 时,如果我们只想更新 attribute_value 怎么办? 在这种情况下,我们需要使用 painless 脚本语言的更多编程逻辑:

POST laptops-demo/_update/1
{
  "script": {
    "source": """
      def attributes = ctx._source.attributes.findAll(
        attr -> attr.attribute_name == params.attribute_name
      );
      for (attr in attributes) {
        attr.attribute_value = params.new_attribute_value
      }
    """,
    "params": {
      "attribute_name": "memory",
      "new_attribute_value": "16GB"
    }
  }
}

逻辑是先用 findAll 方法找到所有相关的 nested 对象,然后用 for 循环一个一个更新。

同样,如果你想更新满足某些条件的多个文档,你可以使用 _update_by_query 端点,如上所示。

最后,我想介绍一下如何查询 nested 字段。 当你第一次看到查询时,它可能会非常吓人。 但是,如果你掌握了模式,你就不会被它吓到,可以在工作中自由使用。

要查询 nested 字段,我们需要在 nested 查询中使用布尔查询。 让我们首先尝试找到所有 memory 为 8GB 的笔记本电脑。

GET laptops-demo/_search
{
  "query": {
    "nested": {
      "path": "attributes",
      "query": {
        "bool": {
          "must": [
            { "match": { "attributes.attribute_name": "memory" }},
            { "match": { "attributes.attribute_value":  "16GB" }} 
          ]
        }
      }
    }
  }
}

关键点

  • nested 关键字指定我们正在查询 nested 字段。
  • path 指定 nested 字段的名称,在本例中为 attributes。
  • bool 表示我们正在使用布尔查询,因为我们希望 attribute_name 和 attribute_value 字段都满足某些条件。
  • must 表示子查询必须全部出现在文档中。
  • match 表示全文搜索,因为 attribute_name 和 attribute_value 字段都是 text 字段。 如果其中一些不是 text 字段,我们需要使用 term、terms 或 range 查询。

现在让我们介绍一个更复杂的。 让我们找出所有 HP 笔记本电脑,其 memory 为 8GB,storage 为 256GB:

GET laptops-demo/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "brand.keyword": {
              "value": "HP"
            }
          }
        },
        {
          "nested": {
            "path": "attributes",
            "query": {
              "bool": {
                "must": [
                  {
                    "match": {
                      "attributes.attribute_name": "memory"
                    }
                  },
                  {
                    "match": {
                      "attributes.attribute_value": "8GB"
                    }
                  }
                ]
              }
            }
          }
        },
        {
          "nested": {
            "path": "attributes",
            "query": {
              "bool": {
                "must": [
                  {
                    "match": {
                      "attributes.attribute_name": "storage"
                    }
                  },
                  {
                    "match": {
                      "attributes.attribute_value": "256GB"
                    }
                  }
                ]
              }
            }
          }
        }
      ]
    }
  }
}

在这个大查询中,外部 bool 查询指定了三个应该满足的条件:

  • 第一个条件是匹配 brand 字段,必须是 HP。
  • 第二个条件是 nested 查询,它包装另一个查询以搜索 nested 属性字段。 在内部 bool 查询中,我们要求 attribute_name 必须是 “memory”,attribute_value必须是 “8G”。 两者都应该满足。
  • 同样,第三个条件也是 nested 查询,要求 attribute_name 必须为 “storage”,attribute_value 必须为 “256GB”。

如果你知道模式,就不会那么复杂,不是吗? 😃通过这个查询,我们可以得到所有brand 为 HP、内存为 8GB、存储空间为 256GB 的笔记本电脑。

总结

在本文中,我们简要介绍了常见的查询以创建、读取、更新、删除和搜索嵌套查询。 对于 CRUD 操作,你需要编写一个简单的 painless 脚本。 你无需掌握整个painless 编程语言即可使用 Elasticsearch。 了解一些常用命令就足以满足日常使用。 如果你想成为 painless 达人,可以查看 painless 指南。你也可以阅读我的文章 “Elastic:开发者上手指南” 中的 “Painless 编程” 章节。

对于 nested 字段搜索,不要被看似复杂的查询吓倒。 只要你了解 bool 和 nested 查询的工作原理,就可以使用 bool 和 nested 查询作为构建块来自行构建强大的查询。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/169863.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

koa-router 正解

Koa-Router 之前分析过 Koa/ Koa-Bodyparser 的源码,今天让我们来分析下koa-router的源码,这个插件其实还是挺重要的。毕竟作为路由,我们还是要知道他的工作原理 这里会重申下 其实我是分析了 koa-router 主干流程。一些小众类的方法并没有看…

多步骤复杂 SQL 优化实例

问题先看数据:deliver 表是主表,一个客户会发生多次投递行为:deliverItem 表是从表,一个投递行为有多个投递项,delivered 是投递状态(1 表示未完成,2 表示投递完成):需求…

如何了解一个软件的设计?

刚入职,接手新项目,面对一个全新项目,怎么快速研究它? 很多人直接看源码,一头扎入代码,很快就迷失其中,最初那股子探索精神,也会逐渐被迷茫所替。有多少次你满怀激情打开一个开源项…

极光推送REST API与Java后台对接

极光推送官网的web推送页面 因为是对接它的api,所以我参照这这个样式实现了一个,效果如下: 定时任务推送界面,可定制。实现了推送一次和每日定时推送,如果再扩展的话有每周、每月的功能,只是没有这个业务…

银行数字化转型导师坚鹏:银行数字化转型的五大痛点

首先从汇丰银行业绩持续下滑谈起,汇丰银行作为一家国际知名的全球性银行,最近10年左右的时间里,营业收入持续下降,已经从2008年的1400多亿美元到2021年的804.29亿美元; 净利润徘徊不前,2021年比2020年下降29.2%,仅为52…

kafka心得记录

1.为何引入kafka? 削峰填谷,主要还是为了应对上游瞬时大流量的冲击,避免出现流量毛刺现象,保护下游应用和数据库不被大流量打垮。 2.kafka备份机制,主从机制,Leader-Follower: Kafka 定义了两类副本:领导…

C语言文件操作函数详解——将你的代码永久化 ( •̀ ω •́ )✧

🎄博客主页:🎐大明超听话 🎋欢迎关注:👍点赞🙌关注✍评论 🎍系列专栏:🎑从零开始C语言 🎊从0开始数据结构与算法详解 🎆计算机考研——…

JavaScript中的原型链

本文作者为奇舞团前端开发工程师概述JavaScript 是 Web 的编程语言,简单易学,功能强大,但由于过于灵活设计理念,导致初学者经常一脸懵,本文要谈的是JavaScript中难点之一原型链。原型链的前世JavaScript的诞生要理解Ja…

Nessus介绍与安装

Nessus介绍与安装 1.Nessus简介 Nessus号称是世界上最流行的漏洞扫描程序,全世界有超过75000个组织在使用它。该工具提供完整的电脑漏洞扫描服务,并随时更新其漏洞数据库。Nessus不同于传统的漏洞扫描软件,Nessus可同时在本机或远端上遥控&…

测试开发 | Dubbo 接口测试原理及多种方法实践总结

image1080478 86.9 KB 1、什么是 Dubbo? Dubbo 最开始是应用于淘宝网,由阿里巴巴开源的一款优秀的高性能服务框架,由 Java 开发,后来贡献给了 Apache 开源基金会组织。 下面以官网的一个说明来了解一下架构的演变过程&#xff0…

初学Java中的方法,看这篇就够了

本篇介绍了Java中方法的概念以及方法的使用(方法的定义和调用,实参和形参的关系).方法重载的介绍和使用,编译器如何实现方法重载- -方法签名,介绍和使用方法调用自身解决问题的技巧–递归 对比递归和循环的优缺点 掌握Java中的方法一.方法的概念及使用1.什么是方法2.方法的使用…

【C++】AVL树

​🌠 作者:阿亮joy. 🎆专栏:《吃透西嘎嘎》 🎇 座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根 目录👉AVL树&am…

【Linux】CentOS、CentOS Stream、RedHat 和Fedora 之间的关系

目录 简单说明 详细说明 红帽(Red Hat)系和德班(Debian)系 简单说明 在centos8之前: Fedora 》RedHat 》CentOS Fedora 是RedHat的“试验场”,很多新功能和特性先加入Fedora 稳定后再加入RedHat&…

YOLOv5 引入 最新 BiFusion Neck | 附详细结构图

YOLO 社区自前两次发布以来一直情绪高涨!随着中国农历新年2023兔年的到来,美团对YOLOv6进行了许多新的网络架构和训练方案改进。此版本标识为 YOLOv6 v3.0。对于性能,YOLOv6-N在COCO数据集上的AP为37.5%,通过NVIDIA Tesla T4 GPU测…

99.恢复二叉搜索树

99.恢复二叉搜索树 1、题目描述 题目的额外要求是: 使用 O(n) 空间复杂度的解法很容易实现。你能想出一个只使用 O(1) 空间的解决方案吗? 2、解题思路 二叉搜索树中某两个节点被交换了数值,那么对中序序列的影响如下: 假设没有交换之前二叉…

活动星投票千人共读一本书网络评选微信的投票方式线上免费投票

“千人共读一本书”网络评选投票_视频投票评选_投票统计小程序_微信不记名投票用户在使用微信投票的时候,需要功能齐全,又快捷方便的投票小程序。而“活动星投票”这款软件使用非常的方便,用户可以随时使用手机微信小程序获得线上投票服务&am…

正则表达式和re模块

目录 一.基础匹配 1.什么是正则表达式 re模块三个基础方法 re.match(匹配规则,被匹配字符串) search(匹配规则,被匹配字符串) findall(匹配规则,被匹配字符串) 小结 二.元字符匹配 单字符匹配: 示例: 数量匹配 边界匹配 分组匹配…

【Java】【系列篇】【Spring源码解析】【三】【体系】【BeanFactory体系】

BeanFactory体系BeanFactory整体结构体系图顶层接口-BeanFactory1.1、描述1.2、方法解析(15个)1.2.1、属性1.2.2、获取bean实例1.2.3、获取bean的提供者(对象工厂)1.2.4、判断是否包含bean1.2.5、单例,原型,bean类型的判断1.2.6、…

SAP ABAP——SAP包(二)【CTS | 传输请求】

💂作者简介: THUNDER王,一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学会计专业大二本科在读,阿里云社区专家博主,华为云社区云享专家,CSDN SAP应用技术领域新兴创作者。   在学习工…

CentOS7.9配置Nginx反向代理+NodeJS部署上线

Nginx配置 Nginx是一个高性能的HTTP和反向代理服务,许多的大型网站都会采用Nginx来进行HTTP服务器托管 安装编译环境gcc g 进入到root目录: yum -y install make zlib zlib-devel gcc-c libtool openssl openssl-devel 安装PCRE PCRE功能时让Ngi…