Elasticsearch:从搜索中获取选定的字段

在实际的搜索返回数据中,我们经常会用选择地返回所需要的字段或部分的 source。这在某些情况下非常有用,因为对于大规模的数据来说,返回的数据大下直接影响网路带宽的使用以及内存的使用。默认情况下,搜索响应中的每个匹配都包含文档 _source,这是在为文档建立索引时提供的整个 JSON 对象。 要检索搜索响应中的特定字段,可以使用 fields 参数:

POST my-index-000001/_search
{
  "query": {
    "match": {
      "message": "foo"
    }
  },
  "fields": ["user.id", "@timestamp"],
  "_source": false
}

fields 参数同时查询文档的 _source 和索引 mapping,以加载和返回值。因为它利用了 mapping,所以与直接引用 _source 相比,字段具有一些优点:它接受 multi-fields 和字段别名,并且还以一致的方式设置诸如日期之类的字段值的格式。

文档的 _source 存储在 Lucene 中的单个字段中。因此,即使仅请求少量字段,也必须加载并解析整个 _source 对象。为避免此限制,你可以尝试另一种加载字段的方法:

  • 使用 docvalue_fields 参数获取选定字段的值。当返回相当少量的支持 doc 值的字段(例如关键字和日期)时,这是一个不错的选择。
  • 使用 stored_fields 参数获取特定存储字段(使用 store 映射选项的字段)的值。

你还可以使用 script_field 参数通过脚本来转换响应中的字段值。

你可以在以下各节中找到有关这些方法的更多详细信息:

 

Fields

fields 参数允许检索搜索响应中的文档字段列表。 它同时查阅文档 _source 和索引 mapping,以符合其映射类型的标准化方式返回每个值。 默认情况下,日期字段是根据其 mapping 中的日期格式参数设置格式的。 你还可以使用 fields 参数来检索运行时字段值。我们使用如下的文档作为例子:

PUT developers/_doc/1
{
  "name": {
    "firstname": "san",
    "secondname": "zhang"
  },
  "age": 20,
  "city": "Beijing",
  "skills": ["Java", "C++"],
  "DOB": "1989-06-04"
}

PUT developers/_doc/2
{
  "name": {
    "firstname": "si",
    "secondname": "li"
  },
  "age": 30,
  "city": "Shanghai",
  "skills": ["Ruby", "C++"],
  "DOB": "1999-07-08"
}

在上面,我们创建了一个叫做 developers 的索引。其中的 DOB 字段指的是 date of birth。上面的 developer 的 mapping 是:

GET developers/_mapping
{
  "developers" : {
    "mappings" : {
      "properties" : {
        "DOB" : {
          "type" : "date"
        },
        "age" : {
          "type" : "long"
        },
        "city" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "name" : {
          "properties" : {
            "firstname" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            },
            "secondname" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            }
          }
        },
        "nianling" : {
          "type" : "alias",
          "path" : "age"
        },
        "skills" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        }
      }
    }
  }
}

以下搜索请求使用 fields 参数来检索 city 字段,以 skil*开头的所有字段, name.firstname 以及 DOB 字段的值:

GET developers/_search
{
  "query": {
    "match": {
      "city": "Beijing"
    }
  },
  "fields": [
    "name.firstname",
    "ski*",
    "nianling",
    "city",
    {
      "field": "DOB",
      "format": "epoch_millis"
    }
  ]
}

在上面,我也使用了一个 alias 字段 nianling。上面的显示结果为:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.6931471,
    "hits" : [
      {
        "_index" : "developers",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.6931471,
        "_source" : {
          "name" : {
            "firstname" : "san",
            "secondname" : "zhang"
          },
          "age" : 20,
          "city" : "Beijing",
          "skills" : [
            "Java",
            "C++"
          ],
          "DOB" : "1989-06-04"
        },
        "fields" : {
          "skills" : [
            "Java",
            "C++"
          ],
          "name.firstname" : [
            "san"
          ],
          "city" : [
            "Beijing"
          ],
          "DOB" : [
            "612921600000"
          ],
          "nianling" : [
            20
          ],
          "skills.keyword" : [
            "Java",
            "C++"
          ]
        }
      }
    ]
  }
}

如果我们不想要 _source,我们也可以直接使用如下的查询:

GET developers/_search
{
  "query": {
    "match": {
      "city": "Beijing"
    }
  },
  "fields": [
    "name.firstname",
    "ski*",
    "nianling",
    "city",
    {
      "field": "DOB",
      "format": "epoch_millis"
    }
  ],
  "_source": false
}

