Elasticsearch文檔查詢


簡單數據集

到目前為止,已經了解了基本知識,現在我們嘗試用更逼真的數據集,這兒已經准備好了一份虛構的JSON,關於客戶銀行賬戶信息的。每個文檔的結構如下:

{
    "account_number": 0,
    "balance": 16623,
    "firstname": "Bradshaw",
    "lastname": "Mckenzie",
    "age": 29,
    "gender": "F",
    "address": "244 Columbus Place",
    "employer": "Euron",
    "email": "bradshawmckenzie@euron.com",
    "city": "Hobucken",
    "state": "CO"
}

出於好奇,我從www.json-generator.com/生成了這些數據,請忽略數據的實際值和語義,因為這些都是隨機生成的。

加載樣本數據集

可以從這里下載示例數據集(accounts.json),解壓到當前目錄,然后用以下方式把它加載到集群中

curl -H "Content-Type: application/json" -XPOST 'localhost:9200/bank/account/_bulk?pretty&refresh' --data-binary "@accounts.json"
curl 'localhost:9200/_cat/indices?v'

返回內容如下:

health status index   uuid                   pri rep docs.count docs.deleted store.size pri.store.size
yellow open   .kibana XYZPR5XGQGWj8YlyZ1et_w   1   1          1            0      3.1kb          3.1kb
yellow open   bank    uoTQIb3GSDOH08CmsIy66A   5   1        999            0    639.5kb        639.5kb

這意味着我們已經成功批量索引999個文檔到bank索引下(類型為account )。

注意,上面的操作不能在kibana中執行,需要使用curl

具體操作是,把下載的json文檔放在和curl.exe相同的目錄,然后打開命令提示符定位到curl.exe所在目錄,然后粘貼以下命令(我的curl版本是7.53.1,需要改成下面的方式才能執行成功),回車即可

curl -H "Content-Type: application/json" -XPOST localhost:9200/bank/account/_bulk?pretty --data-binary "@accounts.json"

查詢API

運行查詢有兩種方式,一是通過 REST request URI 方式發送查詢參數,二是通過 REST request body 。方式二更為靈活,可以使用可讀性好的JSON 格式定義你的查詢條件,下面我們針對方式一舉個例子,以后的教程都使用方式二。

REST API查詢條件放在_search之后,以下例子返回 bank 索引中的所有文檔:

GET /bank/_search?q=*&sort=account_number:asc&pretty
bank 表示查詢bank索引中的文檔, _search 后面跟的是查詢條件,q=* 參數指示 Elasticsearch 匹配索引中的所有文檔。 sort=account_number:asc 參數指示使用 account_number 對結果進行升序排序。 pretty 參數告訴 Elasticsearch 返回漂亮的JSON結果。

返回部分內容如下:

{
  "took" : 63,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1000,
    "max_score" : null,
    "hits" : [ {
      "_index" : "bank",
      "_type" : "account",
      "_id" : "0",
      "sort": [0],
      "_score" : null,
      "_source" : {"account_number":0,"balance":16623,"firstname":"Bradshaw","lastname":"Mckenzie","age":29,"gender":"F","address":"244 Columbus Place","employer":"Euron","email":"bradshawmckenzie@euron.com","city":"Hobucken","state":"CO"}
    }, {
      "_index" : "bank",
      "_type" : "account",
      "_id" : "1",
      "sort": [1],
      "_score" : null,
      "_source" : {"account_number":1,"balance":39225,"firstname":"Amber","lastname":"Duke","age":32,"gender":"M","address":"880 Holmes Lane","employer":"Pyrami","email":"amberduke@pyrami.com","city":"Brogan","state":"IL"}
    }, ...
    ]
  }
}

至於返回內容,我們看到以下部分:

  • took -  Elasticsearch  執行查詢的時間(以毫秒為單位)
  • timed_out - 告訴我們查詢是否超時
  • _shards - 告訴我們查詢了多少個分片,以及查詢成功/失敗的分片數量
  • hits - 查詢結果
  • hits.total - 符合我們查詢條件的文檔總數
  • hits.hits - 實際查詢結果數組(默認為前10個文檔)
  • hits.sort - 對結果進行排序的鍵(如果沒提供,則默認使用_score進行排序)
  • hits._scoremax_score-現在先忽略這些字段

使用方式二執行上面查詢如下

GET /bank/_search
{
  "query": { "match_all": {} },
  "sort": [
    { "account_number": "asc" }
  ]
}

不同點是我們用json格式的請求體代替了_search api uri中的q=*參數。我們將會在后面的內容討論json格式的查詢。

注意,當我們接收到返回結果的時候, elasticsearch 已經完全處理了這個請求,不會維護任何的服務器端的資源或者在結果中打開游標。這與許多其他的平台形成鮮明的對比(比如sql的游標)

