從搜索中獲取選定的字段


導語



在實際的搜索返回數據中,我們經常會用選擇地返回所需要的字段或部分的 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": "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}


在上面我們看到 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。


處理 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}


無論用於搜索它們的模式如何,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 非常慢。


正文完



 作者:劉曉國

本文編輯:喝咖啡的貓



嗨,互動起來吧!

喜歡這篇文章麼?

歡迎留下你想說的,留言 100% 精選哦!

Elastic 社區公衆號長期徵稿,如果您有 Elastic  技術的相關文章,也歡迎投稿至本公衆號,一起進步! 投稿請添加微信:medcl123



招聘信息

Job board

公司

貝殼找房

公司地點

上海

崗位職責

● 負責公司Elasticsearch平臺的運營及日常維護

● 緊跟社區的技術發展,推動Elasticsearch新特性在公司的落地

● 根據公司平臺的特點,規劃,設計並且實現Elasticsearch內核的優化

崗位要求

● 熟悉Java,Go,C/C++等開發語言中的一種或幾種

● 熟悉常用的數據結構及算法

● 熟悉Elasticsearch,Lucene等開源系統

● 有清晰的分析思路及解決問題能力,對底層基礎技術有興趣,學習能力強

具有以下條件者優先

● 有Elasticsearch或者Lucene內核開發經驗

● 有大型分佈式系統研發經驗

● 熟悉常見KV存儲引擎

聯繫方式

[email protected]

18201588100

社區招聘欄目是一個新的嘗試,幫助社區的小夥伴找到心儀的職位,也幫助企業找到所需的人才,爲伯樂和千里馬牽線搭橋。有招聘需求的企業和正在求職的社區小夥伴,可以聯繫微信 medcl123 提交招聘需求和發佈個人簡歷信息。


Elastic中文社區公衆號 (elastic-cn)

爲您彙集 Elastic 社區的最新動態、精選乾貨文章、精華討論、文檔資料、翻譯與版本發佈等。

喜歡本篇內容就請給我們點個[在看]吧




本文分享自微信公衆號 - Elastic中文社區(elastic-cn)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章