Multi fields

edit

It is often useful to index the same field in Elasticsearch in different ways, to serve different purposes, for example, mapping a POCO string property as a text datatype for full text search as well as mapping as a keyword datatype for structured search, sorting and aggregations. Another example is mapping a POCO string property to use different analyzers, to serve different full text search needs.

Let’s look at a few examples. for each, we use the following simple POCO

public class Person
{
    public string Name { get; set; }
}

Default mapping for String properties

edit

When using Auto Mapping, the inferred mapping for a string POCO type is a text datatype with multi fields including a keyword sub field

var createIndexResponse = _client.CreateIndex("myindex", c => c
    .Mappings(ms => ms
        .Map<Person>(m => m
            .AutoMap()
        )
    )
);

This results in the following JSON request

{
  "mappings": {
    "person": {
      "properties": {
        "name": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword",
              "ignore_above": 256
            }
          }
        }
      }
    }
  }
}

This is useful because the property can be used for both full text search as well as for structured search, sorting and aggregations

var searchResponse = _client.Search<Person>(s => s
    .Query(q => q
        .Match(m => m
            .Field(f => f.Name)
            .Query("Russ")
        )
    )
    .Sort(ss => ss
        .Descending(f => f.Name.Suffix("keyword")) 
    )
    .Aggregations(a => a
        .Terms("peoples_names", t => t
            .Field(f => f.Name.Suffix("keyword"))
        )
    )
);

Use the keyword subfield on Name

{
  "query": {
    "match": {
      "name": {
        "query": "Russ"
      }
    }
  },
  "sort": [
    {
      "name.keyword": {
        "order": "desc"
      }
    }
  ],
  "aggs": {
    "peoples_names": {
      "terms": {
        "field": "name.keyword"
      }
    }
  }
}

Multi fields do not change the original _source field in Elasticsearch; they affect only how a field is indexed.

New multi fields can be added to existing fields using the Put Mapping API.

Creating Multi fields

edit

Multi fields can be created on a mapping using the .Fields() method within a field mapping

var createIndexResponse = _client.CreateIndex("myindex", c => c
    .Mappings(ms => ms
        .Map<Person>(m => m
            .Properties(p => p
                .Text(t => t
                    .Name(n => n.Name)
                    .Fields(ff => ff
                        .Text(tt => tt
                            .Name("stop") 
                            .Analyzer("stop")
                        )
                        .Text(tt => tt
                            .Name("shingles")
                            .Analyzer("name_shingles") 
                        )
                        .Keyword(k => k
                            .Name("keyword") 
                            .IgnoreAbove(256)
                        )
                    )
                )
            )
        )
    )
);

Use the stop analyzer on this sub field

Use a custom analyzer named "named_shingles" that is configured in the index

Index as not analyzed

{
  "mappings": {
    "person": {
      "properties": {
        "name": {
          "type": "text",
          "fields": {
            "stop": {
              "type": "text",
              "analyzer": "stop"
            },
            "shingles": {
              "type": "text",
              "analyzer": "name_shingles"
            },
            "keyword": {
              "type": "keyword",
              "ignore_above": 256
            }
          }
        }
      }
    }
  }
}