Elasticsearch:查询嵌套对象

时间:2018-08-31 13:17:40

标签: elasticsearch

尊敬的Elasticsearch专家,
我在查询嵌套对象时遇到问题。让我们使用以下简化的映射:

{
  "mappings" : {
    "_doc" : {
      "properties" : {
        "companies" : {
          "type": "nested",
          "properties" : {
            "company_id": { "type": "long" },
            "name": { "type": "text" }
          }
        },
        "title": { "type": "text" }
      }
    }
  }
}

并在索引中放入一些文档:

PUT my_index/_doc/1
{
  "title" : "CPU release",
  "companies" : [
    { "company_id" : 1, "name" :  "AMD" },
    { "company_id" : 2, "name" :  "Intel" }
  ]
}

PUT my_index/_doc/2
{
  "title" : "GPU release 2018-01-10",
  "companies" : [
    { "company_id" : 1, "name" :  "AMD" },
    { "company_id" : 3, "name" :  "Nvidia" }
  ]
}

PUT my_index/_doc/3
{
  "title" : "GPU release 2018-03-01",
  "companies" : [
    { "company_id" : 3, "name" :  "Nvidia" }
  ]
}

PUT my_index/_doc/4
{
  "title" : "Chipset release",
  "companies" : [
    { "company_id" : 2, "name" :  "Intel" }
  ]
}

现在我要执行这样的查询:

{
  "query": {
    "bool": {
      "must": [
        { "match": { "title": "GPU" } },
        { "nested": {
            "path": "companies",
            "query": {
              "bool": {
                "must": [
                  { "match": { "companies.name": "AMD" } }
                ]
              }
            },
            "inner_hits" : {}
          }
        }
      ]
    }
  }
}

因此,我想获取具有匹配文档数量的匹配公司。所以上面的查询应该给我:

[
  { "company_id" : 1, "name" : "AMD", "matched_documents:": 1 }
]

以下查询:

{
  "query": {
    "bool": {
      "must": [
        { "match": { "title": "GPU" } }
        { "nested": {
            "path": "companies",
            "query": { "match_all": {} },
            "inner_hits" : {}
          }
        }
      ]
    }
  }
}

应该给我所有分配给文档的公司,该公司的标题包含“ GPU”以及匹配的文档数量:

[
  { "company_id" : 1, "name" : "AMD", "matched_documents:": 1 },
  { "company_id" : 3, "name" : "Nvidia", "matched_documents:": 2 }
]

是否有可能具有良好的性能来达到此结果?我显然对匹配的文档不感兴趣,只对匹配的文档和嵌套对象的数量不感兴趣。

感谢您的帮助。

1 个答案:

答案 0 :(得分:0)

就Elasticsearch而言,您需要做的是:

  1. 根据期望的标准(例如在GPU中有title或在Nvidia列表中提到companies)来过滤“父”文档;
  2. 按照特定条件(例如bucket(例如company_id)将“嵌套”文档分组;
  3. 计算每个存储桶中有多少个“嵌套”文档。

数组中的每个nested对象都是indexed as a separate hidden document,这使生活变得有些复杂。让我们看看如何汇总它们。

那么如何汇总和计算nested个文档呢?

您可以结合使用nestedtermstop_hits聚合来实现此目的:

POST my_index/doc/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": "GPU"
          }
        },
        {
          "nested": {
            "path": "companies",
            "query": {
              "match_all": {}
            }
          }
        }
      ]
    }
  },
  "aggs": {
    "Extract nested": {
      "nested": {
        "path": "companies"
      },
      "aggs": {
        "By company id": {
          "terms": {
            "field": "companies.company_id"
          },
          "aggs": {
            "Examples of such company_id": {
              "top_hits": {
                "size": 1
              }
            }
          }
        }
      }
    }
  }
}

这将给出以下输出:

{
  ...
  "hits": { ... },
  "aggregations": {
    "Extract nested": {
      "doc_count": 4, <== How many "nested" documents there were?
      "By company id": {
        "doc_count_error_upper_bound": 0,
        "sum_other_doc_count": 0,
        "buckets": [
          {
            "key": 3,  <== this bucket's key: "company_id": 3
            "doc_count": 2, <== how many "nested" documents there were with such company_id?
            "Examples of such company_id": {
              "hits": {
                "total": 2,
                "max_score": 1.5897496,
                "hits": [  <== an example, "top hit" for such company_id
                  {
                    "_nested": {
                      "field": "companies",
                      "offset": 1
                    },
                    "_score": 1.5897496,
                    "_source": {
                      "company_id": 3,
                      "name": "Nvidia"
                    }
                  }
                ]
              }
            }
          },
          {
            "key": 1,
            "doc_count": 1,
            "Examples of such company_id": {
              "hits": {
                "total": 1,
                "max_score": 1.5897496,
                "hits": [
                  {
                    "_nested": {
                      "field": "companies",
                      "offset": 0
                    },
                    "_score": 1.5897496,
                    "_source": {
                      "company_id": 1,
                      "name": "AMD"
                    }
                  }
                ]
              }
            }
          }
        ]
      }
    }
  }
}

请注意,Nvidia"doc_count": 2

但是,如果我们要计算拥有NvidiaIntel的“父”对象的数量怎么办?

如果我们想基于nested存储桶对父对象进行计数怎么办?

可以通过reverse_nested聚合来实现。

我们只需要稍微更改一下查询:

POST my_index/doc/_search
{
  "query": { ... },
  "aggs": {
    "Extract nested": {
      "nested": {
        "path": "companies"
      },
      "aggs": {
        "By company id": {
          "terms": {
            "field": "companies.company_id"
          },
          "aggs": {
            "Examples of such company_id": {
              "top_hits": {
                "size": 1
              }
            },
            "original doc count": { <== we ask ES to count how many there are parent docs
              "reverse_nested": {}
            }
          }
        }
      }
    }
  }
}

结果将如下所示:

{
  ...
  "hits": { ... },
  "aggregations": {
    "Extract nested": {
      "doc_count": 3,
      "By company id": {
        "doc_count_error_upper_bound": 0,
        "sum_other_doc_count": 0,
        "buckets": [
          {
            "key": 3,
            "doc_count": 2,
            "original doc count": {
              "doc_count": 2  <== how many "parent" documents have such company_id
            },
            "Examples of such company_id": {
              "hits": {
                "total": 2,
                "max_score": 1.5897496,
                "hits": [
                  {
                    "_nested": {
                      "field": "companies",
                      "offset": 1
                    },
                    "_score": 1.5897496,
                    "_source": {
                      "company_id": 3,
                      "name": "Nvidia"
                    }
                  }
                ]
              }
            }
          },
          {
            "key": 1,
            "doc_count": 1,
            "original doc count": {
              "doc_count": 1
            },
            "Examples of such company_id": {
              "hits": {
                "total": 1,
                "max_score": 1.5897496,
                "hits": [
                  {
                    "_nested": {
                      "field": "companies",
                      "offset": 0
                    },
                    "_score": 1.5897496,
                    "_source": {
                      "company_id": 1,
                      "name": "AMD"
                    }
                  }
                ]
              }
            }
          }
        ]
      }
    }
  }
}

我如何发现差异?

为使区别变得明显,让我们稍微更改一下数据,然后在文档列表中添加另一个Nvidia项:

PUT my_index/doc/2
{
  "title" : "GPU release 2018-01-10",
  "companies" : [
    { "company_id" : 1, "name" :  "AMD" },
    { "company_id" : 3, "name" :  "Nvidia" },
    { "company_id" : 3, "name" :  "Nvidia" }
  ]
}

最后一个查询(带有reverse_nested的查询)将为我们提供以下信息:

  "By company id": {
    "doc_count_error_upper_bound": 0,
    "sum_other_doc_count": 0,
    "buckets": [
      {
        "key": 3,
        "doc_count": 3,    <== 3 "nested" documents with Nvidia
        "original doc count": {
          "doc_count": 2   <== but only 2 "parent" documents
        },
        "Examples of such company_id": {
          "hits": {
            "total": 3,
            "max_score": 1.5897496,
            "hits": [
              {
                "_nested": {
                  "field": "companies",
                  "offset": 2
                },
                "_score": 1.5897496,
                "_source": {
                  "company_id": 3,
                  "name": "Nvidia"
                }
              }
            ]
          }
        }
      },

如您所见,这是一个难以理解的细微差别,但它完全改变了语义。

性能如何?

尽管在大多数情况下,nested查询和聚合的性能应该足够,但是当然要付出一定的代价。因此,建议在tuning for search speed时避免使用nested或父子类型。

在Elasticsearch中,尽管没有单一的配方,但通常可以通过denormalization来获得最佳性能。

希望这可以为您澄清一下nested这件事!