NEST编写bool查询

1485 发布于: 2021-05-18 读完约需 6 分钟

概述

在NEST中使用查询DSL时,编写bool查询会变得非常冗长。例如,使用一个带有两个should子句的bool查询:

登录后复制
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 indenting

操作符重载

为此,NEST引入了运算符重载,使复杂的bool查询更容易编写。重载操作符如下:

  • 二元或(|| )操作符
  • 二元与(&&)操作符
  • 一元非(!)操作符
  • 一元加(+)操作符

二元或(|| )操作符

使用重载的二元或(||)操作符,可以更简洁地表达带有should子句的bool查询。

上面的示例可以使用NEST的二元或操作符简写成如下语句:

登录后复制
var firstSearchResponse = client.Search<Project>(s => s .Query(q => q .Term(p => p.Name, "x") || q .Term(p => p.Name, "y") ) );

或者使用对象初始化器语法:

登录后复制
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": { "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") ) );

或者使用对象初始化器语法:

登录后复制
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

一元非操作符

NEST还提供了一元非操作符,你可以用它创建带有must_not子句的bool查询:

登录后复制
var firstSearchResponse = client.Search<Project>(s => s .Query(q => !q .Term(p => p.Name, "x") ) );

或者使用对象初始化器语法结构:

登录后复制
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" } } } ] } } }

两个一元非(!)操作符的查询可以与二元操作符(&&)组合,形成一个带有两个must_not子句的bool查询:

登录后复制
Assert( q => !q.Query() && !q.Query(), !Query && !Query, c => c.Bool.MustNot.Should().HaveCount(2));

一元加(+)操作符

在NEST中,可以使用一元加操作符(+)将查询转换为带有过滤子句的bool查询:

登录后复制
var firstSearchResponse = client.Search<Project>(s => s .Query(q => +q .Term(p => p.Name, "x") ) );

或者使用对象初始化器语法结构:

登录后复制
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" } } } ] } } }

这将在筛选器上下文中运行查询,这对于提高性能非常有用,因为查询的相关度评分不需要影响结果的顺序。

与一元非操作符(!)相似,用一元加操作符(+)标记的查询可以与二元与操作符(&&)组合,形成一个带有两个筛选子句的bool查询:

登录后复制
Assert( q => +q.Query() && +q.Query(), +Query && +Query, c => c.Bool.Filter.Should().HaveCount(2));

组合bool查询

当使用二元操作符(&&)组合多个查询(其中一些或所有查询都使用一元运算符)时,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

使用NEST构建单个bool查询的代码如下:

登录后复制
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 | |___must_not | |___term | |___filter |___term |___term

同时,你也可以使用混合查询:

登录后复制
bool(must=term, term, term) && !term

NEST构建代码如下:

登录后复制
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); });

将查询与二元或操作符(||)或should子句组合

根据前面的例子,当NEST遇到正在运行的bool查询只包含should子句时,它将把多个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子句现在就充当了一个增强因子,这意味着它们都不必须匹配,但如果匹配,该文档的相关性得分就会提高,从而在结果中出现得更高。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子句作为增强因子是一个非常强大的构造。你可以将一个实际的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查询会破坏原始查询的语义,因为只有在term5term6上匹配才会成功。

登录后复制
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); });

锁定bool查询

如果设置了任何查询元数据,NEST将不会组合bool查询,例如,如果设置了boostname等元数据,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"));

两个右查询都不被锁定的bool查询:

登录后复制
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"));

版权声明:本作品系原创,版权归码友网所有,如未经许可,禁止任何形式转载,违者必究。

发表评论

登录用户才能发表评论, 请 登 录 或者 注册