概述
在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
查询时,语句会变得类似如下的冗长的缩进代码。
操作符重载
为此,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
查询会破坏原始查询的语义,因为只有在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);
});
锁定bool查询
如果设置了任何查询元数据,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"));
两个右查询都不被锁定的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"));
发表评论
登录用户才能发表评论, 请 登 录 或者 注册