Elasticsearch学习-嵌套文档

本文涉及的产品
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
简介:

本文以Elasticsearch 6.8.4版本为例,介绍Elasticsearch嵌套文档的使用。

image

最近一段时间都在搞Elasticsearch搜索相关的工作,总结一下搜索知识点供大家参考。

在Elasticsearch取消了多个索引内创建多个type的机制,由于场景需要,所以调研了嵌套文档父子文档

image

以文章和文章留言为例,嵌套文档都在一个文档内,而父子文档则分开存储了父文档与子文档,本文我们来学习嵌套文档的使用。

1、嵌套文档

嵌套文档看似与文档内有一个集合字段类似,但是实则有很大区别,以上面图中嵌套文档为例,留言1,留言2,留言3虽然都在当前文章所在的文档内,但是在内部其实存储为4个独立文档,如下图所示。

image

同时,嵌套文档的字段类型需要设置为nested,设置成nested后的不能被直接查询,需要使用nested查询,这里不做具体介绍,详细查看1.2。

1.1 创建索引

接下来,介绍一下如何创建嵌套文档索引,比如有这样的数据,如下:

{
  "title": "这是一篇文章",
  "body":  "这是一篇文章,从哪里说起呢? ... ...",
  "comments": [ 
    {
      "name":    "张三",
      "comment": "写的不错",
      "age":     28,
      "date":    "2020-05-04"
    },
    {
      "name":    "李四",
      "comment": "写的很好",
      "age":     20,
      "date":    "2020-05-04"
    },
    {
      "name":    "王五",
      "comment": "这是一篇非常棒的文章",
      "age":     31,
      "date":    "2020-05-01"
    }
  ]
}

创建索引名和type均为blog的索引,其中comments字段为嵌套文档类型,需要将type设置为nested,其余都是一些正常的字段,创建索引语句如下:

PUT http://localhost:9200/blog/

{
  "mappings": {
    "blog": {
      "properties": {
        "comments": {
          "type": "nested",
          "properties": {
            "date": {
              "type": "date"
            },
            "name": {
              "type": "text",
              "fields": {
                "keyword": {
                  "type": "keyword"
                }
              }
            },
            "comment": {
              "type": "text",
              "fields": {
                "keyword": {
                  "type": "keyword"
                }
              }
            },
            "age": {
              "type": "long"
            }
          }
        },
        "body": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword"
            }
          }
        },
        "title": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword"
            }
          }
        }
      }
    }
  }
}

如下图所示

image

1.2 插入数据

将1.1中示例的数据插入blog索引,对嵌套文档来说,插入没什么特别的,如下:

PUT http://localhost:9200/blog/blog/1/

{
    "title":"这是一篇文章",
    "body":"这是一篇文章,从哪里说起呢? ... ...",
    "comments":[
        {
            "name":"张三",
            "comment":"写的不错",
            "age":28,
            "date":"2020-05-04"
        },
        {
            "name":"李四",
            "comment":"写的很好",
            "age":20,
            "date":"2020-05-04"
        },
        {
            "name":"王五",
            "comment":"这是一篇非常棒的文章",
            "age":31,
            "date":"2020-05-01"
        }
    ]
}

如图所示:

image

image

1.3 查询

在前面说到,使用嵌套文档时,直接查询nested文档时查询不到的,这里试一下,先查询一下根文档的内容(文章内容),查询title包含‘文章’的内容:

POST http://localhost:9200/blog/blog/_search/

{
  "query": {
    "bool": {
      "filter": [
        {
          "bool": {
            "must": [
              {
                "match_phrase": {
                  "title": {
                    "query": "文章"
                  }
                }
              }
            ]
          }
        }
      ]
    }
  }
}

Elasticsearch-Head,如下图所示

image

接下来我们查询一下,留言中name为张三的数据,查询如下:

{
  "query": {
    "bool": {
      "filter": [
        {
          "bool": {
            "must": [
              {
                "match_phrase": {
                  "comments.name": {
                    "query": "张三"
                  }
                }
              }
            ]
          }
        }
      ]
    }
  }
}

Elasticsearch-Head 如下图所示

image

这里举例,我们要查询title中包含‘文章’且留言name中包含‘张三’的数据,使用如下查询:

POST http://localhost:9200/blog/blog/_search/

{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": "文章"
          }
        },
        {
          "nested": {
            "path": "comments",
            "query": {
              "bool": {
                "must": [
                  {
                    "match": {
                      "comments.name": "张三"
                    }
                  }
                ]
              }
            }
          }
        }
      ]
    }
  }
}

Elasticsearch-Head 如下图所示

image

其实从查询语句中可以看出,nested中查询的是嵌套文档的内容,语法与正常查询时一致。

使用嵌套文档时,文档的分数计算需要注意,参考官方文档的描述:

nested 查询肯定可以匹配到多个嵌套的文档。每一个匹配的嵌套文档都有自己的相关度得分,但是这众多的分数最终需要汇聚为可供根文档使用的一个分数。