查詢語言介紹

 Elasticsearch  t提供了一種 JSON-style 的特定領域語言用來執行查詢,稱為 Query DSL,該查詢語言十分全面,初看可能覺得有點嚇人。事實上,學習它的最好方式就是從幾個基本的例子開始。回到上一個例子,我們執行了這個查詢:

GET /bank/_search
{
  "query": { "match_all": {} }
}

上面的 query   部分告訴我們查詢定義是什么,  match_all 部分僅僅是我們想運行的查詢的類型,也就是查詢指定索引下的所有文檔。

除了查詢參數以外,也可以通過其他參數影響查詢結果。比如前面的 sort 指定排序字段,下面通過 size 指定返回結果數

GET /bank/_search
{
  "query": { "match_all": {} },
  "size": 1
}

注意 size 如果不指定,默認是10。

下面的例子匹配所有,並且返回第11到20之間的文檔

GET /bank/_search
{
  "query": { "match_all": {} },
  "from": 10,
  "size": 10
}

 from參數(最小值是0,不是1)指定返回文檔的起始文檔的索引, size 參數指定一共返回多少個文檔。這個特性對實現分頁非常有用。如果 from 沒有指定,默認值是0。

下面的例子匹配所有,並且通過 balance 字段對結果進行降序排序,返回前10條(默認 size )文檔。

GET /bank/_search
{
  "query": { "match_all": {} },
  "sort": { "balance": { "order": "desc" } }
}

執行查詢

接下來我們進一步探討Query DSL。首先看一下返回的文檔字段。默認情況下,完整的JSON文檔作為所有搜索的一部分返回。

默認情況下,完整的JSON文檔作為所有搜索的一部分返回。文檔原始內容被稱為源(查詢結果中的_source字段)。如果不希望返回整個源文檔,也可以請求僅幾個字段被返回。

以下示例顯示如何返回兩個字段(_source內), account_number  balance

GET /bank/_search
{
  "query": { "match_all": {} },
  "_source": ["account_number", "balance"],
  "size": 1
  
}

返回內容如下:

{
  "took": 3,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 999,
    "max_score": 1,
    "hits": [
      {
        "_index": "bank",
        "_type": "account",
        "_id": "25",
        "_score": 1,
        "_source": {
          "account_number": 25,
          "balance": 40540
        }
      }
    ]
  }
}

以上的例子僅僅減少了 _source 里的字段,返回的字段 account_number 和 balance 仍然包含在 _source

如果你之前有SQL背景,上述在概念上與SQL SELECT FROM字段列表有些相似。

現在來看看查詢部分。通過前面的示例,我們已經學會了如何使用 match_all 查詢來匹配所有文檔。現在介紹一個名為match查詢的新查詢,可以將其視為基本的字段搜索查詢(即針對特定字段或一組字段進行搜索)。

以下示例返回的 account_number 為20:

GET /bank/_search
{
  "query": { "match": { "account_number": 20 } }
}

返回結果:

{
  "took": 15,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 1,
    "hits": [
      {
        "_index": "bank",
        "_type": "account",
        "_id": "20",
        "_score": 1,
        "_source": {
          "account_number": 20,
          "balance": 16418,
          "firstname": "Elinor",
          "lastname": "Ratliff",
          "age": 36,
          "gender": "M",
          "address": "282 Kings Place",
          "employer": "Scentric",
          "email": "elinorratliff@scentric.com",
          "city": "Ribera",
          "state": "WA"
        }
      }
    ]
  }
}

以下實例返回 address 中包含"mill"的所有賬戶:

GET /bank/_search
{
  "query": { "match": { "address": "mill" } }
}

以下示例返回address中包含"mill"或者"lane"的所有賬戶:

GET /bank/_search
{
  "query": { "match": { "address": "mill lane" } }
}

以下示例是matchmatch_phrase)的一個變體,返回在地址中包含短語"mill lane"的所有帳戶:

GET /bank/_search
{
  "query": { "match_phrase": { "address": "mill lane" } }
}

下面介紹bool(ean) query 布爾查詢允許我們把多個 match 查詢合並到一個查詢中。

以下示例由兩個 match  查詢組成,返回 address 中既包含"mill" 又包含"lane" 的所有賬戶:

GET /bank/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "address": "mill" } },
        { "match": { "address": "lane" } }
      ]
    }
  }
}

在上面的示例中, bool must 里面的所有查詢條件必須都為真時才會被匹配。

相比之下,下面的示例由兩個match查詢組成,並返回在地址中包含"mill"或"lane"的所有帳戶:

GET /bank/_search
{
  "query": {
    "bool": {
      "should": [
        { "match": { "address": "mill" } },
        { "match": { "address": "lane" } }
      ]
    }
  }
}

