copy_to

edit

The copy_to parameter allows you to copy the values of multiple fields into a group field, which can then be queried as a single field.

If you often search multiple fields, you can improve search speeds by using copy_to to search fewer fields. See Search as few fields as possible.

For example, the first_name and last_name fields can be copied to the full_name field as follows:

resp = client.indices.create(
    index="my-index-000001",
    mappings={
        "properties": {
            "first_name": {
                "type": "text",
                "copy_to": "full_name"
            },
            "last_name": {
                "type": "text",
                "copy_to": "full_name"
            },
            "full_name": {
                "type": "text"
            }
        }
    },
)
print(resp)

resp1 = client.index(
    index="my-index-000001",
    id="1",
    document={
        "first_name": "John",
        "last_name": "Smith"
    },
)
print(resp1)

resp2 = client.search(
    index="my-index-000001",
    query={
        "match": {
            "full_name": {
                "query": "John Smith",
                "operator": "and"
            }
        }
    },
)
print(resp2)
response = client.indices.create(
  index: 'my-index-000001',
  body: {
    mappings: {
      properties: {
        first_name: {
          type: 'text',
          copy_to: 'full_name'
        },
        last_name: {
          type: 'text',
          copy_to: 'full_name'
        },
        full_name: {
          type: 'text'
        }
      }
    }
  }
)
puts response

response = client.index(
  index: 'my-index-000001',
  id: 1,
  body: {
    first_name: 'John',
    last_name: 'Smith'
  }
)
puts response

response = client.search(
  index: 'my-index-000001',
  body: {
    query: {
      match: {
        full_name: {
          query: 'John Smith',
          operator: 'and'
        }
      }
    }
  }
)
puts response
const response = await client.indices.create({
  index: "my-index-000001",
  mappings: {
    properties: {
      first_name: {
        type: "text",
        copy_to: "full_name",
      },
      last_name: {
        type: "text",
        copy_to: "full_name",
      },
      full_name: {
        type: "text",
      },
    },
  },
});
console.log(response);

const response1 = await client.index({
  index: "my-index-000001",
  id: 1,
  document: {
    first_name: "John",
    last_name: "Smith",
  },
});
console.log(response1);

const response2 = await client.search({
  index: "my-index-000001",
  query: {
    match: {
      full_name: {
        query: "John Smith",
        operator: "and",
      },
    },
  },
});
console.log(response2);
PUT my-index-000001
{
  "mappings": {
    "properties": {
      "first_name": {
        "type": "text",
        "copy_to": "full_name" 
      },
      "last_name": {
        "type": "text",
        "copy_to": "full_name" 
      },
      "full_name": {
        "type": "text"
      }
    }
  }
}

PUT my-index-000001/_doc/1
{
  "first_name": "John",
  "last_name": "Smith"
}

GET my-index-000001/_search
{
  "query": {
    "match": {
      "full_name": { 
        "query": "John Smith",
        "operator": "and"
      }
    }
  }
}

The values of the first_name and last_name fields are copied to the full_name field.

The first_name and last_name fields can still be queried for the first name and last name respectively, but the full_name field can be queried for both first and last names.

