Inner Hits Usage

edit

The parent/child and nested features allow the return of documents that have matches in a different scope. In the parent/child case, parent document are returned based on matches in child documents or child document are returned based on matches in parent documents. In the nested case, documents are returned based on matches in nested inner objects.

In both cases, the actual matches in the different scopes that caused a document to be returned is hidden. In many cases, it’s very useful to know which inner nested objects (in the case of nested) or children/parent documents (in the case of parent/child) caused certain information to be returned. The inner hits feature can be used for this. This feature returns per search hit in the search response additional nested hits that caused a search hit to match in a different scope.

Inner hits can be used by defining an inner_hits definition on a nested, has_child or has_parent query and filter.

See the Elasticsearch documentation on Inner hits for more detail.

Query Inner Hits

edit

Fluent DSL example

edit
s => s
.Index(Index)
.Query(q =>
    q.HasChild<Prince>(hc => hc
        .Query(hcq => hcq.Match(m => m.Field(p => p.FullTextField).Query("default")))
        .InnerHits(ih => ih
            .DocValueFields(f => f.Field(p => p.Name))
            .Name("princes")
            .Highlight(h => h.Fields(f => f.Field(p => p.FullTextField)))
            .IgnoreUnmapped(false)
            .Version()
        )
    ) || q.Nested(n => n
        .Path(p => p.Foes)
        .Query(nq => nq.MatchAll())
        .InnerHits(i => i.Version())
    )
)
.Version()

Object Initializer syntax example

edit
new SearchRequest<King>(Index)
{
    Query = new HasChildQuery
    {
        Type = typeof(Prince),
        Query = new MatchQuery { Field = Field<Prince>(p => p.FullTextField), Query = "default" },
        InnerHits = new InnerHits
        {
            Name = "princes",
            DocValueFields = Field<Prince>(p => p.Name),
            Highlight = Highlight.Field(Field<Prince>(p => p.FullTextField)),
            IgnoreUnmapped = false,
            Version = true
        }
    } || new NestedQuery
    {
        Path = Field<King>(p => p.Foes),
        Query = new MatchAllQuery(),
        InnerHits = new InnerHits()
        {
            Version = true
        }
    },
    Version = true
}

Example json output.

{
  "query": {
    "bool": {
      "should": [
        {
          "has_child": {
            "type": "prince",
            "query": {
              "match": {
                "fullTextField": {
                  "query": "default"
                }
              }
            },
            "inner_hits": {
              "name": "princes",
              "docvalue_fields": [
                "name"
              ],
              "highlight": {
                "fields": {
                  "fullTextField": {}
                }
              },
              "ignore_unmapped": false,
              "version": true
            }
          }
        },
        {
          "nested": {
            "query": {
              "match_all": {}
            },
            "path": "foes",
            "inner_hits": {
              "version": true
            }
          }
        }
      ]
    }
  },
  "version": true
}

Handling Responses

edit
response.Hits.Should().NotBeEmpty();
foreach (var hit in response.Hits)
{
    hit.Id.Should().NotBeNullOrEmpty();
    hit.Index.Should().NotBeNullOrEmpty();
    hit.Version.Should().Be(1);

    var princes = hit.InnerHits["princes"].Documents<Prince>();
    princes.Should().NotBeEmpty();
    foreach (var princeHit in hit.InnerHits["princes"].Hits.Hits)
    {
        var highlights = princeHit.Highlight;
        highlights.Should().NotBeNull("princes should have highlights");
        highlights.Should().ContainKey("fullTextField", "we are highlighting this field");
        var hl = highlights["fullTextField"];
        hl.Should()
            .NotBeEmpty("all docs have the same text so should all highlight")
            .And.Contain(s => s.Contains("<em>default</em>"), "default to be highlighted as its part of the query");

        princeHit.Fields.Should().NotBeNull("all princes have a keyword name so fields should be returned");
        var docValueName = princeHit.Fields.ValueOf<Prince, string>(p => p.Name);
        docValueName.Should().NotBeNullOrWhiteSpace("value of name on Fields");

        princeHit.Version.Should().Be(1);
    }

    var foes = hit.InnerHits["foes"].Documents<King>();
    foes.Should().NotBeEmpty();
}