默认情况下,根文档的分数是这些嵌套文档分数的平均值。可以通过设置 score_mode 参数来控制这个得分策略,相关策略有 avg (平均值), max (最大值), sum (加和) 和 none (直接返回 1.0 常数值分数)。

1.4 排序

可能有一些场景需要按照嵌套文档的字段记性排序,举例:

为了符合上述场景,新增两条数据:

PUT http://localhost:9200/blog/blog/2/

{
  "title": "这是一篇文章2",
  "body":  "这是一篇文章2,从哪里说起呢? ... ...",
  "comments": [ 
    {
      "name":    "张三",
      "comment": "写的不错",
      "age":     28,
      "date":    "2020-05-11"
    },
    {
      "name":    "李四",
      "comment": "写的很好",
      "age":     20,
      "date":    "2020-05-16"
    },
    {
      "name":    "王五",
      "comment": "这是一篇非常棒的文章",
      "age":     31,
      "date":    "2020-05-01"
    }
  ]
}

PUT http://localhost:9200/blog/blog/3/

{
  "title": "这是一篇文章3",
  "body":  "这是一篇文章3,从哪里说起呢? ... ...",
  "comments": [ 
    {
      "name":    "张三",
      "comment": "写的不错",
      "age":     28,
      "date":    "2020-05-03"
    },
    {
      "name":    "李四",
      "comment": "写的很好",
      "age":     20,
      "date":    "2020-05-20"
    },
    {
      "name":    "王五",
      "comment": "这是一篇非常棒的文章",
      "age":     31,
      "date":    "2020-05-01"
    }
  ]
}

查询title中包含‘文章’且留言name中包含‘张三’,并且按照留言date字段倒序排序,查询语句如下:

{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": "文章"
          }
        },
        {
          "nested": {
            "path": "comments",
            "query": {
              "bool": {
                "must": [
                  {
                    "match": {
                      "comments.name": "张三"
                    }
                  }
                ]
              }
            }
          }
        }
      ]
    }
  },
  "sort": {
    "comments.date": {
      "order": "desc",
      "mode": "max",
      "nested_path": "comments",
      "nested_filter": {
        "bool": {
          "must": [
            {
              "match": {
                "comments.name": "张三"
              }
            }
          ]
        }
      }
    }
  }
}

需要注意的是,在sort内,又添加了nested_filter来过滤一遍上面嵌套文档的查询条件,原因是这样的,在嵌套文档查询排序时是先按照条件进行查询,查询后再进行排序,那么可能由于数据的原因,导致排序的字段不是按照匹配上的数据进行排序,比如这是本文正确的结果,如下图所示(为了方便查看,使用图表展示的数据)。

image

如果我们去掉nested_filter,在查询,由于文章3中李四评论的日期是20号,导致这条记录排在了最前面,这就是为什么使用nested_filter的原因,查询结果如下:

image

1.5 聚合

聚合的场景可能也比较常见,其实熟悉上面嵌套文档的使用的话,对聚合文档使用难度应该也不大,

新增一条数据:

PUT http://localhost:9200/blog/blog/4/

{
  "title": "这是一篇文章4",
  "body":  "这是一篇文章4,从哪里说起呢? ... ...",
  "comments": [ 
    {
      "name":    "张三",
      "comment": "写的不错",
      "age":     28,
      "date":    "2020-03-03"
    },
    {
      "name":    "李四",
      "comment": "写的很好",
      "age":     20,
      "date":    "2020-04-20"
    },
    {
      "name":    "王五",
      "comment": "这是一篇非常棒的文章",
      "age":     31,
      "date":    "2020-06-01"
    }
  ]
}

举例:需要查询每个月评论人数的平均数,查询语句如下:

POST http://localhost:9200/blog/blog/_search/

{
  "size": 0,
  "aggs": {
    "comments": {
      "nested": {
        "path": "comments"
      },
      "aggs": {
        "by_month": {
          "date_histogram": {
            "field": "comments.date",
            "interval": "month",
            "format": "yyyy-MM"
          },
          "aggs": {
            "avg_stars": {
              "avg": {
                "field": "comments.age"
              }
            }
          }
        }
      }
    }
  }
}

结果如下图所示:

image

1.6 使用建议

  • 正如本文所说,嵌套文档中,所有内容都在同一个文档内,这就导致嵌套文档进行增加、修改或者删除时,整个文档都要重新被索引。嵌套文档越多,这带来的成本就越大。当时就是由于这个原因,最终没有选择使用嵌套文档。
  • 嵌套文档的分数计算问题需要注意,可以参考本文1.3最后部分。
