source filtering
我們可以通過 _source 來定義返回想要的字段:
GET twitter/_search
{
"_source": ["user", "city"],
"query": {
"match_all": {
}
}
}
返回的結果:
"hits" : [
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"city" : "北京",
"user" : "張三"
}
},
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "2",
"_score" : 1.0,
"_source" : {
"city" : "北京",
"user" : "老劉"
}
},
...
]
我們也可以使用如下的方法:
GET twitter/_search
{
"_source": {
"includes": ["user", "city"]
},
"query": {
"match_all": {
}
}
}
上面返回的結果和之前的返回的是一樣的結果。
我們可以看到只有 user 及 city 兩個字段在 _source 里返回。我們可以可以通過設置 _source 為 false,這樣不返回任何的 _source信息:
GET twitter/_search
{
"_source": false,
"query": {
"match": {
"user": "張三"
}
}
}
返回的信息:
"hits" : [
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "1",
"_score" : 3.0808902
}
]
我們可以看到只有 _id 及 _score 等信息返回。其它任何的 _source 字段都沒有被返回。它也可以接收通配符形式的控制,比如:
GET twitter/_search
{
"_source": {
"includes": [
"user*",
"location*"
],
"excludes": [
"*.lat"
]
},
"query": {
"match_all": {}
}
}
返回的結果是:
"hits" : [
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"location" : {
"lon" : "116.325747"
},
"user" : "張三"
}
},
...
]
如果我們把 _source 設置為[],那么就是顯示所有的字段,而不是不顯示任何字段的功能。
GET twitter/_search
{
"_source": [],
"query": {
"match_all": {
}
}
}
上面的命令將顯示 source 的所有字段。
Script fields
有些時候,我們想要的 field 可能在 _source 里根本沒有,那么我們可以使用 script field 來生成這些 field。允許為每個匹配返回script evaluation(基於不同的字段),例如:
GET twitter/_search
{
"query": {
"match_all": {}
},
"script_fields": {
"years_to_100": {
"script": {
"lang": "painless",
"source": "100-doc['age'].value"
}
},
"year_of_birth":{
"script": "2019 - doc['age'].value"
}
}
}
返回的結果是:
"hits" : [
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"fields" : {
"years_to_100" : [
80
],
"year_of_birth" : [
1999
]
}
},
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "2",
"_score" : 1.0,
"fields" : {
"years_to_100" : [
70
],
"year_of_birth" : [
1989
]
}
},
...
]
必須注意的是這種使用 script 的方法來生成查詢的結果對於大量的文檔來說,可能會占用大量資源。 在這里大家一定要注意的是:doc 在這里指的是 doc value。否則的話,我們需要使用 ctx._source 來做一些搜索的動作。參照鏈接,我們可以把上面的命令修改為:
GET twitter/_search
{
"query": {
"match_all": {}
},
"script_fields": {
"years_to_100": {
"script": {
"lang": "painless",
"source": "100-params._source['age']"
}
},
"year_of_birth":{
"script": "2019 - params._source['age']"
}
}
}
因為 age 是 long 數據類型。它是有 doc value 的,所以,我們可以通過 doc['age'] 來訪問,而且這些訪問是比較快的。
Count API
我們經常會查詢我們的索引里到底有多少文檔,那么我們可以使用_count重點來查詢:
GET twitter/_count
如果我們想知道滿足條件的文檔的數量,我們可以采用如下的格式:
GET twitter/_count
{
"query": {
"match": {
"city": "北京"
}
}
}
在這里,我們可以得到 city 為“北京”的所有文檔的數量:
{
"count" : 5,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
}
}
修改 settings
我們可以通過如下的接口來獲得一個 index 的 settings
GET twitter/_settings
從這里我們可以看到我們的 twitter 索引有多少個 shards 及多少個 replicas。我們也可以通過如下的接口來設置:
PUT twitter
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
}
}
一旦我們把 number_of_shards 定下來了,我們就不可以修改了,除非把 index 刪除,並重新 index 它。這是因為每個文檔存儲到哪一個 shard 是和 number_of_shards這 個數值有關的。一旦這個數值發生改變,那么之后尋找那個文檔所在的 shard 就會不准確。
修改索引的 mapping
Elasticsearch 號稱是 schemaless,在實際所得應用中,每一個 index 都有一個相應的 mapping。這個 mapping 在我們生產第一個文檔時已經生產。它是對每個輸入的字段進行自動的識別從而判斷它們的數據類型。我們可以這么理解 schemaless:
不需要事先定義一個相應的 mapping 才可以生產文檔。字段類型是動態進行識別的。這和傳統的數據庫是不一樣的
如果有動態加入新的字段,mapping 也可以自動進行調整並識別新加入的字段
自動識別字段有一個問題,那就是有的字段可能識別並不精確,比如對於我們例子中的位置信息。那么我們需要對這個字段進行修改。
我們可以通過如下的命令來查詢目前的 index 的 mapping:
GET twitter/_mapping
它顯示的數據如下:
{
"twitter" : {
"mappings" : {
"properties" : {
"address" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"age" : {
"type" : "long"
},
"city" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"country" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"location" : {
"properties" : {
"lat" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"lon" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
},
"message" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"province" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"uid" : {
"type" : "long"
},
"user" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
從上面的顯示中可以看出來 location 里的經緯度是一個 multi-field 的類型。
"location" : {
"properties" : {
"lat" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"lon" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
這個顯然不是我們所需的。正確的類型應該是:geo_point。我們重新修正我們的 mapping。
注意:我們不能為已經建立好的 index 動態修改 mapping。這是因為一旦修改,那么之前建立的索引就變成不能搜索的了。一種辦法是 reindex 從而重新建立我們的索引。如果在之前的 mapping 加入新的字段,那么我們可以不用重新建立索引。
為了能夠正確地創建我們的 mapping,我們必須先把之前的 twitter 索引刪除掉,並同時使用 settings 來創建這個 index。具體的步驟如下:
DELETE twitter
PUT twitter
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
}
}
PUT twitter/_mapping
{
"properties": {
"address": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"age": {
"type": "long"
},
"city": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"country": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"location": {
"type": "geo_point"
},
"message": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"province": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"uid": {
"type": "long"
},
"user": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
重新查看我們的 mapping:
GET twitter/_mapping
我們可以看到我們已經創建好了新的 mapping。我們再次運行之前我們的 bulk 接口,並把我們所需要的數據導入到 twitter 索引中。
POST _bulk
{ "index" : { "_index" : "twitter", "_id": 1} }
{"user":"雙榆樹-張三","message":"今兒天氣不錯啊,出去轉轉去","uid":2,"age":20,"city":"北京","province":"北京","country":"中國","address":"中國北京市海淀區","location":{"lat":"39.970718","lon":"116.325747"}}
{ "index" : { "_index" : "twitter", "_id": 2 }}
{"user":"東城區-老劉","message":"出發,下一站雲南!","uid":3,"age":30,"city":"北京","province":"北京","country":"中國","address":"中國北京市東城區台基廠三條3號","location":{"lat":"39.904313","lon":"116.412754"}}
{ "index" : { "_index" : "twitter", "_id": 3} }
{"user":"東城區-李四","message":"happy birthday!","uid":4,"age":30,"city":"北京","province":"北京","country":"中國","address":"中國北京市東城區","location":{"lat":"39.893801","lon":"116.408986"}}
{ "index" : { "_index" : "twitter", "_id": 4} }
{"user":"朝陽區-老賈","message":"123,gogogo","uid":5,"age":35,"city":"北京","province":"北京","country":"中國","address":"中國北京市朝陽區建國門","location":{"lat":"39.718256","lon":"116.367910"}}
{ "index" : { "_index" : "twitter", "_id": 5} }
{"user":"朝陽區-老王","message":"Happy BirthDay My Friend!","uid":6,"age":50,"city":"北京","province":"北京","country":"中國","address":"中國北京市朝陽區國貿","location":{"lat":"39.918256","lon":"116.467910"}}
{ "index" : { "_index" : "twitter", "_id": 6} }
{"user":"虹橋-老吳","message":"好友來了都今天我生日,好友來了,什么 birthday happy 就成!","uid":7,"age":90,"city":"上海","province":"上海","country":"中國","address":"中國上海市閔行區","location":{"lat":"31.175927","lon":"121.383328"}}
至此,我們已經完整地建立了我們所需要的索引。在下面,我們開始使用 DSL(Domain Specifc Lanaguage)來幫我們進行查詢。
查詢數據
在這個章節里,我們來展示一下從我們的 ES 索引中查詢我們所想要的數據。
match query
GET twitter/_search
{
"query": {
"match": {
"city": "北京"
}
}
}
從我們查詢的結果來看,我們可以看到有5個用戶是來自北京的,而且查詢出來的結果是按照關聯(relavance)來進行排序的。
在很多的情況下,我們也可以使用 script query 來完成:
GET twitter/_search
{
"query": {
"script": {
"script": {
"source": "doc['city.keyword'].contains(params.name)",
"lang": "painless",
"params": {
"name": "北京"
}
}
}
}
}
上面的 script query 和上面的查詢是一樣的結果,但是我們不建議大家使用這種方法。相比較而言,script query 的方法比較低效。另外,假如我們的文檔是幾百萬或者 PB 級的數據量,那么上面的運算可能被執行無數次,那么可能需要巨大的計算量。在這種情況下,我們需要考慮在 ingest 的時候做計算。請閱讀我的另外一篇文章 “避免不必要的腳本 - scripting”。
上面的搜索也可以這么實現:
GET twitter/_search?q=city:"北京"
"hits" : [
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "1",
"_score" : 0.48232412,
"_source" : {
"user" : "雙榆樹-張三",
"message" : "今兒天氣不錯啊,出去轉轉去",
"uid" : 2,
"age" : 20,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市海淀區",
"location" : {
"lat" : "39.970718",
"lon" : "116.325747"
}
}
}
...
]
如果你想了解更多,你可以更進一步閱讀 “Elasticsearch: 使用URI Search”。
如果我們不需要這個 score,我們可以選擇 filter 來完成。
GET twitter/_search
{
"query": {
"bool": {
"filter": {
"term": {
"city.keyword": "北京"
}
}
}
}
}
這里我們使用了 filter 來過濾我們的搜索,顯示的結果如下:
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 5,
"relation" : "eq"
},
"max_score" : 0.0,
"hits" : [
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "1",
"_score" : 0.0,
"_source" : {
"user" : "雙榆樹-張三",
"message" : "今兒天氣不錯啊,出去轉轉去",
"uid" : 2,
"age" : 20,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市海淀區",
"location" : {
"lat" : "39.970718",
"lon" : "116.325747"
}
}
},
...
}
從返回的結果來看,_score 項為0。對於這種搜索,只要 yes 或 no。我們並不關心它們是的相關性。在這里我們使用了city.keyword。對於一些剛接觸 Elasticsearch的人來說,這個可能比較陌生。正確的理解是 city 在我們的 mapping 中是一個 multi-field 項。它既是 text 也是 keyword 類型。對於一個 keyword 類型的項來說,這個項里面的所有字符都被當做一個字符串。它們在建立文檔時,不需要進行 index。keyword 字段用於精確搜索,aggregation 和排序(sorting)。
所以在我們的 filter 中,我們是使用了 term 來完成這個查詢。
我們也可以使用如下的辦法達到同樣的效果:
GET twitter/_search
{
"query": {
"constant_score": {
"filter": {
"term": {
"city.keyword": {
"value": "北京"
}
}
}
}
}
}
在我們使用 match query 時,默認的操作是 OR,我們可以做如下的查詢:
GET twitter/_search
{
"query": {
"match": {
"user": {
"query": "朝陽區-老賈",
"operator": "or"
}
}
}
}
上面的查詢也和如下的查詢是一樣的:
GET twitter/_search
{
"query": {
"match": {
"user": "朝陽區-老賈"
}
}
}
這是因為默認的操作是 or 操作。上面查詢的結果是任何文檔匹配:“朝”,“陽”,“區”,“老”及“賈”這5個字中的任何一個將被顯示:
"hits" : [
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "4",
"_score" : 4.4209847,
"_source" : {
"user" : "朝陽區-老賈",
"message" : "123,gogogo",
"uid" : 5,
"age" : 35,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市朝陽區建國門",
"location" : {
"lat" : "39.718256",
"lon" : "116.367910"
}
}
},
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "5",
"_score" : 2.9019678,
"_source" : {
"user" : "朝陽區-老王",
"message" : "Happy BirthDay My Friend!",
"uid" : 6,
"age" : 50,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市朝陽區國貿",
"location" : {
"lat" : "39.918256",
"lon" : "116.467910"
}
}
},
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "2",
"_score" : 0.8713734,
"_source" : {
"user" : "東城區-老劉",
"message" : "出發,下一站雲南!",
"uid" : 3,
"age" : 30,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市東城區台基廠三條3號",
"location" : {
"lat" : "39.904313",
"lon" : "116.412754"
}
}
},
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "6",
"_score" : 0.4753614,
"_source" : {
"user" : "虹橋-老吳",
"message" : "好友來了都今天我生日,好友來了,什么 birthday happy 就成!",
"uid" : 7,
"age" : 90,
"city" : "上海",
"province" : "上海",
"country" : "中國",
"address" : "中國上海市閔行區",
"location" : {
"lat" : "31.175927",
"lon" : "121.383328"
}
}
},
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "3",
"_score" : 0.4356867,
"_source" : {
"user" : "東城區-李四",
"message" : "happy birthday!",
"uid" : 4,
"age" : 30,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市東城區",
"location" : {
"lat" : "39.893801",
"lon" : "116.408986"
}
}
}
]
我們也可以設置參數 minimum_should_match 來設置至少匹配的 term。比如:
GET twitter/_search
{
"query": {
"match": {
"user": {
"query": "朝陽區-老賈",
"operator": "or",
"minimum_should_match": 3
}
}
}
}
上面顯示我們至少要匹配“朝”,“陽”,“區”,“老” 及 “賈” 這5個中的3個字才可以。顯示結果:
"hits" : [
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "4",
"_score" : 4.4209847,
"_source" : {
"user" : "朝陽區-老賈",
"message" : "123,gogogo",
"uid" : 5,
"age" : 35,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市朝陽區建國門",
"location" : {
"lat" : "39.718256",
"lon" : "116.367910"
}
}
},
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "5",
"_score" : 2.9019678,
"_source" : {
"user" : "朝陽區-老王",
"message" : "Happy BirthDay My Friend!",
"uid" : 6,
"age" : 50,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市朝陽區國貿",
"location" : {
"lat" : "39.918256",
"lon" : "116.467910"
}
}
}
]
我們也可以改為 and 操作看看:
GET twitter/_search
{
"query": {
"match": {
"user": {
"query": "朝陽區-老賈",
"operator": "and"
}
}
}
}
顯示的結果是:
"hits" : [
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "4",
"_score" : 4.4209847,
"_source" : {
"user" : "朝陽區-老賈",
"message" : "123,gogogo",
"uid" : 5,
"age" : 35,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市朝陽區建國門",
"location" : {
"lat" : "39.718256",
"lon" : "116.367910"
}
}
}
]
在這種情況下,需要同時匹配索引的5個字才可以。顯然我們可以通過使用 and 來提高搜索的精度。
Ids query
我們可以通過 id 來進行查詢,比如:
GET twitter/_search
{
"query": {
"ids": {
"values": ["1", "2"]
}
}
}
上面的查詢將返回 id 為 “1” 和 “2” 的文檔。
multi_match
在上面的搜索之中,我們特別指明一個專有的 field 來進行搜索,但是在很多的情況下,我們並胡知道是哪一個 field 含有這個關鍵詞,那么在這種情況下,我們可以使用 multi_match 來進行搜索:
GET twitter/_search
{
"query": {
"multi_match": {
"query": "朝陽",
"fields": [
"user",
"address^3",
"message"
],
"type": "best_fields"
}
}
}
在上面,我們可以看到這個 multi_search 的 type 為 best_fields,也就是說它搜索了3個字段。最終的分數 _score 是按照得分最高的那個字段的分數為准。更多類型的定義,請在鏈接查看。在上面,我們可以同時對三個 fields: user,adress 及 message進行搜索,但是我們對 address 含有 “朝陽” 的文檔的分數進行3倍的加權。返回的結果:
"hits" : [
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "5",
"_score" : 6.1777167,
"_source" : {
"user" : "朝陽區-老王",
"message" : "Happy good BirthDay My Friend!",
"uid" : 6,
"age" : 50,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市朝陽區國貿",
"location" : {
"lat" : "39.918256",
"lon" : "116.467910"
}
}
},
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "4",
"_score" : 5.9349246,
"_source" : {
"user" : "朝陽區-老賈",
"message" : "123,gogogo",
"uid" : 5,
"age" : 35,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市朝陽區建國門",
"location" : {
"lat" : "39.718256",
"lon" : "116.367910"
}
}
}
]
Prefix query
返回在提供的字段中包含特定前綴的文檔。
GET twitter/_search
{
"query": {
"prefix": {
"user": {
"value": "朝"
}
}
}
}
查詢 user 字段里以“朝”為開頭的所有文檔:
"hits" : [
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "4",
"_score" : 1.0,
"_source" : {
"user" : "朝陽區-老賈",
"message" : "123,gogogo",
"uid" : 5,
"age" : 35,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市朝陽區建國門",
"location" : {
"lat" : "39.718256",
"lon" : "116.367910"
}
}
},
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "5",
"_score" : 1.0,
"_source" : {
"user" : "朝陽區-老王",
"message" : "Happy BirthDay My Friend!",
"uid" : 6,
"age" : 50,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市朝陽區國貿",
"location" : {
"lat" : "39.918256",
"lon" : "116.467910"
}
}
}
]
Term query
Term query 會在給定字段中進行精確的字詞匹配。 因此,您需要提供准確的術語以獲取正確的結果。
GET twitter/_search
{
"query": {
"term": {
"user.keyword": {
"value": "朝陽區-老賈"
}
}
}
}
在這里,我們使用 user.keyword 來對“朝陽區-老賈”進行精確匹配查詢相應的文檔:
"hits" : [
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "4",
"_score" : 1.5404451,
"_source" : {
"user" : "朝陽區-老賈",
"message" : "123,gogogo",
"uid" : 5,
"age" : 35,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市朝陽區建國門",
"location" : {
"lat" : "39.718256",
"lon" : "116.367910"
}
}
}
]
Terms query
如果我們想對多個 terms 進行查詢,我們可以使用如下的方式來進行查詢:
GET twitter/_search
{
"query": {
"terms": {
"user.keyword": [
"雙榆樹-張三",
"東城區-老劉"
]
}
}
}
上面查詢 user.keyword 里含有“雙榆樹-張三”或“東城區-老劉”的所有文檔。
Terms_set query
查詢在提供的字段中包含最少數目的精確術語的文檔。除你可以定義返回文檔所需的匹配術語數之外,terms_set 查詢與術語查詢相同。 例如:
PUT /job-candidates
{
"mappings": {
"properties": {
"name": {
"type": "keyword"
},
"programming_languages": {
"type": "keyword"
},
"required_matches": {
"type": "long"
}
}
}
}
PUT /job-candidates/_doc/1?refresh
{
"name": "Jane Smith",
"programming_languages": [ "c++", "java" ],
"required_matches": 2
}
PUT /job-candidates/_doc/2?refresh
{
"name": "Jason Response",
"programming_languages": [ "java", "php" ],
"required_matches": 2
}
GET /job-candidates/_search
{
"query": {
"terms_set": {
"programming_languages": {
"terms": [ "c++", "java", "php" ],
"minimum_should_match_field": "required_matches"
}
}
}
}
在上面,我們為 job-candiates 索引創建了兩個文檔。我們需要找出在 programming_languages 中同時含有 c++, java 以及 php 中至少有兩個 term 的文檔。在這里,我們使用了一個在文檔中定義的字段 required_matches 來定義最少滿足要求的 term 個數。另外一種方式是使用 minimum_should_match_script 來定義,如果沒有一個專有的字段來定義這個的話:
GET /job-candidates/_search
{
"query": {
"terms_set": {
"programming_languages": {
"terms": [ "c++", "java", "php" ],
"minimum_should_match_script": {
"source": "2"
}
}
}
}
}
上面標示需要至少同時滿足有 2 個及以上的 term。上面搜索的結果為:
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.1005894,
"hits" : [
{
"_index" : "job-candidates",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.1005894,
"_source" : {
"name" : "Jane Smith",
"programming_languages" : [
"c++",
"java"
],
"required_matches" : 2
}
},
{
"_index" : "job-candidates",
"_type" : "_doc",
"_id" : "2",
"_score" : 1.1005894,
"_source" : {
"name" : "Jason Response",
"programming_languages" : [
"java",
"php"
],
"required_matches" : 2
}
}
]
}
}
也就是說之前的兩個文檔都同時滿足條件。當然如果我們使用如下的方式來進行搜索:
GET /job-candidates/_search
{
"query": {
"terms_set": {
"programming_languages": {
"terms": [ "c++", "java", "nodejs" ],
"minimum_should_match_script": {
"source": "2"
}
}
}
}
}
我們將看到只有一個文檔是滿足條件的。
復合查詢(compound query)
在上面,我們用到了許多的 leaf 查詢,比如:
"query": {
"match": {
"city": "北京"
}
}
什么是復合查詢呢?如果說上面的查詢是 leaf 查詢的話,那么復合查詢可以把很多個 leaf 查詢組合起來從而形成更為復雜的查詢。它一般的格式是:
POST _search
{
"query": {
"bool" : {
"must" : {
"term" : { "user" : "kimchy" }
},
"filter": {
"term" : { "tag" : "tech" }
},
"must_not" : {
"range" : {
"age" : { "gte" : 10, "lte" : 20 }
}
},
"should" : [
{ "term" : { "tag" : "wow" } },
{ "term" : { "tag" : "elasticsearch" } }
],
"minimum_should_match" : 1,
"boost" : 1.0
}
}
}
從上面我們可以看出,它是由 bool 下面的 must, must_not, should 及 filter 共同來組成的。你可以使用 minimum_should_match 參數指定返回的文檔必須匹配的應當子句的數量或百分比。如果布爾查詢包含至少一個 should 子句,並且沒有 must 或 filter 子句,則默認值為1。否則,默認值為0。
針對我們的例子,
GET twitter/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"city": "北京"
}
},
{
"match": {
"age": "30"
}
}
]
}
}
}
這個查詢的是必須是 北京城市的,並且年剛好是30歲的。
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.4823241,
"hits" : [
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "2",
"_score" : 1.4823241,
"_source" : {
"user" : "東城區-老劉",
"message" : "出發,下一站雲南!",
"uid" : 3,
"age" : 30,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市東城區台基廠三條3號",
"location" : {
"lat" : "39.904313",
"lon" : "116.412754"
}
}
},
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "3",
"_score" : 1.4823241,
"_source" : {
"user" : "東城區-李四",
"message" : "happy birthday!",
"uid" : 4,
"age" : 30,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市東城區",
"location" : {
"lat" : "39.893801",
"lon" : "116.408986"
}
}
}
]
}
}
如果我們想知道為什么得出來這樣的結果,我們可以在搜索的指令中加入"explained" : true。
GET twitter/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"city": "北京"
}
},
{
"match": {
"age": "30"
}
}
]
}
},
"explain": true
}
這樣在我們的顯示的結果中,可以看到一些一些解釋:
我們的顯示結果有2個。同樣,我們可以把一些滿足條件的排出在外,我們可以使用 must_not。
GET twitter/_search
{
"query": {
"bool": {
"must_not": [
{
"match": {
"city": "北京"
}
}
]
}
}
}
我們想尋找不在北京的所有的文檔:
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 0.0,
"hits" : [
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "6",
"_score" : 0.0,
"_source" : {
"user" : "虹橋-老吳",
"message" : "好友來了都今天我生日,好友來了,什么 birthday happy 就成!",
"uid" : 7,
"age" : 90,
"city" : "上海",
"province" : "上海",
"country" : "中國",
"address" : "中國上海市閔行區",
"location" : {
"lat" : "31.175927",
"lon" : "121.383328"
}
}
}
]
}
}
我們顯示的文檔只有一個。他來自上海,其余的都北京的。
接下來,我們來嘗試一下 should。它表述“或”的意思,也就是有就更好,沒有就算了。比如:
GET twitter/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"age": "30"
}
}
],
"should": [
{
"match_phrase": {
"message": "Happy birthday"
}
}
]
}
}
}
這個搜尋的意思是,age 必須是30歲,但是如果文檔里含有 “Hanppy birthday”,相關性會更高,那么搜索得到的結果會排在前面:
{
"took" : 8,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 2.641438,
"hits" : [
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "3",
"_score" : 2.641438,
"_source" : {
"user" : "東城區-李四",
"message" : "happy birthday!",
"uid" : 4,
"age" : 30,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市東城區",
"location" : {
"lat" : "39.893801",
"lon" : "116.408986"
}
}
},
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "2",
"_score" : 1.0,
"_source" : {
"user" : "東城區-老劉",
"message" : "出發,下一站雲南!",
"uid" : 3,
"age" : 30,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市東城區台基廠三條3號",
"location" : {
"lat" : "39.904313",
"lon" : "116.412754"
}
}
}
]
}
}
在上面的結果中,我們可以看到:同樣是年齡30歲的兩個文檔,第一個文檔由於含有 “Happy birthday” 這個字符串在 message 里,所以它的結果是排在前面的,相關性更高。我們可以從它的 _score 中可以看出來。第二個文檔里 age 是30,但是它的 message 里沒有 “Happy birthday” 字樣,但是它的結果還是有顯示,只是得分比較低一些。
在使用上面的復合查詢時,bool 請求通常是 must,must_not, should 及 filter 的一個或其中的幾個一起組合形成的。我們必須注意的是:
查詢類型對 hits 及 _score 的影響
Clause 影響 #hits 影響 _score
must Yes Yes
must_not Yes No
should No* Yes
filter Yes No
如上面的表格所示,should 只有在特殊的情況下才會影響 hits。在正常的情況下它不會影響搜索文檔的個數。那么在哪些情況下會影響搜索的結果呢?這種情況就是針對只有 should 的搜索情況,也就是如果你在 bool query 里,不含有 must, must_not 及 filter 的情況下,一個或更多的 should 必須有一個匹配才會有結果,比如:
GET twitter/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"city": "北京"
}
},
{
"match": {
"city": "武漢"
}
}
]
}
}
}
上面的查詢顯示結果為:
"hits" : {
"total" : {
"value" : 5,
"relation" : "eq"
},
"max_score" : 0.48232412,
"hits" : [
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "1",
"_score" : 0.48232412,
"_source" : {
"user" : "雙榆樹-張三",
"message" : "今兒天氣不錯啊,出去轉轉去",
"uid" : 2,
"age" : 20,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市海淀區",
"location" : {
"lat" : "39.970718",
"lon" : "116.325747"
}
}
},
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "2",
"_score" : 0.48232412,
"_source" : {
"user" : "東城區-老劉",
"message" : "出發,下一站雲南!",
"uid" : 3,
"age" : 30,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市東城區台基廠三條3號",
"location" : {
"lat" : "39.904313",
"lon" : "116.412754"
}
}
},
...
}
在這種情況下,should 是會影響查詢的結果的。如果我們使用 minimum_should_match 為2,也就是:
GET twitter/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"city": "北京"
}
},
{
"match": {
"city": "武漢"
}
}
],
"minimum_should_match": 2
}
}
}
也就是上面的兩個 should 都必須同時滿足才能被搜索到。上面的查詢結果為空,因為我們沒有一個 city 同時是 “北京” 和 “武漢” 的。
位置查詢
Elasticsearch 最厲害的是位置查詢。這在很多的關系數據庫里並沒有。我們舉一個簡單的例子:
GET twitter/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"address": "北京"
}
}
]
}
},
"post_filter": {
"geo_distance": {
"distance": "3km",
"location": {
"lat": 39.920086,
"lon": 116.454182
}
}
}
}
在這里,我們查找在地址欄里有“北京”,並且在以位置(116.454182, 39.920086)為中心的3公里以內的所有文檔。
{
"took" : 58,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 0.48232412,
"hits" : [
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "5",
"_score" : 0.48232412,
"_source" : {
"user" : "朝陽區-老王",
"message" : "Happy BirthDay My Friend!",
"uid" : 6,
"age" : 50,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市朝陽區國貿",
"location" : {
"lat" : "39.918256",
"lon" : "116.467910"
}
}
}
]
}
}
在我們的查詢結果中只有一個文檔滿足要求。
下面,我們找出在5公里以內的所有位置信息,並按照遠近大小進行排序:
GET twitter/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"address": "北京"
}
}
]
}
},
"post_filter": {
"geo_distance": {
"distance": "5km",
"location": {
"lat": 39.920086,
"lon": 116.454182
}
}
},
"sort": [
{
"_geo_distance": {
"location": "39.920086,116.454182",
"order": "asc",
"unit": "km"
}
}
]
}
在這里,我們看到了使用 sort 來對我們的搜索的結果進行排序。按照升序排列。
{
"took" : 5,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : null,
"hits" : [
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "5",
"_score" : null,
"_source" : {
"user" : "朝陽區-老王",
"message" : "Happy BirthDay My Friend!",
"uid" : 6,
"age" : 50,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市朝陽區國貿",
"location" : {
"lat" : "39.918256",
"lon" : "116.467910"
}
},
"sort" : [
1.1882901656104885
]
},
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "2",
"_score" : null,
"_source" : {
"user" : "東城區-老劉",
"message" : "出發,下一站雲南!",
"uid" : 3,
"age" : 30,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市東城區台基廠三條3號",
"location" : {
"lat" : "39.904313",
"lon" : "116.412754"
}
},
"sort" : [
3.9447355972239952
]
},
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "3",
"_score" : null,
"_source" : {
"user" : "東城區-李四",
"message" : "happy birthday!",
"uid" : 4,
"age" : 30,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市東城區",
"location" : {
"lat" : "39.893801",
"lon" : "116.408986"
}
},
"sort" : [
4.837769064666224
]
}
]
}
}
我們可以看到有三個顯示的結果。在 sort 里我們可以看到距離是越來越大啊。另外我們可以看出來,如果 _score 不是 sort 的field,那么在使用 sor t后,所有的結果的 _score 都變為null。如果排序的如果在上面的搜索也可以直接寫作為:
GET twitter/_search
{
"query": {
"bool": {
"must": {
"match": {
"address": "北京"
}
},
"filter": {
"geo_distance": {
"distance": "5km",
"location": {
"lat": 39.920086,
"lon": 116.454182
}
}
}
}
},
"sort": [
{
"_geo_distance": {
"location": "39.920086,116.454182",
"order": "asc",
"unit": "km"
}
}
]
}
范圍查詢
在 ES 中,我們也可以進行范圍查詢。我們可以根據設定的范圍來對數據進行查詢:
GET twitter/_search
{
"query": {
"range": {
"age": {
"gte": 30,
"lte": 40
}
}
}
}
在這里,我們查詢年齡介於30到40歲的文檔:
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "2",
"_score" : 1.0,
"_source" : {
"user" : "東城區-老劉",
"message" : "出發,下一站雲南!",
"uid" : 3,
"age" : 30,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市東城區台基廠三條3號",
"location" : {
"lat" : "39.904313",
"lon" : "116.412754"
}
}
},
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "3",
"_score" : 1.0,
"_source" : {
"user" : "東城區-李四",
"message" : "happy birthday!",
"uid" : 4,
"age" : 30,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市東城區",
"location" : {
"lat" : "39.893801",
"lon" : "116.408986"
}
}
},
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "4",
"_score" : 1.0,
"_source" : {
"user" : "朝陽區-老賈",
"message" : "123,gogogo",
"uid" : 5,
"age" : 35,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市朝陽區建國門",
"location" : {
"lat" : "39.718256",
"lon" : "116.367910"
}
}
}
]
}
}
如上所示,我們找到了3個匹配的文檔。同樣地,我們也可以對它們進行排序:
GET twitter/_search
{
"query": {
"range": {
"age": {
"gte": 30,
"lte": 40
}
}
},"sort": [
{
"age": {
"order": "desc"
}
}
]
}
我們對整個搜索的結果按照降序進行排序。
Exists 查詢
我們可以通過 exists 來查詢一個字段是否存在。比如我們再增加一個文檔:
PUT twitter/_doc/20
{
"user" : "王二",
"message" : "今兒天氣不錯啊,出去轉轉去",
"uid" : 20,
"age" : 40,
"province" : "北京",
"country" : "中國",
"address" : "中國北京市海淀區",
"location" : {
"lat" : "39.970718",
"lon" : "116.325747"
}
}
在這個文檔里,我們的 city 這一個字段是不存在的,那么一下的這個搜索將不會返回上面的這個搜索。
GET twitter/_search
{
"query": {
"exists": {
"field": "city"
}
}
}
如果文檔里只要 city 這個字段不為空,那么就會被返回。反之,如果一個文檔里city這個字段是空的,那么就不會返回。
如果查詢不含 city 這個字段的所有的文檔,可以這樣查詢:
GET twitter/_search
{
"query": {
"bool": {
"must_not": {
"exists": {
"field": "city"
}
}
}
}
}
假如我們創建另外一個索引 twitter1,我們打入如下的命令:
PUT twitter10/_doc/1
{
"locale": null
}
然后,我們使用如下的命令來進行查詢:
GET twitter10/_search
{
"query": {
"exists": {
"field": "locale"
}
}
}
上面查詢的結果顯示:
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 0,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
}
}
也就是沒有找到。
如果你想找到一個 missing 的字段,你可以使用如下的方法:
GET twitter10/_search
{
"query": {
"bool": {
"must_not": [
{
"exists": {
"field": "locale"
}
}
]
}
}
}
上面的方法返回的數據是:
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 0.0,
"hits" : [
{
"_index" : "twitter1",
"_type" : "_doc",
"_id" : "1",
"_score" : 0.0,
"_source" : {
"locale" : null
}
}
]
}
}
顯然這個就是我們想要的結果。
匹配短語
我們可以通過如下的方法來查找 happy birthday。
GET twitter/_search
{
"query": {
"match": {
"message": "happy birthday"
}
}
}
展示的結果:
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : 1.9936417,
"hits" : [
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "3",
"_score" : 1.9936417,
"_source" : {
"user" : "東城區-李四",
"message" : "happy birthday!",
"uid" : 4,
"age" : 30,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市東城區",
"location" : {
"lat" : "39.893801",
"lon" : "116.408986"
}
}
},
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "5",
"_score" : 1.733287,
"_source" : {
"user" : "朝陽區-老王",
"message" : "Happy BirthDay My Friend!",
"uid" : 6,
"age" : 50,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市朝陽區國貿",
"location" : {
"lat" : "39.918256",
"lon" : "116.467910"
}
}
},
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "6",
"_score" : 0.84768087,
"_source" : {
"user" : "虹橋-老吳",
"message" : "好友來了都今天我生日,好友來了,什么 birthday happy 就成!",
"uid" : 7,
"age" : 90,
"city" : "上海",
"province" : "上海",
"country" : "中國",
"address" : "中國上海市閔行區",
"location" : {
"lat" : "31.175927",
"lon" : "121.383328"
}
}
}
]
}
}
在默認的情況下,這個匹配是“或”的關系,也就是找到文檔里含有“Happy"或者“birthday”的文檔。如果我們新增加一個文檔:
PUT twitter/_doc/8
{
"user": "朝陽區-老王",
"message": "Happy",
"uid": 6,
"age": 50,
"city": "北京",
"province": "北京",
"country": "中國",
"address": "中國北京市朝陽區國貿",
"location": {
"lat": "39.918256",
"lon": "116.467910"
}
}
那么我們重新進行搜索,我們可以看到這個新增加的id為8的也會在搜索出的結果之列,雖然它只含有“Happy"在message里。
如果我們想得到“與”的關系,我們可以采用如下的辦法:
GET twitter/_search
{
"query": {
"match": {
"message": {
"query": "happy birthday",
"operator": "and"
}
}
}
}
經過這樣的修改,我們再也看不見那個id為8的文檔了,這是因為我們必須在 message 中同時匹配 “happy” 及 “birthday” 這兩個詞。
我們還有一種方法,那就是:
GET twitter/_search
{
"query": {
"match": {
"message": {
"query": "happy birthday",
"minimum_should_match": 2
}
}
}
}
在這里,我們采用了 “minimum_should_match” 來表面至少有2個是匹配的才可以。
我們可以看到在搜索到的結果中,無論我們搜索的是大小寫字母,在搜索的時候,我們都可以匹配到,並且在 message 中,happy birthday 這兩個詞的先后順序也不是很重要。比如,我們把 id 為5的文檔更改為:
PUT twitter/_doc/5
{
"user": "朝陽區-老王",
"message": "BirthDay My Friend Happy !",
"uid": 6,
"age": 50,
"city": "北京",
"province": "北京",
"country": "中國",
"address": "中國北京市朝陽區國貿",
"location": {
"lat": "39.918256",
"lon": "116.467910"
}
}
在這里,我們有意識地把 BirthDay 弄到 Happy 的前面。我們再次使用上面的查詢看看是否找到 id 為5的文檔。
顯然,match 查詢時時不用分先后順序的。我們下面使用 match_phrase 來看看。
GET twitter/_search
{
"query": {
"match_phrase": {
"message": "Happy birthday"
}
},
"highlight": {
"fields": {
"message": {}
}
}
}
在這里,我們可以看到我們使用了match_phrase。它要求 Happy 必須是在 birthday 的前面。下面是搜尋的結果:
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.6363969,
"hits" : [
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "3",
"_score" : 1.6363969,
"_source" : {
"user" : "東城區-李四",
"message" : "happy birthday!",
"uid" : 4,
"age" : 30,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市東城區",
"location" : {
"lat" : "39.893801",
"lon" : "116.408986"
}
},
"highlight" : {
"message" : [
"<em>happy</em> <em>birthday</em>!"
]
}
}
]
}
}
假如我們把我們之前的那個 id 為5的文檔修改為:
PUT twitter/_doc/5
{
"user": "朝陽區-老王",
"message": "Happy Good BirthDay My Friend!",
"uid": 6,
"age": 50,
"city": "北京",
"province": "北京",
"country": "中國",
"address": "中國北京市朝陽區國貿",
"location": {
"lat": "39.918256",
"lon": "116.467910"
}
}
在這里,我們在 Happy 和 Birthday之前加入了一個 Good。如果用我們之前的那個 match_phrase 是找不到這個文檔的。為了能夠找到上面這個修正的結果,我們可以使用:
GET twitter/_search
{
"query": {
"match_phrase": {
"message": {
"query": "Happy birthday",
"slop": 1
}
}
},
"highlight": {
"fields": {
"message": {}
}
}
}
注意:在這里,我們使用了 slop 為1,表面 Happy 和 birthday 之前是可以允許一個 token 的差別。
Named queries
我們可以使用 _name 為一個 filter 或 query 來取一個名字,比如:
GET twitter/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"city": {
"query": "北京",
"_name": "城市"
}
}
},
{
"match": {
"country": {
"query": "中國",
"_name": "國家"
}
}
}
],
"should": [
{
"match": {
"_id": {
"query": "1",
"_name": "ID"
}
}
}
]
}
}
}
返回結果:
"hits" : [
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.6305401,
"_source" : {
"user" : "雙榆樹-張三",
"message" : "今兒天氣不錯啊,出去轉轉去",
"uid" : 2,
"age" : 20,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市海淀區",
"location" : {
"lat" : "39.970718",
"lon" : "116.325747"
}
},
"matched_queries" : [
"國家",
"ID",
"城市"
]
},
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "2",
"_score" : 0.6305401,
"_source" : {
"user" : "東城區-老劉",
"message" : "出發,下一站雲南!",
"uid" : 3,
"age" : 30,
"city" : "北京",
"province" : "北京",
"country" : "中國",
"address" : "中國北京市東城區台基廠三條3號",
"location" : {
"lat" : "39.904313",
"lon" : "116.412754"
}
},
"matched_queries" : [
"國家",
"城市"
]
},
...
]
我們從上面的返回結果可以看出來多了一個叫做 matched_queries 的字段。在它的里面羅列了每個匹配了的查詢。第一個返回的查詢結果是三個都匹配了的,但是第二個來說就只有兩項是匹配的。
通配符查詢
我們可以使用 wildcard 查詢一個字符串里含有的字符:
GET twitter/_search
{
"query": {
"wildcard": {
"city.keyword": {
"value": "*海"
}
}
}
}
上面查詢在 city 這個關鍵字中含有“海”的文檔。上面的搜尋結果是:
{
"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" : "twitter",
"_type" : "_doc",
"_id" : "6",
"_score" : 1.0,
"_source" : {
"user" : "虹橋-老吳",
"message" : "好友來了都今天我生日,好友來了,什么 birthday happy 就成!",
"uid" : 7,
"age" : 90,
"city" : "上海",
"province" : "上海",
"country" : "中國",
"address" : "中國上海市閔行區",
"location" : {
"lat" : "31.175927",
"lon" : "121.383328"
}
}
}
]
}
}
我們可以看到查到 city 為 “上海” 的文檔。
Disjunction max 查詢
返回與一個或多個包在一起的查詢(稱為查詢子句或子句)匹配的文檔。
如果返回的文檔與多個查詢子句匹配,則 dis_max 查詢為該文檔分配來自任何匹配子句的最高相關性得分,並為任何其他匹配子查詢分配平局打破增量。
你可以使用 dis_max 在以不同 boost 因子映射的字段中搜索術語。比如:
GET twitter/_search
{
"query": {
"dis_max": {
"queries": [
{
"term": {
"city.keyword": "北京"
}
},
{
"match": {
"address": "北京"
}
}
],
"tie_breaker": 0.7
}
}
}
在上面的 dis_max 查詢中,它將返回任何一個在 queries 中所定的查詢的文檔。每個匹配分分數是按照如下的規則來進行計算的:
如果一個文檔匹配其中的一個或多個查詢,那么最終的得分將以其中最高的那個得分來進行計算
在默認的情況下,tie_breaker 的值為0。它可以是 0 到 1.0 之間的數
如果文檔匹配多個子句,則 dis_max 查詢將計算該文檔的相關性得分,如下所示:
從具有最高分數的匹配子句中獲取相關性分數。
將來自其他任何匹配子句的得分乘以 tie_breaker 值。
將最高分數加到相乘的分數上。
如果 tie_breaker 值大於0.0,則所有匹配子句均計數,但得分最高的子句計數最高。
SQL 查詢
對於與很多已經習慣用 RDMS 數據庫的工作人員,他們更喜歡使用 SQL 來進行查詢。Elasticsearch 也對 SQL 有支持:
GET /_sql?
{
"query": """
SELECT * FROM twitter
WHERE age = 30
"""
}
通過這個查詢,我們可以找到所有在年齡等於30的用戶。在個搜索中,我們使用了 SQL 語句。利用 SQL 端點我們可以很快地把我們的 SQL 知識轉化為 Elasticsearch 的使用場景中來。我們可以通過如下的方法得到它對應的 DSL 語句:
GET /_sql/translate
{
"query": """
SELECT * FROM twitter
WHERE age = 30
"""
}
我們得到的結果是:
{
"size" : 1000,
"query" : {
"term" : {
"age" : {
"value" : 30,
"boost" : 1.0
}
}
},
"_source" : {
"includes" : [
"address",
"message",
"region",
"script.source",
"user"
],
"excludes" : [ ]
},
"docvalue_fields" : [
{
"field" : "age"
},
{
"field" : "city"
},
{
"field" : "country"
},
{
"field" : "location"
},
{
"field" : "province"
},
{
"field" : "script.params.value"
},
{
"field" : "uid"
}
],
"sort" : [
{
"_doc" : {
"order" : "asc"
}
}
]
}
如果你想了解更多關於Elasticsearch EQL,請參閱我的另外一篇文章 “Elasticsearch SQL介紹及實例”。
Multi Search API
使用單個 API 請求執行幾次搜索。這個 API 的好處是節省 API 的請求個數,把多個請求放到一個 API 請求中來實現。
為了說明問題的方便,我們可以多加一個叫做 twitter1 的 index。它的內容如下:
POST _bulk
{"index":{"_index":"twitter1","_id":1}}
{"user":"張慶","message":"今兒天氣不錯啊,出去轉轉去","uid":2,"age":20,"city":"重慶","province":"重慶","country":"中國","address":"中國重慶地區","location":{"lat":"39.970718","lon":"116.325747"}}
這樣在我們的 Elasticsearch 中就有兩個索引了。我們可以做如下的 _msearch。
GET twitter/_msearch
{"index":"twitter"}
{"query":{"match_all":{}},"from":0,"size":1}
{"index":"twitter"}
{"query":{"bool":{"filter":{"term":{"city.keyword":"北京"}}}}, "size":1}
{"index":"twitter1"}
{"query":{"match_all":{}}}
上面我們通過 _msearch 終點來實現在一個 API 請求中做多個查詢,對多個 index 進行同時操作。顯示結果為:
多個索引操作
在上面我們引入了另外一個索引 twitter1。在實際的操作中,我們可以通過通配符,或者直接使用多個索引來進行搜索:
GET twitter*/_search
上面的操作是對所有的以 twitter 為開頭的索引來進行搜索,顯示的結果是在所有的 twitter 及 twitter1 中的文檔:
GET /twitter,twitter1/_search
也可以做同樣的事。在寫上面的查詢的時候,在兩個索引之間不能加入空格,比如:
GET /twitter, twitter1/_search
上面的查詢並不能返回你所想要的結果。
Profile API
Profile API 是調試工具。 它添加了有關執行的詳細信息搜索請求中的每個組件。 它為用戶提供有關搜索的每個步驟的洞察力
請求執行並可以幫助確定某些請求為何緩慢。
GET twitter/_search
{
"profile": "true",
"query": {
"match": {
"city": "北京"
}
}
}
在上面,我們加上了 "profile":"true" 后,除了顯示搜索的結果之外,還顯示 profile 的信息:
"profile" : {
"shards" : [
{
"id" : "[ZXGhn-90SISq1lePV3c1sA][twitter][0]",
"searches" : [
{
"query" : [
{
"type" : "BooleanQuery",
"description" : "city:北 city:京",
"time_in_nanos" : 1390064,
"breakdown" : {
"set_min_competitive_score_count" : 0,
"match_count" : 5,
"shallow_advance_count" : 0,
"set_min_competitive_score" : 0,
"next_doc" : 31728,
"match" : 3337,
"next_doc_count" : 5,
"score_count" : 5,
"compute_max_score_count" : 0,
"compute_max_score" : 0,
"advance" : 22347,
"advance_count" : 1,
"score" : 16639,
"build_scorer_count" : 2,
"create_weight" : 342219,
"shallow_advance" : 0,
"create_weight_count" : 1,
"build_scorer" : 973775
},
"children" : [
{
"type" : "TermQuery",
"description" : "city:北",
"time_in_nanos" : 107949,
"breakdown" : {
"set_min_competitive_score_count" : 0,
"match_count" : 0,
"shallow_advance_count" : 3,
"set_min_competitive_score" : 0,
"next_doc" : 0,
"match" : 0,
"next_doc_count" : 0,
"score_count" : 5,
"compute_max_score_count" : 3,
"compute_max_score" : 11465,
"advance" : 3477,
"advance_count" : 6,
"score" : 5793,
"build_scorer_count" : 3,
"create_weight" : 34781,
"shallow_advance" : 18176,
"create_weight_count" : 1,
"build_scorer" : 34236
}
},
{
"type" : "TermQuery",
"description" : "city:京",
"time_in_nanos" : 49929,
"breakdown" : {
"set_min_competitive_score_count" : 0,
"match_count" : 0,
"shallow_advance_count" : 3,
"set_min_competitive_score" : 0,
"next_doc" : 0,
"match" : 0,
"next_doc_count" : 0,
"score_count" : 5,
"compute_max_score_count" : 3,
"compute_max_score" : 5162,
"advance" : 15645,
"advance_count" : 6,
"score" : 3795,
"build_scorer_count" : 3,
"create_weight" : 13562,
"shallow_advance" : 1087,
"create_weight_count" : 1,
"build_scorer" : 10657
}
}
]
}
],
"rewrite_time" : 17930,
"collector" : [
{
"name" : "CancellableCollector",
"reason" : "search_cancelled",
"time_in_nanos" : 204082,
"children" : [
{
"name" : "SimpleTopScoreDocCollector",
"reason" : "search_top_hits",
"time_in_nanos" : 23347
}
]
}
]
}
],
"aggregations" : [ ]
}
]
}
從上面我們可以看出來,這個搜索是搜索了“北”及“京”,而不是把北京作為一個整體來進行搜索的。我們可以在以后的文檔中可以學習使用中文分詞器來進行分詞搜索。有興趣的同學可以把上面的搜索修改為 city.keyword 來看看。如果你對分詞感興趣的話,請參閱我的文章 “Elastic:菜鳥上手指南” 中的分詞器部分。
除了上面的通過命令來進行 profile 以外,我們也可以通過 Kibana 的 UI 對我們的搜索進行 profile:
在很多的時候這個可視化的工具更具直觀性。
原文鏈接:Elastic 中國社區官方博客
