一、請求體查詢
請求體查詢
簡單查詢語句(lite)是一種有效的命令行_adhoc_查詢。但是,如果你想要善用搜索,你必須使用請求體查詢(request body search)API。之所以這么稱呼,是因為大多數的參數以JSON格式所容納而非查詢字符串。
請求體查詢(下文簡稱查詢),並不僅僅用來處理查詢,而且還可以高亮返回結果中的片段,並且給出幫助你的用戶找尋最好結果的相關數據建議。
空查詢
我們以最簡單的 search API開始,空查詢將會返回索引中所有的文檔。
GET /_search {}
- 這是一個空查詢數據。
同字符串查詢一樣,你可以查詢一個,多個或_all索引(indices)或類型(types):
GET /index_2014*/type1,type2/_search
{}
你可以使用from及size參數進行分頁:
GET /_search
{
"from": 30,
"size": 10
}
攜帶內容的GET請求?
任何一種語言(特別是js)的HTTP庫都不允許GET請求中攜帶交互數據。 事實上,有些用戶很驚訝GET請求中居然會允許攜帶交互數據。
真實情況是,http://tools.ietf.org/html/rfc7231#page-24[RFC 7231], 一份規定HTTP語義及內容的RFC中並未規定GET請求中允許攜帶交互數據! 所以,有些HTTP服務允許這種行為,而另一些(特別是緩存代理),則不允許這種行為。
Elasticsearch的作者們傾向於使用GET提交查詢請求,因為他們覺得這個詞相比POST來說,能更好的描述這種行為。 然而,因為攜帶交互數據的GET請求並不被廣泛支持,所以searchAPI同樣支持POST請求,類似於這樣:
POST /_search
{
"from": 30,
"size": 10
}
這個原理同樣應用於其他攜帶交互數據的GETAPI請求中。
二、結構化查詢 Query DSL
結構化查詢是一種靈活的,多表現形式的查詢語言。 Elasticsearch在一個簡單的JSON接口中用結構化查詢來展現Lucene絕大多數能力。 你應當在你的產品中采用這種方式進行查詢。它使得你的查詢更加靈活,精准,易於閱讀並且易於debug。
使用結構化查詢,你需要傳遞query參數:
GET /_search
{
"query": YOUR_QUERY_HERE
}
空查詢 - {} - 在功能上等同於使用match_all查詢子句,正如其名字一樣,匹配所有的文檔:
GET /_search
{
"query": {
"match_all": {}
}
}
查詢子句
一個查詢子句一般使用這種結構:
{
QUERY_NAME: {
ARGUMENT: VALUE,
ARGUMENT: VALUE,...
}
}
或指向一個指定的字段:
{
QUERY_NAME: {
FIELD_NAME: {
ARGUMENT: VALUE,
ARGUMENT: VALUE,...
}
}
}
例如,你可以使用match查詢子句用來找尋在tweet字段中找尋包含elasticsearch的成員:
{
"match": {
"tweet": "elasticsearch"
}
}
完整的查詢請求會是這樣:
GET /_search
{
"query": {
"match": {
"tweet": "elasticsearch"
}
}
}
合並多子句
查詢子句就像是搭積木一樣,可以合並簡單的子句為一個復雜的查詢語句,比如:
-
葉子子句(leaf clauses)(比如
match子句)用以在將查詢字符串與一個字段(或多字段)進行比較 - 復合子句(compound)用以合並其他的子句。例如,
bool子句允許你合並其他的合法子句,must,must_not或者should,如果可能的話:
{
"bool": {
"must": { "match": { "tweet": "elasticsearch" }},
"must_not": { "match": { "name": "mary" }},
"should": { "match": { "tweet": "full text" }}
}
}
復合子句能合並 任意其他查詢子句,包括其他的復合子句。 這就意味着復合子句可以相互嵌套,從而實現非常復雜的邏輯。
以下實例查詢的是
郵件正文中含有“business opportunity”字樣的星標郵件
或
收件箱中正文中含有“business opportunity”字樣的非垃圾郵件:
{
"bool": {
"must": { "match": { "email": "business opportunity" }},
"should": [
{ "match": { "starred": true }},
{ "bool": {
"must": { "folder": "inbox" }},
"must_not": { "spam": true }}
}}
],
"minimum_should_match": 1
}
}
不用擔心這個例子的細節,我們將在后面詳細解釋它。 重點是復合子句可以合並多種子句為一個單一的查詢,無論是葉子子句還是其他的復合子句。
三、查詢與過濾
前面我們講到的是關於結構化查詢語句,事實上我們可以使用兩種結構化語句: 結構化查詢(Query DSL)和結構化過濾(Filter DSL)。 查詢與過濾語句非常相似,但是它們由於使用目的不同而稍有差異。
一條過濾語句會詢問每個文檔的字段值是否包含着特定值:
-
created的日期范圍是否在2013到2014? -
status字段中是否包含單詞 "published" ? lat_lon字段中的地理位置與目標點相距是否不超過10km ?
一條查詢語句與過濾語句相似,但問法不同:
查詢語句會詢問每個文檔的字段值與特定值的匹配程度如何?
查詢語句的典型用法是為了找到文檔:
-
查找與
full text search這個詞語最佳匹配的文檔 -
查找包含單詞
run,但是也包含runs,running,jog或sprint的文檔 -
同時包含着
quick,brown和fox--- 單詞間離得越近,該文檔的相關性越高 - 標識着
lucene,search或java--- 標識詞越多,該文檔的相關性越高
一條查詢語句會計算每個文檔與查詢語句的相關性,會給出一個相關性評分 _score,並且 按照相關性對匹配到的文檔進行排序。 這種評分方式非常適用於一個沒有完全配置結果的全文本搜索。
性能差異
使用過濾語句得到的結果集 -- 一個簡單的文檔列表,快速匹配運算並存入內存是十分方便的, 每個文檔僅需要1個字節。這些緩存的過濾結果集與后續請求的結合使用是非常高效的。
查詢語句不僅要查找相匹配的文檔,還需要計算每個文檔的相關性,所以一般來說查詢語句要比 過濾語句更耗時,並且查詢結果也不可緩存。
幸虧有了倒排索引,一個只匹配少量文檔的簡單查詢語句在百萬級文檔中的查詢效率會與一條經過緩存的過濾語句旗鼓相當,甚至略占上風。
但是一般情況下,一條經過緩存的過濾查詢要遠勝一條查詢語句的執行效率。
過濾語句的目的就是縮小匹配的文檔結果集,所以需要仔細檢查過濾條件。
什么情況下使用
原則上來說,使用查詢語句做全文本搜索或其他需要進行相關性評分的時候,剩下的全部用過濾語句。
四、最重要的查詢過濾語句
Elasticsearch 提供了豐富的查詢過濾語句,而有一些是我們較常用到的。 我們將會在后續的《深入搜索》中展開討論,現在我們快速的介紹一下 這些最常用到的查詢過濾語句。
term 過濾
term主要用於精確匹配哪些值,比如數字,日期,布爾值或 not_analyzed的字符串(未經分析的文本數據類型):
{ "term": { "age": 26 }}
{ "term": { "date": "2014-09-01" }}
{ "term": { "public": true }}
{ "term": { "tag": "full_text" }}
terms 過濾
terms 跟 term 有點類似,但 terms 允許指定多個匹配條件。 如果某個字段指定了多個值,那么文檔需要一起去做匹配:
{
"terms": {
"tag": [ "search", "full_text", "nosql" ]
}
}
range 過濾
range過濾允許我們按照指定范圍查找一批數據:
{
"range": {
"age": {
"gte": 20,
"lt": 30
}
}
}
范圍操作符包含:
gt :: 大於
gte:: 大於等於
lt :: 小於
lte:: 小於等於
exists 和 missing 過濾
exists 和 missing 過濾可以用於查找文檔中是否包含指定字段或沒有某個字段,類似於SQL語句中的IS_NULL條件
{
"exists": {
"field": "title"
}
}
這兩個過濾只是針對已經查出一批數據來,但是想區分出某個字段是否存在的時候使用。
bool 過濾
bool 過濾可以用來合並多個過濾條件查詢結果的布爾邏輯,它包含一下操作符:
must :: 多個查詢條件的完全匹配,相當於 and。
must_not :: 多個查詢條件的相反匹配,相當於 not。
should :: 至少有一個查詢條件匹配, 相當於 or。
這些參數可以分別繼承一個過濾條件或者一個過濾條件的數組:
{
"bool": {
"must": { "term": { "folder": "inbox" }},
"must_not": { "term": { "tag": "spam" }},
"should": [
{ "term": { "starred": true }},
{ "term": { "unread": true }}
]
}
}
match_all 查詢
使用match_all 可以查詢到所有文檔,是沒有查詢條件下的默認語句。
{
"match_all": {}
}
此查詢常用於合並過濾條件。 比如說你需要檢索所有的郵箱,所有的文檔相關性都是相同的,所以得到的_score為1
match 查詢
match查詢是一個標准查詢,不管你需要全文本查詢還是精確查詢基本上都要用到它。
如果你使用 match 查詢一個全文本字段,它會在真正查詢之前用分析器先分析match一下查詢字符:
{
"match": {
"tweet": "About Search"
}
}
如果用match下指定了一個確切值,在遇到數字,日期,布爾值或者not_analyzed 的字符串時,它將為你搜索你給定的值:
{ "match": { "age": 26 }}
{ "match": { "date": "2014-09-01" }}
{ "match": { "public": true }}
{ "match": { "tag": "full_text" }}
提示: 做精確匹配搜索時,你最好用過濾語句,因為過濾語句可以緩存數據。
不像我們在《簡單搜索》中介紹的字符查詢,match查詢不可以用類似"+usid:2 +tweet:search"這樣的語句。 它只能就指定某個確切字段某個確切的值進行搜索,而你要做的就是為它指定正確的字段名以避免語法錯誤。
multi_match 查詢
multi_match查詢允許你做match查詢的基礎上同時搜索多個字段:
{
"multi_match": {
"query": "full text search",
"fields": [ "title", "body" ]
}
}
bool 查詢
bool 查詢與 bool 過濾相似,用於合並多個查詢子句。不同的是,bool 過濾可以直接給出是否匹配成功, 而bool 查詢要計算每一個查詢子句的 _score (相關性分值)。
must:: 查詢指定文檔一定要被包含。must_not:: 查詢指定文檔一定不要被包含。should:: 查詢指定文檔,有則可以為文檔相關性加分。
以下查詢將會找到 title 字段中包含 "how to make millions",並且 "tag" 字段沒有被標為 spam。 如果有標識為 "starred" 或者發布日期為2014年之前,那么這些匹配的文檔將比同類網站等級高:
{ "bool": { "must": { "match": { "title": "how to make millions" }}, "must_not": { "match": { "tag": "spam" }}, "should": [ { "match": { "tag": "starred" }}, { "range": { "date": { "gte": "2014-01-01" }}} ] } }
提示: 如果
bool查詢下沒有must子句,那至少應該有一個should子句。但是 如果有must子句,那么沒有should子句也可以進行查詢。
五、過濾查詢
查詢與過濾條件的合並
查詢語句和過濾語句可以放在各自的上下文中。 在 ElasticSearch API 中我們會看到許多帶有 query 或 filter 的語句。 這些語句既可以包含單條 query 語句,也可以包含一條 filter 子句。 換句話說,這些語句需要首先創建一個query或filter的上下文關系。
復合查詢語句可以加入其他查詢子句,復合過濾語句也可以加入其他過濾子句。 通常情況下,一條查詢語句需要過濾語句的輔助,全文本搜索除外。
所以說,查詢語句可以包含過濾子句,反之亦然。 以便於我們切換 query 或 filter 的上下文。這就要求我們在讀懂需求的同時構造正確有效的語句。
帶過濾的查詢語句
過濾一條查詢語句
比如說我們有這樣一條查詢語句:
{
"match": {
"email": "business opportunity"
}
}
然后我們想要讓這條語句加入 term 過濾,在收信箱中匹配郵件:
{
"term": {
"folder": "inbox"
}
}
search API中只能包含 query 語句,所以我們需要用 filtered 來同時包含 "query" 和 "filter" 子句:
{
"filtered": {
"query": { "match": { "email": "business opportunity" }},
"filter": { "term": { "folder": "inbox" }}
}
}
我們在外層再加入 query 的上下文關系,就是完整的結構化查詢:
GET /_search
{
"query": {
"filtered": {
"query": { "match": { "email": "business opportunity" }},
"filter": { "term": { "folder": "inbox" }}
}
}
}
單條過濾語句
在 query 上下文中,如果你只需要一條過濾語句,比如在匹配全部郵件的時候,你可以 省略 query 子句:
GET /_search
{
"query": {
"filtered": {
"filter": { "term": { "folder": "inbox" }}
}
}
}
如果一條查詢語句沒有指定查詢范圍,那么它默認使用 match_all 查詢,所以上面語句 的完整形式如下:
GET /_search
{
"query": {
"filtered": {
"query": { "match_all": {}},
"filter": { "term": { "folder": "inbox" }}
}
}
}
查詢語句中的過濾
有時候,你需要在 filter 的上下文中使用一個 query 子句。下面的語句就是一條帶有查詢功能 的過濾語句, 這條語句可以過濾掉看起來像垃圾郵件的文檔:
GET /_search
{
"query": {
"filtered": {
"filter": {
"bool": {
"must": { "term": { "folder": "inbox" }},
"must_not": {
"query": {
"match": { "email": "urgent business proposal" }
}
}
}
}
}
}
}
過濾語句中可以使用`query`查詢的方式代替 `bool` 過濾子句。
提示:
我們很少用到的過濾語句中包含查詢,保留這種用法只是為了語法的完整性。
只有在過濾中用到全文本匹配的時候才會使用這種結構。