上面显示的结果为:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.6931471,
    "hits" : [
      {
        "_index" : "developers",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.6931471,
        "fields" : {
          "skills" : [
            "Java",
            "C++"
          ],
          "name.firstname" : [
            "san"
          ],
          "city" : [
            "Beijing"
          ],
          "DOB" : [
            "612921600000"
          ],
          "nianling" : [
            20
          ],
          "skills.keyword" : [
            "Java",
            "C++"
          ]
        }
      }
    ]
  }
}

在上面我们看到 DOB 是以我们想要的格式进行显示的。我们也可以使用 ski* 来显示 multi-fields 字段 skills 以及 skill.keyword。fields 不允许返回整个对象。它只能返回 leaf field。

在这里特别指出的是,我们可以直接可以通过  source filtering 的方法来返回 _source 中的部分字段:

GET developers/_search
{
  "query": {
    "match": {
      "city": "Beijing"
    }
  },
  "_source": ["city", "age", "name"]
}

上面搜索的返回结果为:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.6931471,
    "hits" : [
      {
        "_index" : "developers",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.6931471,
        "_source" : {
          "city" : "Beijing",
          "name" : {
            "secondname" : "zhang",
            "firstname" : "san"
          },
          "age" : 20
        }
      }
    ]
  }
}

fields 参数处理字段类型,例如字段 aliasesconstant_keyword,其值并不总是出现在 _source 中。 还应其他映射选项也被考虑,包括 ignore_aboveignore_malformednull_value

注意:即使 _source 中只有一个值,fields 响应也总是为每个字段返回一个值数组。 这是因为 Elasticsearch 没有专用的数组类型,并且任何字段都可以包含多个值。 fields 参数也不能保证以特定顺序返回数组值。 有关更多背景信息,请参见映射文档中的 array

 

处理 nested 字段

nested 字段的字段响应与常规对象字段的字段响应略有不同。 常规对象字段内的 leaf value 作为扁平列表返回时,nested 字段内的值被分组以维护原始 nested 数组内每个对象的独立性。 对于 nested 字段数组内的每个条目,除非父 nested 对象内还有其他 nested 字段,否则值将再次作为一个扁平列表返回,在这种情况下,将对较深的 nested 字段再次重复相同的过程。

给定以下 mapping,其中 user 是一个 nested 字段,在索引以下文档并检索到 user 字段下的所有字段之后:

PUT my-index-000001
{
  "mappings": {
    "properties": {
      "group" : { "type" : "keyword" },
      "user": {
        "type": "nested",
        "properties": {
          "first" : { "type" : "keyword" },
          "last" : { "type" : "keyword" }
        }
      }
    }
  }
}

PUT my-index-000001/_doc/1?refresh=true
{
  "group" : "fans",
  "user" : [
    {
      "first" : "John",
      "last" :  "Smith"
    },
    {
      "first" : "Alice",
      "last" :  "White"
    }
  ]
}

POST my-index-000001/_search
{
  "fields": ["*"],
  "_source": false
}

响应会将 first 和 last 分组,而不是将它们作为扁平列表返回。

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "my-index-000001",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "fields" : {
          "user" : [
            {
              "last" : [
                "Smith"
              ],
              "first" : [
                "John"
              ]
            },
            {
              "last" : [
                "White"
              ],
              "first" : [
                "Alice"
              ]
            }
          ],
          "group" : [
            "fans"
          ]
        }
      }
    ]
  }
}

无论用于搜索它们的模式如何,nested 字段都将按其 nested 路径进行分组。 例如,在上面的示例中仅查询 user.first 字段:

POST my-index-000001/_search
{
  "fields": ["user.first"],
  "_source": false
}

将仅返回用户的 first 字段,但仍保持 nested 用户数组的结构:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "my-index-000001",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "fields" : {
          "user" : [
            {
              "first" : [
                "John"
              ]
            },
            {
              "first" : [
                "Alice"
              ]
            }
          ]
        }
      }
    ]
  }
}

但是,当字段模式直接定位嵌套的 user 字段时,由于该模式与任何 leaf field 都不匹配,因此不会返回任何值。就像上面所说的那样,它不能返回任何 Object。

 

检索未映射的字段

默认情况下,fields 参数仅返回映射字段的值。 但是,Elasticsearch 允许将未映射的字段存储在 _source 中,例如,通过将 “动态字段映射” 设置为 false 或使用具有 enable: false的对象字段,从而禁用对其内容的解析和索引编制。

可以使用字段部分中的 include_unmapped 选项从 _source 检索此类对象中的字段:

PUT my-index-000002
{
  "mappings": {
    "enabled": false 
  }
}

在上面,我们对索引 my-index-000002 禁止所有的 mapping。它也意味着我们不能对它做任何的搜索:

GET my-index-000002/_search
{
  "query": {
    "match": {
      "user_id": "kimchy"
    }
  }
}

上面的搜索将不会返回任何的数值。

