From cb91e91e85676600a37cc40e0ec756a62d324d61 Mon Sep 17 00:00:00 2001 From: Fanit Kolchina Date: Thu, 9 Jul 2026 14:46:11 -0400 Subject: [PATCH] Update reverse nested aggregation documentation Signed-off-by: Fanit Kolchina --- _aggregations/bucket/reverse-nested.md | 347 ++++++++++++++++++++++--- 1 file changed, 305 insertions(+), 42 deletions(-) diff --git a/_aggregations/bucket/reverse-nested.md b/_aggregations/bucket/reverse-nested.md index 3757ec7ab78..0a036be62f9 100644 --- a/_aggregations/bucket/reverse-nested.md +++ b/_aggregations/bucket/reverse-nested.md @@ -9,34 +9,81 @@ redirect_from: # Reverse nested aggregations -You can aggregate values from nested documents to their parent; this aggregation is called `reverse_nested`. -You can use `reverse_nested` to aggregate a field from the parent document after grouping by the field from the nested object. The `reverse_nested` aggregation "joins back" the root page and gets the `load_time` for each for your variations. +The `reverse_nested` aggregation allows you to aggregate on parent document fields from within a [`nested` aggregation]({{site.url}}{{site.baseurl}}/aggregations/bucket/nested/) context. When you group by a nested field, the aggregation context shifts to the nested documents. The `reverse_nested` aggregation breaks out of that nested context and joins back to the parent (or root) document, making parent fields accessible for further subaggregations. -The `reverse_nested` aggregation is a sub-aggregation inside a nested aggregation. It accepts a single option named `path`. This option defines how many steps backwards in the document hierarchy OpenSearch takes to calculate the aggregations. +The `reverse_nested` aggregation must be defined inside a `nested` aggregation. +{: .note} + +## Parameters + +The `reverse_nested` aggregation takes the following parameters. + +| Parameter | Required/Optional | Data type | Description | +| :--- | :--- | :--- | :--- | +| `path` | Optional | String | The nested object path to join back to. Default is empty (joins back to the root document). For multi-level nesting, specify an intermediate nested path to join to that level instead of the root. | + +## Example setup + +Create an index with issues containing nested comments: ```json -GET logs/_search +PUT /issues { - "query": { - "match": { "response": "200" } - }, + "mappings": { + "properties": { + "title": { "type": "text" }, + "tags": { "type": "keyword" }, + "comments": { + "type": "nested", + "properties": { + "username": { "type": "keyword" }, + "comment": { "type": "text" } + } + } + } + } +} +``` +{% include copy-curl.html %} + +Index some documents: + +```json +POST /issues/_bulk?refresh=true +{"index":{"_id":"1"}} +{"title":"Add dark mode to settings page","tags":["ui","enhancement"],"comments":[{"username":"alice","comment":"Would love this feature"},{"username":"bob","comment":"Should support system preference detection"}]} +{"index":{"_id":"2"}} +{"title":"Export report as PDF","tags":["export","enhancement"],"comments":[{"username":"alice","comment":"CSV export would also be useful"},{"username":"carol","comment":"Added to the roadmap"}]} +{"index":{"_id":"3"}} +{"title":"Improve mobile navigation","tags":["ui","mobile"],"comments":[{"username":"bob","comment":"Hamburger menu would work well"},{"username":"carol","comment":"Agree, especially for tablets"}]} +``` +{% include copy-curl.html %} + +## Example + +The following example finds the most active commenters and then uses `reverse_nested` to determine the issue tags with which each commenter is most involved. Without `reverse_nested`, the `tags` field would be inaccessible because the aggregation context is inside the nested `comments` objects: + +```json +GET /issues/_search +{ + "size": 0, "aggs": { - "pages": { + "comments": { "nested": { - "path": "pages" + "path": "comments" }, "aggs": { - "top_pages_per_load_time": { + "top_commenters": { "terms": { - "field": "pages.load_time" + "field": "comments.username" }, "aggs": { - "comment_to_logs": { + "back_to_issue": { "reverse_nested": {}, "aggs": { - "min_load_time": { - "min": { - "field": "pages.load_time" + "top_tags": { + "terms": { + "field": "tags" } } } @@ -50,42 +97,258 @@ GET logs/_search ``` {% include copy-curl.html %} -#### Example response +The response shows that Bob's comments appear on issues tagged with "ui" (2 issues), "enhancement" (1), and "mobile" (1): ```json -... -"aggregations" : { - "pages" : { - "doc_count" : 2, - "top_pages_per_load_time" : { - "doc_count_error_upper_bound" : 0, - "sum_other_doc_count" : 0, - "buckets" : [ - { - "key" : 200.0, - "doc_count" : 1, - "comment_to_logs" : { - "doc_count" : 1, - "min_load_time" : { - "value" : null +{ + ... + "aggregations": { + "comments": { + "doc_count": 6, + "top_commenters": { + "doc_count_error_upper_bound": 0, + "sum_other_doc_count": 0, + "buckets": [ + { + "key": "alice", + "doc_count": 2, + "back_to_issue": { + "doc_count": 2, + "top_tags": { + "doc_count_error_upper_bound": 0, + "sum_other_doc_count": 0, + "buckets": [ + { + "key": "enhancement", + "doc_count": 2 + }, + { + "key": "export", + "doc_count": 1 + }, + { + "key": "ui", + "doc_count": 1 + } + ] + } + } + }, + { + "key": "bob", + "doc_count": 2, + "back_to_issue": { + "doc_count": 2, + "top_tags": { + "doc_count_error_upper_bound": 0, + "sum_other_doc_count": 0, + "buckets": [ + { + "key": "ui", + "doc_count": 2 + }, + { + "key": "enhancement", + "doc_count": 1 + }, + { + "key": "mobile", + "doc_count": 1 + } + ] + } + } + }, + { + "key": "carol", + "doc_count": 2, + "back_to_issue": { + "doc_count": 2, + "top_tags": { + "doc_count_error_upper_bound": 0, + "sum_other_doc_count": 0, + "buckets": [ + { + "key": "enhancement", + "doc_count": 1 + }, + { + "key": "export", + "doc_count": 1 + }, + { + "key": "mobile", + "doc_count": 1 + }, + { + "key": "ui", + "doc_count": 1 + } + ] + } } } - }, - { - "key" : 500.0, - "doc_count" : 1, - "comment_to_logs" : { - "doc_count" : 1, - "min_load_time" : { - "value" : null + ] + } + } + } +} +``` + +## Example: Using the path parameter with multi-level nesting + +When documents contain nested objects within nested objects, you can use the `path` parameter to join back to an intermediate level rather than the root. The following example uses a forum-style structure where posts contain nested comments, and each comment contains nested replies: + +```json +PUT /forum_posts +{ + "mappings": { + "properties": { + "title": { "type": "keyword" }, + "comments": { + "type": "nested", + "properties": { + "author": { "type": "keyword" }, + "replies": { + "type": "nested", + "properties": { + "author": { "type": "keyword" } } } } - ] + } } } - } } ``` +{% include copy-curl.html %} + +Index a post with comments and replies: + +```json +POST /forum_posts/_doc/1?refresh=true +{ + "title": "Post A", + "comments": [ + { "author": "alice", "replies": [{"author": "bob"}, {"author": "carol"}] }, + { "author": "bob", "replies": [{"author": "alice"}] } + ] +} +``` +{% include copy-curl.html %} + +The following aggregation groups by reply author, then uses `reverse_nested` with `"path": "comments"` to join back to the comment level (not the root) and find which comment authors each person replied to: + +```json +GET /forum_posts/_search +{ + "size": 0, + "aggs": { + "comments": { + "nested": { "path": "comments" }, + "aggs": { + "replies": { + "nested": { "path": "comments.replies" }, + "aggs": { + "reply_authors": { + "terms": { "field": "comments.replies.author" }, + "aggs": { + "back_to_comment": { + "reverse_nested": { "path": "comments" }, + "aggs": { + "comment_authors": { + "terms": { "field": "comments.author" } + } + } + } + } + } + } + } + } + } + } +} +``` +{% include copy-curl.html %} + +The response shows that Bob replied to Alice's comment, and Carol also replied to Alice's comment: + +```json +{ + ... + "aggregations": { + "comments": { + "doc_count": 2, + "replies": { + "doc_count": 3, + "reply_authors": { + "doc_count_error_upper_bound": 0, + "sum_other_doc_count": 0, + "buckets": [ + { + "key": "alice", + "doc_count": 1, + "back_to_comment": { + "doc_count": 1, + "comment_authors": { + "doc_count_error_upper_bound": 0, + "sum_other_doc_count": 0, + "buckets": [ + { + "key": "bob", + "doc_count": 1 + } + ] + } + } + }, + { + "key": "bob", + "doc_count": 1, + "back_to_comment": { + "doc_count": 1, + "comment_authors": { + "doc_count_error_upper_bound": 0, + "sum_other_doc_count": 0, + "buckets": [ + { + "key": "alice", + "doc_count": 1 + } + ] + } + } + }, + { + "key": "carol", + "doc_count": 1, + "back_to_comment": { + "doc_count": 1, + "comment_authors": { + "doc_count_error_upper_bound": 0, + "sum_other_doc_count": 0, + "buckets": [ + { + "key": "alice", + "doc_count": 1 + } + ] + } + } + } + ] + } + } + } + } +} +``` + +## Response body fields + +The following table lists the response body fields. -The response shows the logs index has one page with a `load_time` of 200 and one with a `load_time` of 500. \ No newline at end of file +| Field | Data type | Description | +| :--- | :--- | :--- | +| `doc_count` | Integer | The number of parent documents that the aggregation joined back to. This count reflects distinct parent documents, not nested documents. |