相关实践学习
使用阿里云Elasticsearch体验信息检索加速
通过创建登录阿里云Elasticsearch集群,使用DataWorks将MySQL数据同步至Elasticsearch,体验多条件检索效果,简单展示数据同步和信息检索加速的过程和操作。
ElasticSearch 入门精讲
ElasticSearch是一个开源的、基于Lucene的、分布式、高扩展、高实时的搜索与数据分析引擎。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr(也是基于Lucene)。 ElasticSearch的实现原理主要分为以下几个步骤: 用户将数据提交到Elastic Search 数据库中 通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据 当用户搜索数据时候,再根据权重将结果排名、打分 将返回结果呈现给用户 Elasticsearch可以用于搜索各种文档。它提供可扩展的搜索,具有接近实时的搜索,并支持多租户。
目录
相关文章
|
10天前
|
自然语言处理 Java 网络架构
elasticsearch学习三:elasticsearch-ik分词器的自定义配置 分词内容
这篇文章是关于如何自定义Elasticsearch的ik分词器配置以满足特定的中文分词需求。
57 0
elasticsearch学习三:elasticsearch-ik分词器的自定义配置 分词内容
|
10天前
|
JSON Java 网络架构
elasticsearch学习四:使用springboot整合 rest 进行搭建elasticsearch服务
这篇文章介绍了如何使用Spring Boot整合REST方式来搭建和操作Elasticsearch服务。
74 4
elasticsearch学习四:使用springboot整合 rest 进行搭建elasticsearch服务
|
10天前
|
自然语言处理 搜索推荐 关系型数据库
elasticsearch学习六:学习 全文搜索引擎 elasticsearch的语法,使用kibana进行模拟测试(持续更新学习)
这篇文章是关于Elasticsearch全文搜索引擎的学习指南,涵盖了基本概念、命令风格、索引操作、分词器使用,以及数据的增加、修改、删除和查询等操作。
11 0
elasticsearch学习六:学习 全文搜索引擎 elasticsearch的语法,使用kibana进行模拟测试(持续更新学习)
|
10天前
|
Web App开发 JavaScript Java
elasticsearch学习五:springboot整合 rest 操作elasticsearch的 实际案例操作,编写搜索的前后端,爬取京东数据到elasticsearch中。
这篇文章是关于如何使用Spring Boot整合Elasticsearch,并通过REST客户端操作Elasticsearch,实现一个简单的搜索前后端,以及如何爬取京东数据到Elasticsearch的案例教程。
104 0
elasticsearch学习五:springboot整合 rest 操作elasticsearch的 实际案例操作,编写搜索的前后端,爬取京东数据到elasticsearch中。
|
10天前
|
自然语言处理 Java Maven
elasticsearch学习二:使用springboot整合TransportClient 进行搭建elasticsearch服务
这篇博客介绍了如何使用Spring Boot整合TransportClient搭建Elasticsearch服务,包括项目创建、Maven依赖、业务代码和测试示例。
37 0
elasticsearch学习二:使用springboot整合TransportClient 进行搭建elasticsearch服务
|
10天前
|
存储 JSON Java
elasticsearch学习一:了解 ES,版本之间的对应。安装elasticsearch,kibana,head插件、elasticsearch-ik分词器。
这篇文章是关于Elasticsearch的学习指南,包括了解Elasticsearch、版本对应、安装运行Elasticsearch和Kibana、安装head插件和elasticsearch-ik分词器的步骤。
50 0
elasticsearch学习一:了解 ES,版本之间的对应。安装elasticsearch,kibana,head插件、elasticsearch-ik分词器。
|
1月前
|
JSON 自然语言处理 算法
ElasticSearch基础2——DSL查询文档,黑马旅游项目查询功能
DSL查询文档、RestClient查询文档、全文检索查询、精准查询、复合查询、地理坐标查询、分页、排序、高亮、黑马旅游案例
ElasticSearch基础2——DSL查询文档,黑马旅游项目查询功能
|
1月前
|
JSON 自然语言处理 数据库
ElasticSearch基础1——索引和文档。Kibana,RestClient操作索引和文档+黑马旅游ES库导入
概念、ik分词器、倒排索引、索引和文档的增删改查、RestClient对索引和文档的增删改查
ElasticSearch基础1——索引和文档。Kibana,RestClient操作索引和文档+黑马旅游ES库导入
|
2月前
|
存储 搜索推荐 API
探究:Elasticsearch 文档的 _id 是 Lucene 的 docid 吗?
【8月更文挑战第31天】在深入探索Elasticsearch(简称ES)这一强大的搜索引擎时,了解其底层存储机制——特别是与Lucene的关系,对于优化查询性能、设计高效的数据模型至关重要。其中,一个常见且容易引发误解的问题便是:Elasticsearch中文档的_id字段是否直接等同于Lucene的docid?本文将通过图文并茂的方式,详细剖析这一问题,帮助读者理解两者之间的微妙关系。
63 0
|
2月前
|
JSON 测试技术 API
黑马商城 Elasticsearch从入门到部署 RestClient操作文档
这篇文章详细介绍了如何使用Java的RestHighLevelClient客户端与Elasticsearch进行文档操作,包括新增、查询、删除、修改文档以及批量导入文档的方法,并提供了相应的代码示例和操作步骤。