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 了解更多信息。