copy_to

copy_to

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:

  1. resp = client.indices.create(
  2. index="my-index-000001",
  3. mappings={
  4. "properties": {
  5. "first_name": {
  6. "type": "text",
  7. "copy_to": "full_name"
  8. },
  9. "last_name": {
  10. "type": "text",
  11. "copy_to": "full_name"
  12. },
  13. "full_name": {
  14. "type": "text"
  15. }
  16. }
  17. },
  18. )
  19. print(resp)
  20. resp1 = client.index(
  21. index="my-index-000001",
  22. id="1",
  23. document={
  24. "first_name": "John",
  25. "last_name": "Smith"
  26. },
  27. )
  28. print(resp1)
  29. resp2 = client.search(
  30. index="my-index-000001",
  31. query={
  32. "match": {
  33. "full_name": {
  34. "query": "John Smith",
  35. "operator": "and"
  36. }
  37. }
  38. },
  39. )
  40. print(resp2)
  1. response = client.indices.create(
  2. index: 'my-index-000001',
  3. body: {
  4. mappings: {
  5. properties: {
  6. first_name: {
  7. type: 'text',
  8. copy_to: 'full_name'
  9. },
  10. last_name: {
  11. type: 'text',
  12. copy_to: 'full_name'
  13. },
  14. full_name: {
  15. type: 'text'
  16. }
  17. }
  18. }
  19. }
  20. )
  21. puts response
  22. response = client.index(
  23. index: 'my-index-000001',
  24. id: 1,
  25. body: {
  26. first_name: 'John',
  27. last_name: 'Smith'
  28. }
  29. )
  30. puts response
  31. response = client.search(
  32. index: 'my-index-000001',
  33. body: {
  34. query: {
  35. match: {
  36. full_name: {
  37. query: 'John Smith',
  38. operator: 'and'
  39. }
  40. }
  41. }
  42. }
  43. )
  44. puts response
  1. const response = await client.indices.create({
  2. index: "my-index-000001",
  3. mappings: {
  4. properties: {
  5. first_name: {
  6. type: "text",
  7. copy_to: "full_name",
  8. },
  9. last_name: {
  10. type: "text",
  11. copy_to: "full_name",
  12. },
  13. full_name: {
  14. type: "text",
  15. },
  16. },
  17. },
  18. });
  19. console.log(response);
  20. const response1 = await client.index({
  21. index: "my-index-000001",
  22. id: 1,
  23. document: {
  24. first_name: "John",
  25. last_name: "Smith",
  26. },
  27. });
  28. console.log(response1);
  29. const response2 = await client.search({
  30. index: "my-index-000001",
  31. query: {
  32. match: {
  33. full_name: {
  34. query: "John Smith",
  35. operator: "and",
  36. },
  37. },
  38. },
  39. });
  40. console.log(response2);
  1. PUT my-index-000001
  2. {
  3. "mappings": {
  4. "properties": {
  5. "first_name": {
  6. "type": "text",
  7. "copy_to": "full_name"
  8. },
  9. "last_name": {
  10. "type": "text",
  11. "copy_to": "full_name"
  12. },
  13. "full_name": {
  14. "type": "text"
  15. }
  16. }
  17. }
  18. }
  19. PUT my-index-000001/_doc/1
  20. {
  21. "first_name": "John",
  22. "last_name": "Smith"
  23. }
  24. GET my-index-000001/_search
  25. {
  26. "query": {
  27. "match": {
  28. "full_name": {
  29. "query": "John Smith",
  30. "operator": "and"
  31. }
  32. }
  33. }
  34. }

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:

    1. resp = client.indices.create(
    2. index="bad_example_index",
    3. mappings={
    4. "properties": {
    5. "field_1": {
    6. "type": "text",
    7. "copy_to": "field_2"
    8. },
    9. "field_2": {
    10. "type": "text",
    11. "copy_to": "field_3"
    12. },
    13. "field_3": {
    14. "type": "text"
    15. }
    16. }
    17. },
    18. )
    19. print(resp)
    1. const response = await client.indices.create({
    2. index: "bad_example_index",
    3. mappings: {
    4. properties: {
    5. field_1: {
    6. type: "text",
    7. copy_to: "field_2",
    8. },
    9. field_2: {
    10. type: "text",
    11. copy_to: "field_3",
    12. },
    13. field_3: {
    14. type: "text",
    15. },
    16. },
    17. },
    18. });
    19. console.log(response);
    1. PUT bad_example_index
    2. {
    3. "mappings": {
    4. "properties": {
    5. "field_1": {
    6. "type": "text",
    7. "copy_to": "field_2"
    8. },
    9. "field_2": {
    10. "type": "text",
    11. "copy_to": "field_3"
    12. },
    13. "field_3": {
    14. "type": "text"
    15. }
    16. }
    17. }
    18. }

    Instead, copy to multiple fields from the source field:

    1. resp = client.indices.create(
    2. index="good_example_index",
    3. mappings={
    4. "properties": {
    5. "field_1": {
    6. "type": "text",
    7. "copy_to": [
    8. "field_2",
    9. "field_3"
    10. ]
    11. },
    12. "field_2": {
    13. "type": "text"
    14. },
    15. "field_3": {
    16. "type": "text"
    17. }
    18. }
    19. },
    20. )
    21. print(resp)
    1. const response = await client.indices.create({
    2. index: "good_example_index",
    3. mappings: {
    4. properties: {
    5. field_1: {
    6. type: "text",
    7. copy_to: ["field_2", "field_3"],
    8. },
    9. field_2: {
    10. type: "text",
    11. },
    12. field_3: {
    13. type: "text",
    14. },
    15. },
    16. },
    17. });
    18. console.log(response);
    1. PUT good_example_index
    2. {
    3. "mappings": {
    4. "properties": {
    5. "field_1": {
    6. "type": "text",
    7. "copy_to": ["field_2", "field_3"]
    8. },
    9. "field_2": {
    10. "type": "text"
    11. },
    12. "field_3": {
    13. "type": "text"
    14. }
    15. }
    16. }
    17. }

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

Dynamic mapping

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:

      1. resp = client.indices.create(
      2. index="test_index",
      3. mappings={
      4. "dynamic": "strict",
      5. "properties": {
      6. "description": {
      7. "properties": {
      8. "notes": {
      9. "type": "text",
      10. "copy_to": [
      11. "description.notes_raw"
      12. ],
      13. "analyzer": "standard",
      14. "search_analyzer": "standard"
      15. },
      16. "notes_raw": {
      17. "type": "keyword"
      18. }
      19. }
      20. }
      21. }
      22. },
      23. )
      24. print(resp)
      1. const response = await client.indices.create({
      2. index: "test_index",
      3. mappings: {
      4. dynamic: "strict",
      5. properties: {
      6. description: {
      7. properties: {
      8. notes: {
      9. type: "text",
      10. copy_to: ["description.notes_raw"],
      11. analyzer: "standard",
      12. search_analyzer: "standard",
      13. },
      14. notes_raw: {
      15. type: "keyword",
      16. },
      17. },
      18. },
      19. },
      20. },
      21. });
      22. console.log(response);
      1. PUT /test_index
      2. {
      3. "mappings": {
      4. "dynamic": "strict",
      5. "properties": {
      6. "description": {
      7. "properties": {
      8. "notes": {
      9. "type": "text",
      10. "copy_to": [ "description.notes_raw"],
      11. "analyzer": "standard",
      12. "search_analyzer": "standard"
      13. },
      14. "notes_raw": {
      15. "type": "keyword"
      16. }
      17. }
      18. }
      19. }
      20. }
      21. }

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.