Some important points:

  • It is the field value which is copied, not the terms (which result from the analysis process).
  • The original _source field will not be modified to show the copied values.
  • The same value can be copied to multiple fields, with "copy_to": [ "field_1", "field_2" ]
  • You cannot copy recursively using intermediary fields. The following configuration will not copy data from field_1 to field_3:

    resp = client.indices.create(
        index="bad_example_index",
        mappings={
            "properties": {
                "field_1": {
                    "type": "text",
                    "copy_to": "field_2"
                },
                "field_2": {
                    "type": "text",
                    "copy_to": "field_3"
                },
                "field_3": {
                    "type": "text"
                }
            }
        },
    )
    print(resp)
    const response = await client.indices.create({
      index: "bad_example_index",
      mappings: {
        properties: {
          field_1: {
            type: "text",
            copy_to: "field_2",
          },
          field_2: {
            type: "text",
            copy_to: "field_3",
          },
          field_3: {
            type: "text",
          },
        },
      },
    });
    console.log(response);
    PUT bad_example_index
    {
      "mappings": {
        "properties": {
          "field_1": {
            "type": "text",
            "copy_to": "field_2"
          },
          "field_2": {
            "type": "text",
            "copy_to": "field_3"
          },
          "field_3": {
            "type": "text"
          }
        }
      }
    }

    Instead, copy to multiple fields from the source field:

    resp = client.indices.create(
        index="good_example_index",
        mappings={
            "properties": {
                "field_1": {
                    "type": "text",
                    "copy_to": [
                        "field_2",
                        "field_3"
                    ]
                },
                "field_2": {
                    "type": "text"
                },
                "field_3": {
                    "type": "text"
                }
            }
        },
    )
    print(resp)
    const response = await client.indices.create({
      index: "good_example_index",
      mappings: {
        properties: {
          field_1: {
            type: "text",
            copy_to: ["field_2", "field_3"],
          },
          field_2: {
            type: "text",
          },
          field_3: {
            type: "text",
          },
        },
      },
    });
    console.log(response);
    PUT good_example_index
    {
      "mappings": {
        "properties": {
          "field_1": {
            "type": "text",
            "copy_to": ["field_2", "field_3"]
          },
          "field_2": {
            "type": "text"
          },
          "field_3": {
            "type": "text"
          }
        }
      }
    }

copy_to is not supported for field types where values take the form of objects, e.g. date_range.

Dynamic mapping

edit

Consider the following points when using copy_to with dynamic mappings:

  • If the target field does not exist in the index mappings, the usual dynamic mapping behavior applies. By default, with dynamic set to true, a non-existent target field will be dynamically added to the index mappings.
  • If dynamic is set to false, the target field will not be added to the index mappings, and the value will not be copied.
  • If dynamic is set to strict, copying to a non-existent field will result in an error.

    • If the target field is nested, then copy_to fields must specify the full path to the nested field. Omitting the full path will lead to a strict_dynamic_mapping_exception. Use "copy_to": ["parent_field.child_field"] to correctly target a nested field.

      For example:

      resp = client.indices.create(
          index="test_index",
          mappings={
              "dynamic": "strict",
              "properties": {
                  "description": {
                      "properties": {
                          "notes": {
                              "type": "text",
                              "copy_to": [
                                  "description.notes_raw"
                              ],
                              "analyzer": "standard",
                              "search_analyzer": "standard"
                          },
                          "notes_raw": {
                              "type": "keyword"
                          }
                      }
                  }
              }
          },
      )
      print(resp)
      const response = await client.indices.create({
        index: "test_index",
        mappings: {
          dynamic: "strict",
          properties: {
            description: {
              properties: {
                notes: {
                  type: "text",
                  copy_to: ["description.notes_raw"],
                  analyzer: "standard",
                  search_analyzer: "standard",
                },
                notes_raw: {
                  type: "keyword",
                },
              },
            },
          },
        },
      });
      console.log(response);
      PUT /test_index
      {
        "mappings": {
          "dynamic": "strict",
          "properties": {
            "description": {
              "properties": {
                "notes": {
                  "type": "text",
                  "copy_to": [ "description.notes_raw"], 
                  "analyzer": "standard",
                  "search_analyzer": "standard"
                },
                "notes_raw": {
                  "type": "keyword"
                }
              }
            }
          }
        }
      }

The notes field is copied to the notes_raw field. Targeting notes_raw alone instead of description.notes_raw would lead to a strict_dynamic_mapping_exception.

In this example, notes_raw is not defined at the root of the mapping, but under the description field. Without the fully qualified path, Elasticsearch would interpret the copy_to target as a root-level field, not as a nested field under description.