NEST - 编写布尔查询
Writing bool queries
Version:5.x
英文原文地址:Writing bool queries
在使用查询 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(波动拳) 缩进的练习
Operator overloading
由于这个原因,NEST 引入了运算符重载,使得更容易去编写复杂的 bool
查询。这些重载的运算符是:
我们会示例来演示这几个运算符
Binary || operator
使用重载的二元 ||
运算符,可以更简洁地表达含有 should
子句的 bool
查询
之前哈杜根的栗子现在变成了 Fluent API 的样子
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": {
"should": [
{
"term": {
"name": {
"value": "x"
}
}
},
{
"term": {
"name": {
"value": "y"
}
}
}
]
}
}
}
Binary && operator
重载的二元 &&
运算符用于将多个查询组合在一起。当要组合的查询没有应用任何一元运算符时,生成的查询是一个包含 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
子句
Unary ! operator
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));
Unary + operator
可以使用一元 +
运算符将查询转换为带有 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));
Combining bool queries
在使用二元 &&
运算符组合多个查询时,如果某些或者全部的查询都应用了一元运算符,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);
});
Combining queries with || or should clauses
就像之前的栗子,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);
});
Locked bool queries
如果设置了任何一个查询元数据,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"));
Performance considerations
如果你需要使用 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 了解更多信息。
NEST - 编写布尔查询的更多相关文章
- Elasticsearch .Net Client NEST 多条件查询示例
Elasticsearch .Net Client NEST 多条件查询示例 /// <summary> /// 多条件搜索例子 /// </summary> public c ...
- ElasticSearch查询 第五篇:布尔查询
布尔查询是最常用的组合查询,不仅将多个查询条件组合在一起,并且将查询的结果和结果的评分组合在一起.当查询条件是多个表达式的组合时,布尔查询非常有用,实际上,布尔查询把多个子查询组合(combine)成 ...
- Elasticsearch布尔查询——bool
布尔查询允许我们利用布尔逻辑将较小的查询组合成较大的查询. 1.查询返回包含"mill"和"lane"的所有的账户 curl -XPOST 'localhost ...
- LINQ查询表达式(2) - 在 C# 中编写 LINQ 查询
在 C# 中编写 LINQ 查询 C# 中编写 LINQ 查询的三种方式: 使用查询语法. 使用方法语法. 组合使用查询语法和方法语法. // 查询语法 IEnumerable<int> ...
- es的查询、排序查询、分页查询、布尔查询、查询结果过滤、高亮查询、聚合函数、python操作es
今日内容概要 es的查询 Elasticsearch之排序查询 Elasticsearch之分页查询 Elasticsearch之布尔查询 Elasticsearch之查询结果过滤 Elasticse ...
- NEST - 编写查询
Writing queries Version:5.x 英文原文地址:Writing queries 将数据索引到了 Elasticsearch 之后,就可以准备搜索它们了.Elasticsearch ...
- 编写sql查询语句思路
编写查询语句思路/* 1.首先确定最终输出结果的列,包括几个方面:A.首先这些列来自于一个 表.还是多个表,如果是多个表则可能用到多表查询的(等值连接.不等值 连接.外连接.自连接):B.这些列是直接 ...
- NEST 根据id查询
想要在NEST里根据id查询 GET /employee/employee/1 可使用Get方法 public IGetResponse<employee> GetDoc() { var ...
- Elasticsearch查询——布尔查询Bool Query
Elasticsearch在2.x版本的时候把filter查询给摘掉了,因此在query dsl里面已经找不到filter query了.其实es并没有完全抛弃filter query,而是它的设计与 ...
随机推荐
- 带你玩转Visual Studio——带你了解VC++各种类型的工程
原文地址:http://blog.csdn.net/luoweifu/article/details/48816605 上一篇文章带你玩转Visual Studio——带你新建一个工程一文中提到新建一 ...
- 日期控件datepicker的使用
引入JS: <script type="text/javascript" src="static/my/js/bootstrap-datepicker.min.js ...
- Openssl编程--源码分析
Openssl编程 赵春平 著 Email: forxy@126.com 第一章 基础知识 8 1.1 对称算法 8 1.2 摘要算法 9 1.3 公钥算法 9 1.4 回调函数 11 第二章 ope ...
- javascript日期格式yyyyMMddHHmmss
1. function GetDateTimeToString() { var date_ = new Date(); var year = date_.getFullYear(); ; var da ...
- C#winform 窗体缩放自适应
1. 首先在窗体上放上一个Panel容器,并将容器的Dock属性设为Fill,即所有的控件都放在了这个容器里. using System; using System.Collections.Gene ...
- jqueryui插件slider的简单使用
<!DOCTYPE html> <html> <head> <title>slider</title> <meta charset=& ...
- ajax返回的欧洲字符(例如:法文)乱码
ajax返回值的乱码现象产生的相关代码如下: Java代码: JsonObject jsonObject = new JsonObject(); jsonObject.addProperty(&quo ...
- Swift 学习- 04 -- 字符串和字符
// 字符串 和 字符 // 字符串 是有序的 Character (字符) 类型的值的集合, 通过 String 类型的集合 // swift 的 String 和 Character 类型提供了 ...
- 在XIB 或者 SB 上面 实现 半透明的背景上添加UILabel是文字不透明
设透明背景的时候直接设置RGB值 然后设置Opacity即可
- NPOI写Excel,Spire.XLS for.NET组件转换Excel为PDF
首先要引用NPOI动态库和Microsoft.Office.Interop.excel.dll (Microsoft.Office.Interop.excel.dll 最好使用使用NuGet下载 , ...