fieldsedit

It is often useful to index the same field in different ways for different purposes. This is the purpose of multi-fields. For instance, a string field could be mapped as a text field for full-text search, and as a keyword field for sorting or aggregations:

response = client.indices.create(
  index: 'my-index-000001',
  body: {
    mappings: {
      properties: {
        city: {
          type: 'text',
          fields: {
            raw: {
              type: 'keyword'
            }
          }
        }
      }
    }
  }
)
puts response

response = client.indices.create(
  index: 'my-index-000001',
  id: 1,
  body: {
    city: 'New York'
  }
)
puts response

response = client.indices.create(
  index: 'my-index-000001',
  id: 2,
  body: {
    city: 'York'
  }
)
puts response

response = client.indices.create(
  index: 'my-index-000001',
  body: {
    query: {
      match: {
        city: 'york'
      }
    },
    sort: {
      "city.raw": 'asc'
    },
    aggregations: {
      "Cities": {
        terms: {
          field: 'city.raw'
        }
      }
    }
  }
)
puts response
{
	res, err := es.Indices.Create(
		"my-index-000001",
		es.Indices.Create.WithBody(strings.NewReader(`{
	  "mappings": {
	    "properties": {
	      "city": {
	        "type": "text",
	        "fields": {
	          "raw": {
	            "type": "keyword"
	          }
	        }
	      }
	    }
	  }
	}`)),
	)
	fmt.Println(res, err)
}

{
	res, err := es.Index(
		"my-index-000001",
		strings.NewReader(`{
	  "city": "New York"
	}`),
		es.Index.WithDocumentID("1"),
		es.Index.WithPretty(),
	)
	fmt.Println(res, err)
}

{
	res, err := es.Index(
		"my-index-000001",
		strings.NewReader(`{
	  "city": "York"
	}`),
		es.Index.WithDocumentID("2"),
		es.Index.WithPretty(),
	)
	fmt.Println(res, err)
}

{
	res, err := es.Search(
		es.Search.WithIndex("my-index-000001"),
		es.Search.WithBody(strings.NewReader(`{
	  "query": {
	    "match": {
	      "city": "york"
	    }
	  },
	  "sort": {
	    "city.raw": "asc"
	  },
	  "aggs": {
	    "Cities": {
	      "terms": {
	        "field": "city.raw"
	      }
	    }
	  }
	}`)),
		es.Search.WithPretty(),
	)
	fmt.Println(res, err)
}
PUT my-index-000001
{
  "mappings": {
    "properties": {
      "city": {
        "type": "text",
        "fields": {
          "raw": { 
            "type":  "keyword"
          }
        }
      }
    }
  }
}

PUT my-index-000001/_doc/1
{
  "city": "New York"
}

PUT my-index-000001/_doc/2
{
  "city": "York"
}

GET my-index-000001/_search
{
  "query": {
    "match": {
      "city": "york" 
    }
  },
  "sort": {
    "city.raw": "asc" 
  },
  "aggs": {
    "Cities": {
      "terms": {
        "field": "city.raw" 
      }
    }
  }
}

The city.raw field is a keyword version of the city field.

The city field can be used for full text search.

The city.raw field can be used for sorting and aggregations

You can add multi-fields to an existing field using the update mapping API.

If an index (or data stream) contains documents when you add a multi-field, those documents will not have values for the new multi-field. You can populate the new multi-field with the update by query API.

A multi-field mapping is completely separate from the parent field’s mapping. A multi-field doesn’t inherit any mapping options from its parent field. Multi-fields don’t change the original _source field.

Multi-fields with multiple analyzersedit

Another use case of multi-fields is to analyze the same field in different ways for better relevance. For instance we could index a field with the standard analyzer which breaks text up into words, and again with the english analyzer which stems words into their root form:

response = client.indices.create(
  index: 'my-index-000001',
  body: {
    mappings: {
      properties: {
        text: {
          type: 'text',
          fields: {
            english: {
              type: 'text',
              analyzer: 'english'
            }
          }
        }
      }
    }
  }
)
puts response

response = client.indices.create(
  index: 'my-index-000001',
  id: 1,
  body: {
    text: 'quick brown fox'
  }
)
puts response

response = client.indices.create(
  index: 'my-index-000001',
  id: 2,
  body: {
    text: 'quick brown foxes'
  }
)
puts response

response = client.indices.create(
  index: 'my-index-000001',
  body: {
    query: {
      multi_match: {
        query: 'quick brown foxes',
        fields: [
          'text',
          'text.english'
        ],
        type: 'most_fields'
      }
    }
  }
)
puts response
{
	res, err := es.Indices.Create(
		"my-index-000001",
		es.Indices.Create.WithBody(strings.NewReader(`{
	  "mappings": {
	    "properties": {
	      "text": {
	        "type": "text",
	        "fields": {
	          "english": {
	            "type": "text",
	            "analyzer": "english"
	          }
	        }
	      }
	    }
	  }
	}`)),
	)
	fmt.Println(res, err)
}

{
	res, err := es.Index(
		"my-index-000001",
		strings.NewReader(`{
	  "text": "quick brown fox"
	} `),
		es.Index.WithDocumentID("1"),
		es.Index.WithPretty(),
	)
	fmt.Println(res, err)
}

{
	res, err := es.Index(
		"my-index-000001",
		strings.NewReader(`{
	  "text": "quick brown foxes"
	} `),
		es.Index.WithDocumentID("2"),
		es.Index.WithPretty(),
	)
	fmt.Println(res, err)
}

{
	res, err := es.Search(
		es.Search.WithIndex("my-index-000001"),
		es.Search.WithBody(strings.NewReader(`{
	  "query": {
	    "multi_match": {
	      "query": "quick brown foxes",
	      "fields": [
	        "text",
	        "text.english"
	      ],
	      "type": "most_fields"
	    }
	  }
	}`)),
		es.Search.WithPretty(),
	)
	fmt.Println(res, err)
}
PUT my-index-000001
{
  "mappings": {
    "properties": {
      "text": { 
        "type": "text",
        "fields": {
          "english": { 
            "type":     "text",
            "analyzer": "english"
          }
        }
      }
    }
  }
}

PUT my-index-000001/_doc/1
{ "text": "quick brown fox" } 

PUT my-index-000001/_doc/2
{ "text": "quick brown foxes" } 

GET my-index-000001/_search
{
  "query": {
    "multi_match": {
      "query": "quick brown foxes",
      "fields": [ 
        "text",
        "text.english"
      ],
      "type": "most_fields" 
    }
  }
}

The text field uses the standard analyzer.

The text.english field uses the english analyzer.

Index two documents, one with fox and the other with foxes.

Query both the text and text.english fields and combine the scores.

The text field contains the term fox in the first document and foxes in the second document. The text.english field contains fox for both documents, because foxes is stemmed to fox.

The query string is also analyzed by the standard analyzer for the text field, and by the english analyzer for the text.english field. The stemmed field allows a query for foxes to also match the document containing just fox. This allows us to match as many documents as possible. By also querying the unstemmed text field, we improve the relevance score of the document which matches foxes exactly.