Link Search Menu Expand Document Documentation Menu

Has child query

The has_child query returns parent documents whose child documents match a specific query. You can establish parent/child relationships between documents in the same index by using a join field type.

The has_child query is slower than other queries because of the join operation it performs. Performance decreases as the number of matching child documents pointing to different parent documents increases. Each has_child query in your search may significantly impact query performance. If you prioritize speed, avoid using this query or limit its usage as much as possible.

Example

Before you can run a has_child query, your index must contain a join field in order to establish parent/child relationships. The index mapping request uses the following format:

PUT /example_index
{
  "mappings": {
    "properties": {
      "relationship_field": {
        "type": "join",
        "relations": {
          "parent_doc": "child_doc"
        }
      }
    }
  }
}

In this example, you’ll configure an index that contains documents representing products and their brands.

First, create the index and establish the parent/child relationship between brand and product:

PUT testindex1
{
  "mappings": {
    "properties": {
      "product_to_brand": { 
        "type": "join",
        "relations": {
          "brand": "product" 
        }
      }
    }
  }
}

Index two parent (brand) documents:

PUT testindex1/_doc/1
{
  "name": "Luxury brand",
  "product_to_brand" : "brand" 
}

PUT testindex1/_doc/2
{
  "name": "Economy brand",
  "product_to_brand" : "brand" 
}

Index three child (product) documents:

PUT testindex1/_doc/3?routing=1
{
  "name": "Mechanical watch",
  "sales_count": 150,
  "product_to_brand": {
    "name": "product", 
    "parent": "1" 
  }
}

PUT testindex1/_doc/4?routing=2
{
  "name": "Electronic watch",
  "sales_count": 300,
  "product_to_brand": {
    "name": "product", 
    "parent": "2" 
  }
}

PUT testindex1/_doc/5?routing=2
{
  "name": "Digital watch",
  "sales_count": 100,
  "product_to_brand": {
    "name": "product", 
    "parent": "2" 
  }
}

To search for the parent of a child, use a has_child query. The following query returns parent documents (brands) that make watches:

GET testindex1/_search
{
  "query" : {
    "has_child": {
      "type":"product",
      "query": {
        "match" : {
            "name": "watch"
        }
      }
    }
  }
}

The response returns both brands:

{
  "took": 15,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 2,
      "relation": "eq"
    },
    "max_score": 1,
    "hits": [
      {
        "_index": "testindex1",
        "_id": "1",
        "_score": 1,
        "_source": {
          "name": "Luxury brand",
          "product_to_brand": "brand"
        }
      },
      {
        "_index": "testindex1",
        "_id": "2",
        "_score": 1,
        "_source": {
          "name": "Economy brand",
          "product_to_brand": "brand"
        }
      }
    ]
  }
}

Retrieving inner hits

To return child documents that matched the query, provide the inner_hits parameter:

GET testindex1/_search
{
  "query" : {
    "has_child": {
      "type":"product",
      "query": {
        "match" : {
            "name": "watch"
        }
      },
      "inner_hits": {}
    }
  }
}

The response contains child documents in the inner_hits field:

{
  "took": 52,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 2,
      "relation": "eq"
    },
    "max_score": 1,
    "hits": [
      {
        "_index": "testindex1",
        "_id": "1",
        "_score": 1,
        "_source": {
          "name": "Luxury brand",
          "product_to_brand": "brand"
        },
        "inner_hits": {
          "product": {
            "hits": {
              "total": {
                "value": 1,
                "relation": "eq"
              },
              "max_score": 0.53899646,
              "hits": [
                {
                  "_index": "testindex1",
                  "_id": "3",
                  "_score": 0.53899646,
                  "_routing": "1",
                  "_source": {
                    "name": "Mechanical watch",
                    "sales_count": 150,
                    "product_to_brand": {
                      "name": "product",
                      "parent": "1"
                    }
                  }
                }
              ]
            }
          }
        }
      },
      {
        "_index": "testindex1",
        "_id": "2",
        "_score": 1,
        "_source": {
          "name": "Economy brand",
          "product_to_brand": "brand"
        },
        "inner_hits": {
          "product": {
            "hits": {
              "total": {
                "value": 2,
                "relation": "eq"
              },
              "max_score": 0.53899646,
              "hits": [
                {
                  "_index": "testindex1",
                  "_id": "4",
                  "_score": 0.53899646,
                  "_routing": "2",
                  "_source": {
                    "name": "Electronic watch",
                    "sales_count": 300,
                    "product_to_brand": {
                      "name": "product",
                      "parent": "2"
                    }
                  }
                },
                {
                  "_index": "testindex1",
                  "_id": "5",
                  "_score": 0.53899646,
                  "_routing": "2",
                  "_source": {
                    "name": "Digital watch",
                    "sales_count": 100,
                    "product_to_brand": {
                      "name": "product",
                      "parent": "2"
                    }
                  }
                }
              ]
            }
          }
        }
      }
    ]
  }
}

For more information about retrieving inner hits, see Inner hits.

Parameters

The following table lists all top-level parameters supported by has_child queries.

Parameter Required/Optional Description
type Required Specifies the name of the child relationship as defined in the join field mapping.
query Required The query to run on child documents. If a child document matches the query, the parent document is returned.
ignore_unmapped Optional Indicates whether to ignore unmapped type fields and not return documents instead of throwing an error. You can provide this parameter when querying multiple indexes, some of which may not contain the type field. Default is false.
max_children Optional The maximum number of matching child documents for a parent document. If exceeded, the parent document is excluded from the search results.
min_children Optional The minimum number of matching child documents required for a parent document to be included in the results. If not met, the parent is excluded. Default is 1.
score_mode Optional Defines how scores of matching child documents influence the parent document’s score. Valid values are:
- none: Ignores the relevance scores of child documents and assigns a score of 0 to the parent document.
- avg: Uses the average relevance score of all matching child documents.
- max: Assigns the highest relevance score from the matching child documents to the parent.
- min: Assigns the lowest relevance score from the matching child documents to the parent.
- sum: Sums the relevance scores of all matching child documents.
Default is none.
inner_hits Optional If provided, returns the underlying hits (child documents) that matched the query.

Sorting limitations

The has_child query does not support sorting results using standard sorting options. If you need to sort parent documents by fields in their child documents, you can use a function_score query and sort by the parent document’s score.

In the preceding example, you can sort parent documents (brands) based on the sales_count of their child products. This query multiplies the score by the sales_count field of the child documents and assigns the highest relevance score from the matching child documents to the parent:

GET testindex1/_search
{
  "query": {
    "has_child": {
      "type": "product",
      "query": {
        "function_score": {
          "script_score": {
            "script": "_score * doc['sales_count'].value"
          }
        }
      },
      "score_mode": "max"
    }
  }
}

The response contains the brands sorted by the highest child sales_count:

{
  "took": 6,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 2,
      "relation": "eq"
    },
    "max_score": 300,
    "hits": [
      {
        "_index": "testindex1",
        "_id": "2",
        "_score": 300,
        "_source": {
          "name": "Economy brand",
          "product_to_brand": "brand"
        }
      },
      {
        "_index": "testindex1",
        "_id": "1",
        "_score": 150,
        "_source": {
          "name": "Luxury brand",
          "product_to_brand": "brand"
        }
      }
    ]
  }
}

Next steps

350 characters left

Have a question? .

Want to contribute? or .