vlambda博客
学习文章列表

读书笔记《elasticsearch-7-0-cookbook-fourth-edition》关系和地理查询

Relationship and Geo Queries

 在本章中,我们将探讨可用于搜索 Elasticsearch 与地理位置文档之间的关系的特殊查询。

当我们有父子关系时(基于 join 字段映射),我们可以使用特殊查询来查询相似的关系。 Elasticsearch 不提供 SQL 连接,但它可以让您搜索与子/父相关的文档;它可以通过父选择或匹配父文档并按其子级过滤来检索子文档。在 Elasticsearch 中创建父子关系的方式非常强大,可以帮助解决许多常见的数据关系问题,这些问题用于在 Elasticsearch 中轻松解决传统关系数据库中的问题。根据我的经验,这些功能在年轻的 Elasticsearch 用户中并不受欢迎,但如果您管理智能数据源,它们就很有价值。

在本章中,我们还将了解如何使用嵌套查询来查询嵌套对象。本章的最后一部分与地理定位查询有关,它提供基于距离、框和多边形的查询,以匹配符合此条件的文档。

在本章中,我们将介绍以下食谱:

  • Using the has_child query
  • Using the has_parent query
  • Using the nested query
  • Using the geo_bounding_box query
  • Using the geo_polygon query
  • Using the geo_distance query

Using the has_child query

Elasticsearch 不仅支持简单的不相关文档,它还允许您定义基于父级和子级的层次结构。 has_child 查询允许您通过匹配其他查询来查询子项的父文档。

Getting ready

正如我们在 下载和安装 Elasticsearch  第 1 章入门,您需要安装并运行 Elasticsearch 来执行当前的配方代码。

要执行以下部分中的代码,可以使用任何 HTTP 客户端。这包括 curl (https://curl.haxx.se/)、Postman (https://www.getpostman.com/),或其他类似版本。我建议使用 Kibana 控制台,因为这为 Elasticsearch 提供了代码完成和更好的字符转义。

要正确执行以下命令,您需要使用在线提供的 ch04/populate_kibana.txt 命令填充的索引代码。

本秘籍中使用的索引是 mybooks-join 统一建模语言UML)的数据模型如下:

读书笔记《elasticsearch-7-0-cookbook-fourth-edition》关系和地理查询

How to do it...

要执行 has_child 查询,我们将执行以下步骤:

  1. We want to search the parent book of the children author, which has a term in the name field called martin. We can create this kind of query using the following code:
POST /mybooks-join/_search
{
  "query": {
    "has_child": {
      "type": "author",
      "query": {
        "term": {
          "name": "martin"
        }
      },
      "inner_hits" : {}
    }
  }
}
  1. If everything is working well, the result returned by Elasticsearch should be as follows:
{
  ...truncated...,
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "mybooks-join",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 1.0,
        "_source" : ...truncated...,
        "inner_hits" : {
          "author" : {
            "hits" : {
              "total" : 1,
              "max_score" : 1.2039728,
              "hits" : [
                {
                  "_index" : "mybooks-join",
                  "_type" : "_doc",
                  "_id" : "a3",
                  "_score" : 1.2039728,
                  "_routing" : "3",
                  "_source" : {
                    "name" : "Martin",
                    "surname" : "Twisted",
                    "join" : {
                      "name" : "author",
                      "parent" : "3"
                    }
                  }
                }
              ]
            }
          }
        }
      }
    ]
  }
}

对于这个例子,我们使用了 inner_hits 来返回匹配的孩子。

How it works...

这种查询通过返回其子项与查询匹配的父文档来工作。查询可以是任何类型。这种查询的前提是子节点必须在其父节点的分片中正确索引。在内部,这种查询是在children上执行的,所有children的ID都用于过滤parent。系统必须有足够的内存来存储子 ID。

用于控制此过程的参数如下:

  • The type parameter describes the type of children. This type is part of the same index of the parent: it's the name provided in the join field parameter at index time.
  • The query parameter can be executed for selection of the children. Any kind of query can be used.
  • If defined, the score_mode parameter (the default is none; available values are max, sum, avg, and none) allows you the aggreagate the children scores with the parent ones.
  • min_children and max_children are optional parameters. This is the minimum/maximum number of children that are required to match the parent document.
  • ignore_unmapped (default false), when set to true, will ignore unmapped types. This is very useful when executing a query on multiple indices and some types are missing. The default behavior is to throw an exception if there is a mapping error.
在 Elasticsearch 中,一个文档必须只有一个父级,因为父级 ID 用于选择将子级放入的分片。处理子级文档时,请务必记住,它们必须与父级存储在同一个分片中。如果路由未知,则在获取、修改和删除它们时必须采取特殊的预防措施。 

由于父子关系可以被认为类似于标准 SQL 中的外键,因此由于 Elasticsearch 的分布式特性,存在一些限制。这包括以下内容:

  • There must be only one parent for each type
  • The join part of child or parent is done in a shard and not distributed on all the clusters to reduce networking and increase performance

There's more...

有时,您需要根据子字段对父母进行排序。为此,您需要通过查看子字段的最高分数来对父项进行排序。要执行这种查询,您可以通过以下方式使用 function_score 查询:

POST /mybooks-join/_search
{
  "query": {
    "has_child": {
      "type": "author",
      "score_mode": "max",
      "query": {
        "function_score": {
          "script_score": {
            "script": """doc["rating"].value"""
          }
        }
      },
      "inner_hits": {}
    }
  }
}

通过对父项的每个子项执行此查询,获取最大分数(使用 function_score),这是我们要排序的字段的值。

在前面的示例中,我们使用了脚本,这将在 第 9 章管理集群中讨论。这需要处于活动状态才能使用。

See also

您可以参考以下食谱以获取与此食谱相关的更多信息:

  • The Indexing a document recipe in Chapter 3, Basic Operations
  • The Mapping a child document with a join field recipe in Chapter 2, Managing Mapping
  • Using a  function score query recipe in Chapter 6, Text and Numeric Queries

Using the has_parent query

在前面的秘籍中,我们看到了 has_child 查询。 Elasticsearch 提供了一个查询来根据父查询 has_parent 搜索子文档。

Getting ready

您需要启动并运行 Elasticsearch 安装,正如我们在 下载和安装 Elasticsearch 第 1 章, 开始

要执行这些命令,我​​建议使用 Kibana 控制台,因为这为 Elasticsearch 提供了代码完成和更好的字符转义。

要正确执行以下命令,您需要使用 ch04/populate_kibana.txt 命令填充的索引,即 可在在线代码中找到。这个秘籍中使用的索引是  mybooks-join

How to do it...

要执行 has_parent 查询,我们将执行以下步骤:

  1. We want to search for the children author of the parents book that has the term joe in the description field. We can create this kind of query using the following code:
POST /mybooks-join/_search
{
  "query": {
    "has_parent": {
      "parent_type": "book",
      "query": {
        "term": {
          "description": "bill"
        }
      }
    }
  }
}
  1. If everything is fine here, the result returned by Elasticsearch should be as follows:
{
  ...truncated...
  "hits" : {
    "total" : 2,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "mybooks-join",
        "_type" : "_doc",
        "_id" : "a2",
        "_score" : 1.0,
        "_routing" : "2",
        "_source" : {
          "name" : "Agatha",
          "surname" : "Princeton",
          "rating" : 2.1,
          "join" : {
            "name" : "author",
            "parent" : "2"
          }
        }
      },
      {
        "_index" : "mybooks-join",
        "_type" : "_doc",
        "_id" : "a3",
        "_score" : 1.0,
        "_routing" : "3",
        "_source" : {
          "name" : "Martin",
          "surname" : "Twisted",
          "rating" : 3.2,
          "join" : {
            "name" : "author",
            "parent" : "3"
          }
        }
      }
    ]
  }
}

How it works...

这种查询通过返回其父文档与子查询匹配的子文档来工作。在内部,这个子查询是在父节点上执行的,所有匹配的父节点的 ID 都用于过滤子节点。系统必须有足够的内存来存储所有父 ID。

用于控制此过程的参数如下:

  • parent_type: This is the type of the parent.
  • query: This is the query that can be executed to select the parents. Every kind of query can be used.
  • score: The default value is false. Using the default configuration of false, Elasticsearch ignores the scores for parent documents, thus reducing memory and increasing performance. If it's set to true, the parent query score is aggregated into the children's.

使用计算出的分数,您可以使用与上一节中使用 function_score 显示的方法相同的方法基于父级对结果命中进行排序。

See also

您可以参考以下食谱以获取与此食谱相关的更多信息:

  • The Indexing a document recipe in Chapter 4Exploring Search Capabilities
  • The Mapping a child document with a join field recipe in Chapter 3, Basic Operations

Using nested queries

对于基于嵌套对象的查询,正如我们在 第 2 章中看到的,  管理映射,有一个特殊的嵌套查询。这种查询是必需的,因为嵌套对象在 Elasticsearch 中以一种特殊的方式被索引。

Getting ready

您需要启动并运行 Elasticsearch 安装,正如我们在 下载和安装 Elasticsearch 第 1 章, 开始

要执行这些命令,可以使用任何 HTTP 客户端,例如 curl (https://curl.haxx.se/)、Postman (https://www.getpostman.com/),或类似的。我建议使用 Kibana 控制台,因为它为查询文本提供了代码完成和更好的字符转义。

要正确执行以下命令,您需要使用 ch04/populate_kibana.txt 命令填充的索引,该命令可用在在线代码中。

这个秘籍中使用的索引是 mybooks-join

How to do it...

要执行嵌套查询,我们将执行以下步骤:

  1. We want to search the document for nested objects that are blue and whose size is greater than 10. The nested query will be as follows:
POST /mybooks-join/_search
{
  "query": {
    "nested": {
      "path": "versions",
      "score_mode": "avg",
      "query": {
        "bool": {
          "must": [
            {
              "term": {
                "versions.color": "blue"
              }
            },
            {
              "range": {
                "versions.size": {
                  "gt": 10
                }
              }
            }
          ]
        }
      }
    }
  }
}
  1. The result returned by Elasticsearch, if everything is alright, should be as follows:
{
  ...truncated...
  "hits" : {
    "total" : 1,
    "max_score" : 1.8754687,
    "hits" : [
      {
        "_index" : "mybooks-join",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.8754687,
        "_source" : {
          ...truncated...
          "versions" : [
            {
              "color" : "yellow",
              "size" : 5
            },
            {
              "color" : "blue",
              "size" : 15
            }
          ]
        }
      }
    ]
  }
}

How it works...

Elasticsearch 以一种特殊的方式管理嵌套对象。在索引期间,它们从主文档中提取并作为单独的文档进行索引,该文档保存在主文档的同一 Lucene 块中。

嵌套查询对嵌套文档执行第一个查询,在收集结果 ID 后,它们用于过滤主文档。用于控制此过程的参数如下:

  • path: This is the path of the parent document that contains the nested objects.
  • query: This is the query that can be executed to select the nested objects. Every kind of query can be used.
  • score_mode: The default value is avg. The valid values are avg, sum, min, max, and none, which control how to use the score of the nested document matches to better improve the query.

使用 score_mode,您可以使用 function_score 查询基于嵌套对象对结果文档进行排序。

See also

您可以参考以下食谱以获取与此食谱相关的更多信息:

  • The Managing nested objects recipe in Chapter 3, Managing Mappings
  • The Using the has_child query recipe in this chapter

Using the geo_bounding_box query

地理定位中最常见的操作之一是搜索框(正方形)。广场通常近似于商店、建筑物或城市的形状。

如果用户、文档或事件正在进入一个特殊的地方,这种查询可以在过滤器中用于实时监控。

Getting ready

您需要启动并运行 Elasticsearch 安装,正如我们在 下载和安装 Elasticsearch 第 1 章, 开始

要执行这些命令,可以使用任何 HTTP 客户端,例如 curl (https://curl.haxx.se/)、Postman (https://www.getpostman.com/),或类似的。我更喜欢使用 Kibana 控制台,因为它为 Elasticsearch 提供了代码完成、格式化和更好的字符转义。

要正确执行以下命令,您需要使用 ch04/populate_kibana.txt 命令填充的索引,该命令可用在在线代码中。

本节中使用的索引是 mygeo-index

How to do it...

要执行地理边界框查询,我们将执行以下步骤:

  1. A search to filter documents related to a with the coordinates 40.03, 72.0 and 40.717, 70.99 can be achieved with the following query:
POST /mygeo-index/_search?pretty
{
  "query": {
    "geo_bounding_box": {
      "pin.location": {
        "bottom_right": {
          "lat": 40.03,
          "lon": 72
        },
        "top_left": {
          "lat": 40.717,
          "lon": 70.99
        }
      }
    }
  }
}
  1. The result returned by Elasticsearch, if everything is alright, should be as follows:
{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "mygeo-index",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "pin" : {
            "location" : {
              "lat" : 40.12,
              "lon" : 71.34
            }
          }
        }
      }
    ]
  }
}

How it works...

Elasticsearch 有很多优化,以方便搜索盒子形状。纬度和经度被索引用于快速范围检查,因此这种过滤器执行得非常快。在 Elasticsearch 5.x 或更高版本中,由于 Lucene 6.2.x 中对地理数据索引的大量改进,地理查询比以前的版本更快。

执行地理边界框过滤器所需的参数是 top_left(框的顶部和左侧坐标)和bottom_right(底部和右侧的坐标)框)地理点。

可以使用多种地理点表示,如 中的 映射 GeoPoint 字段 配方中所述第 2 章管理映射

See also

您可以参考以下配方以获取与此配方相关的更多信息:

  • The Mapping a GeoPoint field recipe in Chapter 2Managing Mapping

Using the geo_polygon query

使用 geo_bounding_box 查询 配方展示了如何过滤正方形部分,这是最常见的情况; Elasticsearch 提供了一种使用 geo_polygon 过滤器过滤用户定义的多边形形状的方法。如果多边形代表一个国家或地区,或一个地区形状,则此查询很有用。

Getting ready

您需要启动并运行 Elasticsearch 安装,正如我们在 下载和安装 Elasticsearch 第 1 章, 开始

