Elasticsearch.Nest 教程系列 7-2 搜索:Writing bool queries | 编写布尔查询
本系列博文是“伪”官方文档翻译(更加本土化),并非完全将官方文档进行翻译,而是在查阅、测试原始文档并转换为自己真知灼见后的“准”翻译。有不同见解 / 说明不周的地方,还请海涵、不吝拍砖 :)
官方文档见此:https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/introduction.html
本系列对应的版本环境:ElasticSearch@7.3.1,NEST@7.3.1,IDE 和开发平台默认为 VS2019,.NET CORE 2.1
在使用查询 DSL 的时候,编写布尔查询命令会很冗长,如使用带有 2 个 should 子句的单个布尔查询:
1 | var searchResults = this.Client.Search<Project>(s => s |
可以相像,如果有多个嵌套布尔查询,最终代码久会长成这样:
为了解决代码冗长的问题,Nest 重写了一元操作符(“!”和“+”)以及二元操作符(“||”和“&&”)
运算符 | 对应的操作 |
---|---|
&& | must |
|| | should |
+ | filter |
! | must_not |
重写二元操作符:“||”
使用重写的二元运算符“||”,可以让你更加简洁的在 should 子句中使用布尔查询。
基于最上面的例子,改写如下:
1 | var firstSearchResponse = client.Search<Project>(s => s |
同样,如果使用的是对象初始化语法的,同样可以使用“||”:
1 | var secondSearchResponse = client.Search<Project>(new SearchRequest<Project> |
以上两种方式生成的命令相同,如下:
1 | { |
重写二元操作符:“&&”
重写的“&&”二元运算符可用于简化 must 子句的布尔查询:
1 | var firstSearchResponse = client.Search<Project>(s => s |
生成的命令均为:
1 | { |
NEST 客户端会对像上面这样的请求合并为一次 bool 查询。
重写一元操作符:“!”
重写的一元操作附“!”主要面向含有 must_not 子句的查询。
1 | var firstSearchResponse = client.Search<Project>(s => s |
以上两种方式最终生成的命令如下:
1 | { |
两个含有“!”运算符的查询可以用“&&”进行合并,以形成带有 2 个 must_not 子句的单个布尔查询。
1 | //伪代码: |
重写一元操作符:“+”
通过使用一元“+”运算符可以将查询转换为带有过滤子句的布尔查询。
1 | var firstSearchResponse = client.Search<Project>(s => s |
最终生成的命令如下:
1 | { |
以上命令将再过滤器上下文中运行,当你不需要使用 _score 来排序的时候,使用这种方式可以提高性能。
跟一元运算符“!”类似,使用“+”的 2 个子句同样可以使用“&&”进行合并,形成带有 2 个 filter 子句的单个布尔查询。
1 | //伪代码: |
使用“&&”合并查询
1.当将多个 Term 级别的查询用二元运算符“&&”(其中某些或所有查询都应用了一元运算符)进行连接,如:
1 | term && term && term |
NEST 会形成如下的嵌套顺序:
-
Nest 会自动将 && 连接的条件限制在单个 bool 查询中。
使用“||”合并查询或 should 子句
在上面的示例(描述“||”的小节)中,NEST将合并多个“||”或 should 子句到单个布尔查询中 。
像下面这样的组合:
1 | term || term || term |
最终会被解析为:
不过,布尔查询并非完全遵循编程语言中的布尔逻辑,如:
1 | term1 && (term2 || term3 || term4) |
需要特别注意,以上组合并不会被解析为以下关系:
原因:
-
当布尔查询中仅包含 should 子句时,表示的是必须要有一个被匹配上。
-
但当该布尔查询还具有 must 子句时,should 子句则会充当提升因子,这意味着它们不必匹配,但是如果匹配了,则该文档的相关性得分将得到提升。也就是说,should 子句的行为语义会根据 must 子句的存在而发生改变。
因此,对于“term1 && (term2 || term3 || term4)”这样的组合,你将得到仅包含 term1 的结果,这跟使用重载运算符时的预期是不相符。
在构建搜索查询时,将 should 子句用作提升因子非常有用,你可以将实际的 bool 查询与NEST的运算符重载混合后进行匹配。
另外需要注意下,当 2 个布尔查询仅包含 should 子句的时候,NEST 不会进行合并,如下:
1 | bool(should=term1, term2, term3, term4, minimum_should_match=2) || term5 || term6 |
-
如果 NEST 发现运算符“||”左右两边仅包含 should 子句,那么此时 NEST 会改变第一个布尔查询中的 minimum_should_match 参数的原意。
- 像上面的的示例 仅会在 term5 和 term6 上进行匹配 。
不会合并处理的情况
如果设置了任何查询元数据,如设置了 boost 或 name 等元数据,则 NEST 不会合并布尔查询,而是会将其视为已锁定,如下:
1 | Assert( |
性能优化建议
假设你有很多 must 子句,使用“&=”进行合并
1 | var c = new QueryContainer(); |
性能分析结果如下:
-
分配了过多的对象,这会导致 GC 压力。
因为已经知道了布尔查询的大致形态,因此可以通过如下方式来进行优化:
-
因为知道最终是一个布尔查询,所以这里可以直接用布尔查询来代替 Term 查询合并。
1 | QueryContainer q = new TermQuery { Field = "x", Value = "x" }; |
性能分析结果如下: