NEST - 编写查询
Writing queries
Version:5.x
英文原文地址:Writing queries
将数据索引到了 Elasticsearch 之后,就可以准备搜索它们了。Elasticsearch 提供了一个强大的查询 DSL ,使得用户可以定义个性化的搜索逻辑。这个 DSL 是基于 JSON 的,NEST 提供了 Fluent API 和 Object Initializer 语法来实现 DSL 。
Match All query
最简单的查询应该就是 match_all
了,这种查询会返回所有的文档,并给每份文档的 _score
统一赋值为 1.0
匹配的文档并不是都会在一次响应中全部返回,默认情况下只返回前十份。你可以使用 from
和 size
来将结果分页
var searchResponse = client.Search<Project>(s => s .Query(q => q .MatchAll() ) );
上面的请求会被序列化成下面这个 JSON 对象
{ "query": { "match_all": {} } }
由于 match_all
查询很常见,因此前面的栗子有一个简单的写法,两种方式序列化的结果是一样的
searchResponse = client.Search<Project>(s => s .MatchAll() );
前面的两个栗子都是使用 Fluent API 来描述查询。NEST 还公开了一种 Object Initializer 语法去构造查询
var searchRequest = new SearchRequest<Project> { Query = new MatchAllQuery() }; searchResponse = client.Search<Project>(searchRequest);
Search request parameters
search request 有一些可用的参数,你可以参阅 search 以获得详细的信息。
Common queries
默认情况下,文档会根据 _score
降序返回。每个命中的 _score
是根据文档和查询条件的匹配程度计算的关联分数。数字越大,表示越符合查询条件。
NEST 提供了许多搜索查询,它们都记录在 Query DSL 参考部分。在这里,我们要强调用户经常执行的三类查询操作
- Structured search
- Unstructured search
- Combining queries
Structured search
结构化搜索是指,查询具有固定结构的数据。日期、时间和数字都是结构化的,查询这些类型的字段通常是为了查找准确的匹配项、某个范围内的值等等。文本也可以结构化,比如博客里使用的关键字标签。
通过结构化搜索,查询的答案总是 “是” 或者 “否”。也就是说,文档要么匹配查询,要么就不匹配。
术语级别的查询通常用于结构化搜索。下面的栗子查找开始日期在指定范围内的文档
var searchResponse = client.Search<Project>(s => s .Query(q => q .DateRange(r => r .Field(f => f.StartedOn) .GreaterThanOrEquals(new DateTime(2017, 01, 01)) .LessThan(new DateTime(2018, 01, 01)) ) ) );
(1) 查找开始于 2017 年的所有的 Project
会生成以下查询 JSON
{ "query": { "range": { "startedOn": { "lt": "2018-01-01T00:00:00", "gte": "2017-01-01T00:00:00" } } } }
因为这个查询的答案只有 yes
和 no
两种情况,我自然就不需要给查询计分了。为此,我们可以把这个查询包装在一个 bool
查询 filter
子句中,这样就可以让查询在筛选上下文中执行
searchResponse = client.Search<Project>(s => s .Query(q => q .Bool(b => b .Filter(bf => bf .DateRange(r => r .Field(f => f.StartedOn) .GreaterThanOrEquals(new DateTime(2017, 01, 01)) .LessThan(new DateTime(2018, 01, 01)) ) ) ) ) );
{ "query": { "bool": { "filter": [ { "range": { "startedOn": { "lt": "2018-01-01T00:00:00", "gte": "2017-01-01T00:00:00" } } } ] } } }
在筛选上下文中执行查询的好处是,Elasticsearch 可以放弃计算相关性分数,还可以缓存筛选器从而获得更快的后续性能
重要:术语级别的查询没有分析阶段,也就是说不会分析查询的输入,进而在反向索引中寻找输入的精确匹配。如果一个字段在索引时进行了分析,那么再通过术语级别查询多半会失败。
当字段仅用于精确匹配时,应当考虑将其映射为 keyword
类型。如果字段既用于精确匹配,又用于全文搜索,则应考虑将其映射为 multi fields
。
Unstructured search
另一个常见的用例是,在全文字段中搜索以查找最相关的文档。
全文查询用于非结构化的搜索。在这里,我们使用 match
查询来查找开发人员的名字中包含 “Russ” 的所有文档
var searchResponse = client.Search<Project>(s => s .Query(q => q .Match(m => m .Field(f => f.LeadDeveloper.FirstName) .Query("Russ") ) ) );
会生成以下查询 JSON
{ "query": { "match": { "leadDeveloper.firstName": { "query": "Russ" } } } }
重要:全文查询有分析阶段。也就是说要分析查询输入,然后将分析后产生的术语和反向索引中的术语进行比较。
通过在映射期间给字段设置分析器,你可以完全控制索引和搜索阶段的分析过程。
Combining queries
一个非常常见的情况是,将不同的查询组合在一起形成一个复合查询。其中最常见的是 bool
查询
var searchResponse = client.Search<Project>(s => s .Query(q => q .Bool(b => b .Must(mu => mu .Match(m => m (1) .Field(f => f.LeadDeveloper.FirstName) .Query("Russ") ), mu => mu .Match(m => m (2) .Field(f => f.LeadDeveloper.LastName) .Query("Cam") ) ) .Filter(fi => fi .DateRange(r => r .Field(f => f.StartedOn) .GreaterThanOrEquals(new DateTime(2017, 01, 01)) .LessThan(new DateTime(2018, 01, 01)) (3) ) ) ) ) );
(1) 匹配开发人员的名字包含 Russ
的所有文档
(2) ... 并且开发人员的姓氏包含 Cam
(3) ... 并且项目开始于 2017
会生成以下查询 JSON
{ "query": { "bool": { "must": [ { "match": { "leadDeveloper.firstName": { "query": "Russ" } } }, { "match": { "leadDeveloper.lastName": { "query": "Cam" } } } ], "filter": [ { "range": { "startedOn": { "lt": "2018-01-01T00:00:00", "gte": "2017-01-01T00:00:00" } } } ] } } }
一份文档必须满足三个查询才算匹配成功
- 对名字和姓氏的
match
查询有助于计算出相关性分数,因为它们都在查询上下文中执行 - 针对开始日期的
range
查询是在筛选上下文中执行的,索引没有为匹配的文档计算分数(针对这个查询的所有文档具有相同的分数1.0
)
由于 bool
查询非常常见,因此 NEST 在查询上重载了运算符,以使得 bool
查询的形式更加简洁。前面的 bool
查询可以更加简洁地表示为
searchResponse = client.Search<Project>(s => s .Query(q => q .Match(m => m .Field(f => f.LeadDeveloper.FirstName) .Query("Russ") ) && q .Match(m => m .Field(f => f.LeadDeveloper.LastName) .Query("Cam") ) && +q .DateRange(r => r .Field(f => f.StartedOn) .GreaterThanOrEquals(new DateTime(2017, 01, 01)) .LessThan(new DateTime(2018, 01, 01)) ) ) );
查看 writing bool
queries ,了解有关 bool
查询的更多详细信息和示例
Search response
搜索查询返回的响应是一个 ISearchResponse<T>
对象,其中 T
是在调用搜索方法时传入的泛型参数类型。响应对象有几个属性,其中你最可能使用的是 .Documents
,我们将在下面演示。
Matching documents
获取匹配搜索查询的文档是相当简单的
var searchResponse = client.Search<Project>(s => s .Query(q => q .MatchAll() ) ); var projects = searchResponse.Documents;
.Documents
是对下面这段逻辑的一个方便的速记
searchResponse.HitsMetaData.Hits.Select(h => h.Source);
并且可以从命中集合中检索有关每个命中的其他元数据。下面的示例在使用 highlighting
时检索命中的突出显示
var highlights = searchResponse.HitsMetaData.Hits.Select(h => h .Highlights );