要执行这些命令,我​​建议使用 Kibana 控制台,因为这为 Elasticsearch 提供了代码完成、代码格式化和更好的字符转义。

要正确执行以下命令,您需要使用 ch04/populate_kibana.txt 命令填充的索引,该命令可用在在线代码中。

本节中使用的索引是  mygeo-index

How to do it...

要执行 geo_polygon 查询,我们将执行以下步骤:

  1. Searching documents in which pin.location is part of a triangle (its shape is made up of three geo points) is done with a query similar to the following:
POST /mygeo-index/_search
{
  "query": {
    "geo_polygon": {
      "pin.location": {
        "points": [
          {
            "lat": 50,
            "lon": -30
          },
          {
            "lat": 30,
            "lon": -80
          },
          {
            "lat": 80,
            "lon": -90
          }
        ]
      }
    }
  }
}
  1. The result returned by Elasticsearch, if everything is alright, should be as follows:
{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "mygeo-index",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "pin" : {
            "location" : {
              "lat" : 40.12,
              "lon" : -71.34
            }
          }
        }
      }
    ]
  }
}

How it works...

geo_polygon 查询允许您使用地理点列表定义自己的形状,以便 Elasticsearch 可以过滤多边形中使用的文档。这可以被认为是通用多边形形式的地理边界框的扩展。

geo_polygon 查询允许使用 ignore_unmapped 参数,这有助于在未定义字段的多索引或类型的情况下安全地执行搜索(GeoPoint没有为某些索引或分片定义字段,因此会静默失败而不会给出错误)。

See also

您可以参考以下食谱以获取与此食谱相关的更多信息:

  • The Mapping a GeoPoint field recipe in Chapter 2Managing Mapping
  • The Using the geo_bounding_box query recipe in this chapter

Using the geo_distance query

处理地理位置时,一项常见任务是根据与某个位置的距离过滤结果。此方案涵盖了非常常见的站点要求,例如:

  • Finding the nearest restaurant within a distance of 20 km
  • Finding my nearest friends within a range of 10 km

geo_distance 查询用于实现此目标。

Getting ready

您需要启动并运行 Elasticsearch 安装,正如我们在 下载和安装 Elasticsearch 第 1 章, 开始

要执行这些命令,可以使用任何 HTTP 客户端,例如 curl (https://curl.haxx.se/)、Postman (https://www.getpostman.com/),或类似的。我建议使用 Kibana 控制台,因为它为 Elasticsearch 提供了代码完成和更好的字符转义。

要正确执行以下命令,您需要使用 ch04/populate_kibana.txt 命令填充的索引,该命令可用在在线代码中。

这个秘籍中使用的索引是 mygeo-index

How to do it...

要执行 geo_distance 查询,我们将执行以下步骤:

  1. Searching documents in which pin.location is 200km away from lat as 40 and lon as 70 is done with a query similar to the following:
GET /mygeo-index/_search
{
  "query": {
    "geo_distance": {
      "pin.location": {
        "lat": 40,
        "lon": 70
      },
      "distance": "200km"
    }
  }
}
  1. The result returned by Elasticsearch, if everything is alright, should be as follows:
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "mygeo-index",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "pin" : {
            "location" : {
              "lat" : 40.12,
              "lon" : 71.34
            }
          }
        }
      }
    ]
  }
}

How it works...

正如我们在 映射 GeoPoint 字段 配方中所讨论的,有几种方法可以定义一个地理点,以便以优化的方式在内部保存搜索的项目。距离查询执行给定地理点和文档中的点之间的距离计算,返回满足距离要求的命中。

控制距离查询的参数如下:

  • The field and the point of reference to be used to calculate the distance. In the preceding example, we have pin.location and (40,70).
  • distance defines the distance to be considered. It is usually expressed as a string by a number plus a unit.
  • unit (optional) can be the unit of the distance value, if distance is defined as a number. The valid values are as follows:
    • in or inch
    • yd or yards
    • m or miles
    • km or kilometers
    • m or meters
    • mm or millimeters
    • cm or centimeters
  • distance_type (default: sloppy_arc; valid choices are arcsloppy_arc, or plane) defines the type of algorithm to calculate the distance.
  • validation_method (default STRICT) is used for validating the geo point. The valid values are as follows:
    • IGNORE_MALFORMED is used to accept invalid values for latitude and longitude
    • COERCE is used to try to correct wrong values
    • STRICT is used to reject invalid values
  • ignore_unmapped is used to safely execute the query in the case of multi-indices that can have a missing definition of GeoPoint.

See also

您可以参考以下食谱以获取与此食谱相关的更多信息:

  • The Mapping a GeoPoint field recipe in Chapter 2Managing Mapping
  • The Using the range query recipe in Chapter 5, Text and Numeric Queries