PUT my-index-000002/_doc/1?refresh=true
{
  "user_id": "kimchy",
  "session_data": {
     "object": {
       "some_field": "some_value"
     }
   }
}

POST my-index-000002/_search
{
  "fields": [
    "user_id",
    {
      "field": "session_data.object.*",
      "include_unmapped" : true 
    }
  ],
  "_source": false
}

即使未映射字段,响应也将在 session_data.object.* 路径下包含字段结果。由于未映射但 user_id 字段模式的 include_unmapped 标志未设置为 true,因此响应中将不包含 user_id。上面的搜索结果为:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "my-index-000002",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "fields" : {
          "session_data.object.some_field" : [
            "some_value"
          ]
        }
      }
    ]
  }
}

 

Doc value 字段

你可以使用 docvalue_fields 参数返回搜索响应中一个或多个字段的 doc value。

Doc value 存储与 _source 相同的值,但采用基于磁盘的基于列的结构,该结构针对排序和聚合进行了优化。 由于每个字段都是单独存储的,因此 Elasticsearch 仅读取请求的字段值,并且可以避免加载整个文档 _source。

默认情况下,将为支持的字段存储文档值。 但是,text 或 text_annotated 字段不支持  doc  value。

PUT developers/_doc/1
{
  "name": {
    "firstname": "san",
    "secondname": "zhang"
  },
  "age": 20,
  "city": "Beijing",
  "skills": ["Java", "C++"],
  "DOB": "1989-06-04"
}

PUT developers/_doc/2
{
  "name": {
    "firstname": "si",
    "secondname": "li"
  },
  "age": 30,
  "city": "Shanghai",
  "skills": ["Ruby", "C++"],
  "DOB": "1999-07-08"
}

以下搜索请求使用 docvalue_fields 参数检索 age 字段,以 ski*开头的所有字段以及 DOB 字段的 doc value:

GET developers/_search
{
  "query": {
    "match": {
      "city": "Beijing"
    }
  },
  "docvalue_fields": [
    "age",
    "ski*.keyword",
    {
      "field": "date",
      "format": "epoch_millis"
    }
  ]
}

提示:你不能使用 docvalue_fields 参数来检索 nested 对象的 doc value。 如果指定 nested 对象,则搜索将为该字段返回一个空数组([])。 要访问 nested 字段,请使用inner_hits 参数的 docvalue_fields 属性。

上面的搜索结果为:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.6931471,
    "hits" : [
      {
        "_index" : "developers",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.6931471,
        "_source" : {
          "name" : {
            "firstname" : "san",
            "secondname" : "zhang"
          },
          "age" : 20,
          "city" : "Beijing",
          "skills" : [
            "Java",
            "C++"
          ],
          "DOB" : "1989-06-04"
        },
        "fields" : {
          "age" : [
            20
          ],
          "skills.keyword" : [
            "C++",
            "Java"
          ]
        }
      }
    ]
  }
}

Stored fields

也可以使用 store 映射选项来存储单个字段的值。 你可以使用 stored_fields 参数将这些存储的值包括在搜索响应中。

警告:stored_fields 参数用于显式标记为存储在映射中的字段,该字段默认情况下处于关闭状态,通常不建议使用。 而是使用源过滤来选择要返回的原始源文档的子集。

允许有选择地为 search hit 表示的每个文档加载特定的 store 字段。

PUT my_index
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "store": true 
      },
      "date": {
        "type": "date",
        "store": true 
      },
      "content": {
        "type": "text"
      },
      "city": {
        "type": "keyword"
      }
    }
  }
}

PUT my_index/_doc/1
{
  "title": "Some short title",
  "date": "2015-01-01",
  "content": "A very long content field...",
  "city": "Beijing"
}

我们可以通过如下的方式来进行搜索:

GET my_index/_search
{
  "stored_fields": [
    "title",
    "date"
  ],
  "query": {
    "term": {
      "city": "Beijing"
    }
  }
}

上面的搜索结果为:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.2876821,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.2876821,
        "fields" : {
          "date" : [
            "2015-01-01T00:00:00.000Z"
          ],
          "title" : [
            "Some short title"
          ]
        }
      }
    ]
  }
}

在上面,我们可以使用 * 来返回所有的 stored fields:

GET my_index/_search
{
  "stored_fields": "*",
  "query": {
    "term": {
      "city": "Beijing"
    }
  }
}

空数组将导致每次匹配仅返回 _id 和 _type,例如:

GET my_index/_search
{
  "stored_fields": [],
  "query": {
    "term": {
      "city": "Beijing"
    }
  }
}

上面的搜索将返回:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.2876821,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.2876821
      }
    ]
  }
}

如果未设定为 store 请求的字段(将存储映射设置为false),则将忽略它们。

从文档本身获取的 store 字段值始终以数组形式返回。 相反,诸如 _routing 之类的元数据字段从不作为数组返回。