在上面的例子中, bool should 子句指定了一個查詢列表,只要其中一個查詢為真,文檔就會被匹配。

以下示例由兩個match查詢組成,並返回在地址中既不包含"mill"也不包含"lane"的所有帳戶:

GET /bank/_search
{
  "query": {
    "bool": {
      "must_not": [
        { "match": { "address": "mill" } },
        { "match": { "address": "lane" } }
      ]
    }
  }
}

在上面的例子中,bool must_not 子句指定一個查詢列表,只有查詢列表中的條件都為假的時候才會被匹配。

也可以把 must,should,must_not 同時組合到bool子句。此外,我們也可以組合bool 到任何一個bool子句中,實現復雜的多層bool子句嵌套邏輯。

下面的例子返回所有年齡是40歲但不居住在ID(Idaho)的賬戶:

GET /bank/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "age": "40" } }
      ],
      "must_not": [
        { "match": { "state": "ID" } }
      ]
    }
  }
}

執行過濾

前面我們跳過了一點細節,文檔得分(也就是在搜索結果中的 _score 字段)。分數是一個數值,它是文檔與我們指定的搜索查詢匹配的相對度量。分數越高,文檔越相關,分數越低,文檔的相關性越低。但查詢並不總是需要產生分數,特別是當它們僅用於"過濾"文檔集時。 Elasticsearch 會檢測這些情況,並自動優化查詢執行,以免計算無用的分數。

bool查詢支持filter子句,它允許你使用一個查詢語句去限制其它子句的匹配結果,同時不會計算文檔的得分。例如,我們來介紹一下 range query, 它允許我們通過一個范圍值去過濾文檔。通常用於數字或日期過濾。

以下示例使用布爾查詢返回余額在20000到30000之間(包括端值)的所有帳戶。換句話說,我們想找到余額大於或等於20000且小於等於30000的賬戶。

GET /bank/_search
{
  "query": {
    "bool": {
      "must": { "match_all": {} },
      "filter": {
        "range": {
          "balance": {
            "gte": 20000,
            "lte": 30000
          }
        }
      }
    }
  }
}

仔細分析上面的例子,bool查詢包含了一個match_all查詢(查詢部分)和一個range查詢(過濾部分)。我們也可以用任何其它的查詢語句代替查詢和過濾部分的語句。對於上面的例子,因為所有文檔都是指定范圍之內的,他們從某種意義上來說是等價的(equally),即他們的相關度都是一樣的(filter子句查詢,不會改變得分)。

除了 match_all,match,bool,range查詢,還有很多種類的查詢,但我們不在這里一一介紹。從現在開始,我們對查詢已經有一個基礎的了解,把學到的知識應用到其他查詢類型應該也沒什么難度。

執行聚合

聚合提供從數據中分組和提取統計信息的功能。理解聚合的最簡單的方法是將其大致等同於SQL GROUP BY和SQL聚合函數。在 Elasticsearch 中,可以返回匹配搜索的同時返回聚合結果,在一個響應中將所有匹配的結果和聚合結果同時返回。這是非常強大和高效的,可以降低網絡請求的次數。

以下示例通過state字段進行分組,並按照count 降序排序,返回前10(默認值)條數據:

GET /bank/_search
{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state.keyword"
      }
    }
  }
}

在SQL中,上述聚合在概念上類似於:

SELECT state, COUNT(*) FROM bank GROUP BY state ORDER BY COUNT(*) DESC

返回內容(僅部分)如下:

{
  "took": 50,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 999,
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "group_by_state": {
      "doc_count_error_upper_bound": 20,
      "sum_other_doc_count": 770,
      "buckets": [
        {
          "key": "ID",
          "doc_count": 27
        },
        {
          "key": "TX",
          "doc_count": 27
        },
        {
          "key": "AL",
          "doc_count": 25
        },
        {
          "key": "MD",
          "doc_count": 25
        },
        {
          "key": "TN",
          "doc_count": 23
        },
        {
          "key": "MA",
          "doc_count": 21
        },
        {
          "key": "NC",
          "doc_count": 21
        },
        {
          "key": "ND",
          "doc_count": 21
        },
        {
          "key": "MO",
          "doc_count": 20
        },
        {
          "key": "AK",
          "doc_count": 19
        }
      ]
    }
  }
}

可以看到,有27個賬戶居住在ID(Idaho),27個賬戶居住在TX(Texas),25個賬戶居住在AL(Alabama)等等。

注意,設置size=0 是為了不顯示搜索結果,因為我們僅僅想看返回的聚合結果。

基於上述例子,下面的例子除了分組還會計算每個州的賬戶的平均余額:

GET /bank/_search
{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state.keyword"
      },
      "aggs": {
        "average_balance": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  }
}

返回內容(僅部分)如下:

{
  "took": 32,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 999,
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "group_by_state": {
      "doc_count_error_upper_bound": 20,
      "sum_other_doc_count": 770,
      "buckets": [
        {
          "key": "ID",
          "doc_count": 27,
          "average_balance": {
            "value": 24368.777777777777
          }
        },
        {
          "key": "TX",
          "doc_count": 27,
          "average_balance": {
            "value": 27462.925925925927
          }
        },
        {
          "key": "AL",
          "doc_count": 25,
          "average_balance": {
            "value": 25739.56
          }
        },
        {
          "key": "MD",
          "doc_count": 25,
          "average_balance": {
            "value": 24963.52
          }
        },
        {
          "key": "TN",
          "doc_count": 23,
          "average_balance": {
            "value": 29796.782608695652
          }
        },
        {
          "key": "MA",
          "doc_count": 21,
          "average_balance": {
            "value": 29726.47619047619
          }
        },
        {
          "key": "NC",
          "doc_count": 21,
          "average_balance": {
            "value": 26785.428571428572
          }
        },
        {
          "key": "ND",
          "doc_count": 21,
          "average_balance": {
            "value": 26303.333333333332
          }
        },
        {
          "key": "MO",
          "doc_count": 20,
          "average_balance": {
            "value": 24151.8
          }
        },
        {
          "key": "AK",
          "doc_count": 19,
          "average_balance": {
            "value": 24088.63157894737
          }
        }
      ]
    }
  }
}

注意我們是如何把average_balance聚合嵌入到group_by_state聚合中的。在所有的聚合中這是一種普遍的模式。你可以按你的需求隨意的在聚合中嵌套聚合子句,匯總你的數據。

基於上面的例子,以下示例加入了按每個州的賬戶平均余額降序排序:

GET /bank/_search
{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state.keyword",
        "order": {
          "average_balance": "desc"
        }
      },
      "aggs": {
        "average_balance": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  }
}

以下示例演示了如何按年齡段(20-29歲,30-39歲和40-49歲),然后按性別分組,然后最終得到每個年齡段的男女平均賬戶余額:

GET /bank/_search
{
  "size": 0,
  "aggs": {
    "group_by_age": {
      "range": {
        "field": "age",
        "ranges": [
          {
            "from": 20,
            "to": 30
          },
          {
            "from": 30,
            "to": 40
          },
          {
            "from": 40,
            "to": 50
          }
        ]
      },
      "aggs": {
        "group_by_gender": {
          "terms": {
            "field": "gender.keyword"
          },
          "aggs": {
            "average_balance": {
              "avg": {
                "field": "balance"
              }
            }
          }
        }
      }
    }
  }
}

返回結果如下:

{
  "took": 21,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 999,
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "group_by_age": {
      "buckets": [
        {
          "key": "20.0-30.0",
          "from": 20,
          "to": 30,
          "doc_count": 450,
          "group_by_gender": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": "M",
                "doc_count": 231,
                "average_balance": {
                  "value": 27400.982683982686
                }
              },
              {
                "key": "F",
                "doc_count": 219,
                "average_balance": {
                  "value": 25341.260273972603
                }
              }
            ]
          }
        },
        {
          "key": "30.0-40.0",
          "from": 30,
          "to": 40,
          "doc_count": 504,
          "group_by_gender": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": "F",
                "doc_count": 253,
                "average_balance": {
                  "value": 25670.869565217392
                }
              },
              {
                "key": "M",
                "doc_count": 251,
                "average_balance": {
                  "value": 24288.239043824702
                }
              }
            ]
          }
        },
        {
          "key": "40.0-50.0",
          "from": 40,
          "to": 50,
          "doc_count": 45,
          "group_by_gender": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": "M",
                "doc_count": 24,
                "average_balance": {
                  "value": 26474.958333333332
                }
              },
              {
                "key": "F",
                "doc_count": 21,
                "average_balance": {
                  "value": 27992.571428571428
                }
              }
            ]
          }
        }
      ]
    }
  }
}

還有許多其他聚合功能,將不再詳細介紹。如果您想進一步試驗聚合參考指南是一個很好的起點。

結論

 elasticsearch 是一個既簡單又復雜的產品。目前為止,我們已經學了如何使用REST API以及 elasticsearch 的基本概念和特性。希望這個教程可以讓你很好的理解 elasticsearch ,更重要的是,激勵你繼續學習后續教程要介紹的強大特性。

官方文檔

https://www.elastic.co/guide/en/elasticsearch/reference/current/_exploring_your_data.html

參考文檔

https://github.com/13428282016/elasticsearch-CN/wiki/es-gettting-started


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM