Version:5.xhtml
英文原文地址:Writing bool queriesgit
在使用查詢 DSL 時,編寫 bool
查詢會很容易把代碼變得冗長。舉個栗子,使用一個包含兩個 should
子句的 bool
查詢github
var searchResults = this.Client.Search<Project>(s => s .Query(q => q .Bool(b => b .Should( bs => bs.Term(p => p.Name, "x"), bs => bs.Term(p => p.Name, "y") ) ) ) );
如今設想多層嵌套的 bool
查詢,你會意識到這很快就會成爲一個 hadouken(波動拳) 縮進的練習編程
因爲這個緣由,NEST 引入了運算符重載,使得更容易去編寫複雜的 bool
查詢。這些重載的運算符是:json
咱們會示例來演示這幾個運算符c#
使用重載的二元 ||
運算符,能夠更簡潔地表達含有 should
子句的 bool
查詢api
以前哈杜根的栗子如今變成了 Fluent API 的樣子elasticsearch
var firstSearchResponse = client.Search<Project>(s => s .Query(q => q .Term(p => p.Name, "x") || q .Term(p => p.Name, "y") ) );
使用 Object Initializer 語法編程語言
var secondSearchResponse = client.Search<Project>(new SearchRequest<Project> { Query = new TermQuery { Field = Field<Project>(p => p.Name), Value = "x" } || new TermQuery { Field = Field<Project>(p => p.Name), Value = "y" } });
二者都會生成以下 JSON 查詢 DSLide
{ "query": { "bool": { "should": [ { "term": { "name": { "value": "x" } } }, { "term": { "name": { "value": "y" } } } ] } } }
重載的二元 &&
運算符用於將多個查詢組合在一塊兒。當要組合的查詢沒有應用任何一元運算符時,生成的查詢是一個包含 must
子句的 bool
查詢
var firstSearchResponse = client.Search<Project>(s => s .Query(q => q .Term(p => p.Name, "x") && q .Term(p => p.Name, "y") ) );
使用 Object Initializer 語法
var secondSearchResponse = client.Search<Project>(new SearchRequest<Project> { Query = new TermQuery { Field = Field<Project>(p => p.Name), Value = "x" } && new TermQuery { Field = Field<Project>(p => p.Name), Value = "y" } });
二者都會生成以下 JSON 查詢 DSL
{ "query": { "bool": { "must": [ { "term": { "name": { "value": "x" } } }, { "term": { "name": { "value": "y" } } } ] } } }
運算符重載會重寫原生的實現
term && term && term
會轉換成
bool |___must |___term |___bool |___must |___term |___term
能夠想象,隨着查詢變得愈來愈複雜,結果很快就會變得笨拙。NEST 是很聰明的,它會把多個 &&
查詢聯合成一個 bool
查詢
bool |___must |___term |___term |___term
以下所示
Assert( q => q.Query() && q.Query() && q.Query(), (1) Query && Query && Query, (2) c => c.Bool.Must.Should().HaveCount(3) (3) );
(1) 使用 Fluent API 將三個查詢 &&
在一塊兒
(2) 使用 Object Initializer 語法將三個查詢 &&
在一塊兒
(3) 斷言最終的 bool
查詢會包含 3 個 must
子句
NEST 使用一元 !
運算符建立包含 must_not
子句的 bool
查詢
var firstSearchResponse = client.Search<Project>(s => s .Query(q => !q .Term(p => p.Name, "x") ) );
使用 Object Initializer 語法
var secondSearchResponse = client.Search<Project>(new SearchRequest<Project> { Query = !new TermQuery { Field = Field<Project>(p => p.Name), Value = "x" } });
二者都會生成以下 JSON 查詢 DSL
{ "query": { "bool": { "must_not": [ { "term": { "name": { "value": "x" } } } ] } } }
用一元 !
運算符標記的兩個查詢可使用 and
運算符組合起來,從而造成一個包含兩個 must_not
子句的 bool
查詢
Assert( q => !q.Query() && !q.Query(), !Query && !Query, c => c.Bool.MustNot.Should().HaveCount(2));
可使用一元 +
運算符將查詢轉換爲帶有 filter
子句的 bool
查詢
var firstSearchResponse = client.Search<Project>(s => s .Query(q => +q .Term(p => p.Name, "x") ) );
使用 Object Initializer 語法
var secondSearchResponse = client.Search<Project>(new SearchRequest<Project> { Query = +new TermQuery { Field = Field<Project>(p => p.Name), Value = "x" } });
二者都會生成以下 JSON 查詢 DSL
{ "query": { "bool": { "filter": [ { "term": { "name": { "value": "x" } } } ] } } }
在篩選上下文中運行查詢,這在提升性能方面頗有用。由於不須要計算查詢的相關性評分來影響結果的順序。
一樣的,使用一元 +
運算符標記的查詢能夠和 &&
運算符組合在一塊兒,構成一個包含兩個 filter
子句的 bool
查詢
Assert( q => +q.Query() && +q.Query(), +Query && +Query, c => c.Bool.Filter.Should().HaveCount(2));
在使用二元 &&
運算符組合多個查詢時,若是某些或者所有的查詢都應用了一元運算符,NEST 仍然能夠把它們合併成一個 bool
查詢
參考下面這個 bool
查詢
bool |___must | |___term | |___term | |___term | |___must_not |___term
NEST 中能夠這樣構建
Assert( q => q.Query() && q.Query() && q.Query() && !q.Query(), Query && Query && Query && !Query, c=> { c.Bool.Must.Should().HaveCount(3); c.Bool.MustNot.Should().HaveCount(1); });
一個更復雜的栗子
term && term && term && !term && +term && +term
依然會生成下面這個結構的單個 bool
查詢
bool |___must | |___term | |___term | |___term | |___must_not | |___term | |___filter |___term |___term
Assert( q => q.Query() && q.Query() && q.Query() && !q.Query() && +q.Query() && +q.Query(), Query && Query && Query && !Query && +Query && +Query, c => { c.Bool.Must.Should().HaveCount(3); c.Bool.MustNot.Should().HaveCount(1); c.Bool.Filter.Should().HaveCount(2); });
你也能夠將使用重載運算符的查詢和真正的 bool
查詢混合在一塊兒
bool(must=term, term, term) && !term
仍然會合併爲一個 bool
查詢
Assert( q => q.Bool(b => b.Must(mq => mq.Query(), mq => mq.Query(), mq => mq.Query())) && !q.Query(), new BoolQuery { Must = new QueryContainer[] { Query, Query, Query } } && !Query, c => { c.Bool.Must.Should().HaveCount(3); c.Bool.MustNot.Should().HaveCount(1); });
就像以前的栗子,NEST 會把多個 should
或者 ||
查詢合併成一個包含多個 should
子句的 bool
查詢。
總而言之,這個
term || term || term
會變成
bool |___should |___term |___term |___term
可是,bool
查詢不會徹底遵循你從編程語言所指望的布爾邏輯
term1 && (term2 || term3 || term4)
不會變成
bool |___must | |___term1 | |___should |___term2 |___term3 |___term4
爲何會這樣?當一個 bool
查詢中只包含 should
子句時,至少會匹配一個。可是,當這個 bool
查詢還包含一個 must
子句時,應該將 should
子句看成一個 boost
因子,這意味着他們都不是必需匹配的。可是若是匹配,文檔的相關性評分會獲得提升,從而在結果中顯示更高的值。should
子句的行爲會由於 must
的存在而發生改變。
所以,再看看前面那個示例,你只能獲得包含 term1
的結果。這顯然不是使用運算符重載的目的。
爲此,NEST 將以前的查詢重寫成了:
bool |___must |___term1 |___bool |___should |___term2 |___term3 |___term4
Assert( q => q.Query() && (q.Query() || q.Query() || q.Query()), Query && (Query || Query || Query), c => { c.Bool.Must.Should().HaveCount(2); var lastMustClause = (IQueryContainer)c.Bool.Must.Last(); lastMustClause.Should().NotBeNull(); lastMustClause.Bool.Should().NotBeNull(); lastMustClause.Bool.Should.Should().HaveCount(3); });
添加圓括號,強制改變運算順序
在構建搜索查詢時,使用 should
子句做爲 boost
因子多是一個很是強大的構造方式。另外須要記住,你能夠將實際的 bool
查詢和 NEST 的重載運算符混合使用
還有一個微妙的狀況,NEST 不會盲目地合併兩個只包含 should
子句的 bool
查詢。考慮下面這個查詢
bool(should=term1, term2, term3, term4, minimum_should_match=2) || term5 || term6
若是 NEST 肯定二元 ||
運算符兩邊的查詢只包含 should
子句,並把它們合併在了一塊兒。這將給第一個 bool
查詢中的 minimum_should_match
參數賦予不一樣的含義。將其改寫爲包含 5 個 should
子句的 bool
查詢會破壞原始查詢的語義,由於只匹配了 term5
或者 term6
的文檔也應該被命中。
Assert( q => q.Bool(b => b .Should(mq => mq.Query(), mq => mq.Query(), mq => mq.Query(), mq => mq.Query()) .MinimumShouldMatch(2) ) || !q.Query() || q.Query(), new BoolQuery { Should = new QueryContainer[] { Query, Query, Query, Query }, MinimumShouldMatch = 2 } || !Query || Query, c => { c.Bool.Should.Should().HaveCount(3); var nestedBool = c.Bool.Should.First() as IQueryContainer; nestedBool.Bool.Should.Should().HaveCount(4); });
若是設置了任何一個查詢元數據,NEST 將不會合並 bool
查詢。舉個栗子,若是設置了 boost
或者 name
,NEST 會視其爲已被鎖定。
在這裏,咱們演示兩個鎖定的 bool
查詢
Assert( q => q.Bool(b => b.Name("leftBool").Should(mq => mq.Query())) || q.Bool(b => b.Name("rightBool").Should(mq => mq.Query())), new BoolQuery { Name = "leftBool", Should = new QueryContainer[] { Query } } || new BoolQuery { Name = "rightBool", Should = new QueryContainer[] { Query } }, c => AssertDoesNotJoinOntoLockedBool(c, "leftBool"));
鎖定右邊的查詢
Assert( q => q.Bool(b => b.Should(mq => mq.Query())) || q.Bool(b => b.Name("rightBool").Should(mq => mq.Query())), new BoolQuery { Should = new QueryContainer[] { Query } } || new BoolQuery { Name = "rightBool", Should = new QueryContainer[] { Query } }, c => AssertDoesNotJoinOntoLockedBool(c, "rightBool"));
鎖定左邊的查詢
Assert( q => q.Bool(b => b.Name("leftBool").Should(mq => mq.Query())) || q.Bool(b => b.Should(mq => mq.Query())), new BoolQuery { Name = "leftBool", Should = new QueryContainer[] { Query } } || new BoolQuery { Should = new QueryContainer[] { Query } }, c => AssertDoesNotJoinOntoLockedBool(c, "leftBool"));
若是你須要使用 bool
DSL 組合多個查詢,請考慮一下內容。
你能夠在循環中使用按位賦值來將多個查詢合併爲一個更大的查詢。
本例中,咱們使用 &=
賦值運算符建立一個含有 1000 個 must
子句的 bool
查詢。
var c = new QueryContainer(); var q = new TermQuery { Field = "x", Value = "x" }; for (var i = 0; i < 1000; i++) { c &= q; }
| Median| StdDev| Gen 0| Gen 1| Gen 2| Bytes Allocated/Op | 1.8507 ms| 0.1878 ms| 1,793.00| 21.00| -| 1.872.672,28
能夠看到,由於每次迭代咱們都須要從新評估 bool
查詢的合併能力,因此致使了大量的分配的產生。
因爲咱們事先已經知道了 bool
查詢的形狀,因此下面這個栗子要快的多
QueryContainer q = new TermQuery { Field = "x", Value = "x" }; var x = Enumerable.Range(0, 1000).Select(f => q).ToArray(); var boolQuery = new BoolQuery { Must = x };
| Median| StdDev| Gen 0| Gen 1| Gen 2| Bytes Allocated/Op | 31.4610 μs| 0.9495 μs| 439.00| -| -| 7.912,95
在性能和分配上的降低是巨大的!
若是你使用的是 NEST 2.4.6 以前的版本,經過循環把不少
bool
查詢分配給了一個更大的bool
查詢,客戶端沒有作好以最優化的方式合併結果的工做,而且在執行大約 2000 次迭代時可能會引起異常。這僅適用於按位分配許多bool
查詢,其餘查詢不受影響。從 NEST 2.4.6 開始,你能夠隨意組合大量的
bool
查詢。查閱 PR #2335 on github 瞭解更多信息。