另外,只能通过 stored_fields 选项返回 leaf field。 如果指定了对象字段,它将被忽略。

注意:就其本身而言,stored_fields 不能用于加载 nested 对象中的字段 - 如果字段在其路径中包含 nested 对象,则不会为该存储字段返回任何数据。 要访问 nested 字段,必须在 inner_hits 块内使用 stored_fields。

禁止 stored fields

要完全禁用 store 字段(和元数据字段),请使用:_none_ :

GET my_index/_search
{
  "stored_fields": "_none_",
  "query": {
    "term": {
      "city": "Beijing"
    }
  }
}

 

Soure filtering

你可以使用 _source 参数选择返回源的哪些字段。 这称为源过滤。

以下搜索 API 请求将 _source 请求主体参数设置为 false。 该文档来源不包含在响应中。

GET developers/_search
{
  "_source": false,
  "query": {
    "match": {
      "city": "Beijing"
    }
  }
}

要仅返回 source 字段的子集,请在 _source参 数中指定通配符(*)模式。 以下搜索 API 请求仅返回 ski 为开头的所有字段以及 name.fir 为开头的所有字段:

GET developers/_search
{
  "_source": ["ski*", "name.fir*"],
  "query": {
    "match": {
      "city": "Beijing"
    }
  }
}
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.6931471,
    "hits" : [
      {
        "_index" : "developers",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.6931471,
        "_source" : {
          "skills" : [
            "Java",
            "C++"
          ],
          "name" : {
            "firstname" : "san"
          }
        }
      }
    ]
  }
}

为了更好地控制,你可以在 _source 参数中指定一个includes 和  excludes 模式的数组的对象。

如果指定了 includes 属性,则仅返回与其模式之一匹配的源字段。 你可以使用 excludes 属性从此子集中排除字段。

如果未指定 include 属性,则返回整个文档源,不包括与 excludes 属性中与模式匹配的任何字段。

以下搜索 API 请求仅返回 sk* 和 name 字段及其属性的源,不包括 name.secondname 字段。

GET developers/_search
{
  "query": {
    "match_all": {}
  },
  "_source": {
    "includes": [ "sk*", "name" ],
    "excludes": [ "name.secondname"]
  }
}
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "developers",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "skills" : [
            "Java",
            "C++"
          ],
          "name" : {
            "firstname" : "san"
          }
        }
      },
      {
        "_index" : "developers",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "skills" : [
            "Ruby",
            "C++"
          ],
          "name" : {
            "firstname" : "si"
          }
        }
      }
    ]
  }
}

 

Scripted fields

你可以使用 script_fields 参数为每个 hit 检索进行脚本运算(基于不同的字段)。 例如:

GET developers/_search
{
  "query": {
    "match_all": {}
  },
  "script_fields": {
    "2_times_age": {
      "script": {
        "source": """
          doc['age'].value * 2
        """
      }
    },
    "3_times_age": {
      "script": {
        "source": """
          doc['age'].value * 3
        """
      }
    }
  }
}

上面将返回:

{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "developers",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "fields" : {
          "2_times_age" : [
            40
          ],
          "3_times_age" : [
            60
          ]
        }
      },
      {
        "_index" : "developers",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "fields" : {
          "2_times_age" : [
            60
          ],
          "3_times_age" : [
            90
          ]
        }
      }
    ]
  }
}

Scripted fields 可以在未 store 的字段上工作(在上述情况下为 age),并允许返回要返回的自定义值(脚本的计算值)。

脚本字段还可以使用 params['_ source'] 访问实际的 _source 文档并提取要从中返回的特定元素。 这是一个例子:

GET developers/_search
{
  "query": {
    "match_all": {}
  },
  "script_fields": {
    "my_city": {
      "script": {
        "source": """
          "I am living in " + params["_source"]["city"]
        """
      }
    }
  }
}
{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "developers",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "fields" : {
          "my_city" : [
            "I am living in Beijing"
          ]
        }
      },
      {
        "_index" : "developers",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "fields" : {
          "my_city" : [
            "I am living in Shanghai"
          ]
        }
      }
    ]
  }
}

请注意此处的 _source 关键字,以浏览类似 json 的模型。

重要的是要了解 doc['my_field'].value 和 params['_ source'] ['my_field'] 之间的区别。 第一个使用 doc 关键字,将导致将该字段的术语加载到内存中(缓存),这将导致执行速度更快,但会占用更多内存。 此外,doc [...]表示法仅允许使用简单值字段(你无法从中返回 json 对象),并且仅对未分析或基于单个术语的字段有意义。 但是,从文档访问值的方式来说,仍然建议使用doc(即使有可能),因为 _source 每次使用时都必须加载和解析。 使用 _source 非常慢。

已标记关键词 清除标记
相关推荐