该功能从 7.10 版本开始被支持!
在实际的搜索返回数据中,我们经常会用选择地返回所需要的字段或部分的 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
- Doc value fields
- Stored fields
- Source filtering
- Script fields
Fields
fields 参数允许检索搜索响应中的文档字段列表。 它同时查阅文档 _source 和索引 mapping,以符合其映射类型的标准化方式返回每个值。 默认情况下,日期字段是根据其 mapping 中的日期格式参数设置格式的。 你还可以使用 fields 参数来检索运行时字段值。我们使用如下的文档作为例子:
PUT developers/_doc/1
{
"name": {
"firstname": "san",
"secondname": "zhang"
},
"age": 20,
"city": "Beijing",
"skills": ["Java", "C++"],
"DOB": "1988-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" : "1988-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 参数处理字段类型,例如字段 aliases 和 constant_keyword,其值并不总是出现在 _source 中。 还应其他映射选项也被考虑,包括 ignore_above,ignore_malformed 和null_value。
注意:即使 _source 中只有一个值,fields 响应也总是为每个字段返回一个值数组。 这是因为 Elasticsearch 没有专用的数组类型,并且任何字段都可以包含多个值。 fields 参数也不能保证以特定顺序返回数组值。 有关更多背景信息,请参见映射文档中的 array。
目前在 Kibana 中的 Discover 中,基本都是使用 fiields 而不 source 来进行显示的:
处理 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": "1988-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" : "1988-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 非常慢。