使用NEST编写查询语句

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

概述

我们在Elasticsearch中存储了数据索引,当然希望通过Elasticseach提供的强大的全文索引能力来搜索文档。Elasticsearch提供了一个强大的查询DSL来定义针对Elasticsearch执行的查询。该DSL基于JSON,在NEST中是以Fluent API和对象初始化器语法的形式供C#/.NET开发者调用。

MatchAll查询

在Elasticsearch中,最简单的查询语法是match_all,它将返回目标索引中的所有文档,并且所有文档的_score均为1.0。

使用match_all查询时并不是所有匹配的文档都在一个响应中返回,默认情况下,只返回前10个文档,你可以使用fromsize对结果进行分页。

在NEST中,要实现与Elastisearch API中的match_all相同的查询语法,则可以使用如下的方式:

var searchResponse = _client.Search<Project>(s => s
    .Query(q => q
        .MatchAll()
    )
);

NEST序列化后得到的JSON结果为:

{
  "query": {
    "match_all": {}
  }
}

在NEST中,MatchAll方法还提供了一种简写方式:

searchResponse = _client.Search<Project>(s => s
    .MatchAll()
);

此写法与上例写法在序列化后得到的JSON是相同的。

前面的两个示例都使用Fluent API来构建查询。NEST还公开了一个对象初始化器语法来构建查询语句,如下:

var searchRequest = new SearchRequest<Project>
{
    Query = new MatchAllQuery()
};

searchResponse = _client.Search<Project>(searchRequest);

Search()方法提供了几个可用参数,你可以点击这里查看更多关于查询参数的细节。

通用查询

默认情况下,文档将按_score降序返回,其中每次命中的_score是计算文档与查询条件匹配程度的相关度评分。

NEST提供了许多查询功能,在这里,我们将重点分析三种常用的查询操作:

  • 结构化搜索(Structured search)
  • 非结构化搜索(Unstructured search)
  • 组合查询(Combining queries)

结构化查询

结构化搜索是关于查询具有固有结构的数据。日期、时间和数字都是结构化的,通常需要对这些类型的字段进行查询,以查找精确匹配、范围内的值等。文本也可以结构化,例如,应用于博客文章的关键字标签。

在结构化搜索中,查询的答案总是,即:文档要么与查询匹配,要么不匹配。

术语级查询通常用于结构化搜索。下面是一个示例,它查找以日期开始的文档位于指定范围内:

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))
        )
    )
);

以上查询语句将生成以下查询JSON:

{
  "query": {
    "range": {
      "startedOn": {
        "lt": "2018-01-01T00:00:00",
        "gte": "2017-01-01T00:00:00"
      }
    }
  }
}

由于该查询的答案总是,所以我们不想对该查询进行评分。为此,我们可以通过将查询包装在bool查询过滤中来获得在过滤器上下文中执行的查询:

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))
               )
           )
       )

   )
);

以上查询语句将生成以下查询JSON:

{
  "query": {
    "bool": {
      "filter": [
        {
          "range": {
            "startedOn": {
              "lt": "2018-01-01T00:00:00",
              "gte": "2017-01-01T00:00:00"
            }
          }
        }
      ]
    }
  }
}

在过滤器上下文中执行查询的好处是Elasticsearch可以放弃计算相关度评分,以及缓存过滤器以获得更高的执行效率和查询性能。

注:术语级查询没有分词阶段,也就是说,不会对查询输入进行分词,而是在倒排索引中寻找与输入的精确匹配。作业Elasticsearch的新手,当对索引的字段使用术语级查询时,需要特别注意。

当字段仅用于精确匹配时,应该考虑将其作为关键字数据类型进行索引。如果一个字段同时用于精确匹配和全文搜索,应该考虑使用多个字段(multi fields)为其建立索引。

非结构化搜索

Elasticsearch的另一类常用的场景是在全文字段中进行搜索,以找到最相关的文档。

全文搜索用于非结构化搜索。以下示例中,我们使用匹配(match)查询在Project索引的LeadDeveloperfirstName字段中查找所有包含”Russ”关键字的文档:

var searchResponse = _client.Search<Project>(s => s
    .Query(q => q
        .Match(m => m
            .Field(f => f.LeadDeveloper.FirstName)
            .Query("Russ")
        )
    )
);

NEST生成以下查询JSON:

{
  "query": {
    "match": {
      "leadDeveloper.firstName": {
        "query": "Russ"
      }
    }
  }
}

全文查询有分词阶段,即会对查询输入进行分词,并将查询分词器产生的术语与倒排索引中的术语进行比较。

通过将分词器通过映射应用到文本数据类型字段,你可以完全控制搜索分词器和索引分词器。

组合查询

在Elasticsearch中,一个非常常见的场景是将单独的查询组合在一起形成复合查询,其中最常见的是bool查询L,NEST中同样实现相应的方法:

var searchResponse = _client.Search<Project>(s => s
    .Query(q => q
        .Bool(b => b
            .Must(mu => mu
                .Match(m => m 
                    .Field(f => f.LeadDeveloper.FirstName)
                    .Query("Russ")
                ), mu => mu
                .Match(m => m 
                    .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)) 
                )
            )
        )
    )
);

NEST将生成以下查询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"
            }
          }
        }
      ]
    }
  }
}

文档必须满足本例中的所有三个查询才能算成功匹配:

  • 姓氏和名字的匹配查询将有助于计算相关性得分,因为这两个查询都在查询上下文中运行
  • 针对开始日期(staredOn)的范围查询在过滤器(filter)上下文中运行,因此不计算匹配文档的分数

因为bool是一个非常常用的查询,NEST重载了Query运算符,使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))
        )
    )
);

查询的响应结果

NEST中,搜索返回的响应是SearchResponse<T>,其中T是搜索方法调用中定义的泛型参数类型,响应结果上有相当多的属性,但最常见的可能是. documents,将在下面演示:

var searchResponse = client.Search<Project>(s => s
    .Query(q => q
        .MatchAll()
    )
);

var projects = searchResponse.Documents;

.Documents是检索每个命中的_source的方便快捷方式:

var sources = searchResponse.HitsMetadata.Hits.Select(h => h.Source);

此外,也可以从命中集合中检索与每个命中相关的其他元数据。以下是一个在使用高亮显示时检索命中结果中高亮文档的示例:

var highlights = searchResponse.HitsMetadata.Hits.Select(h => h
    .Highlight // 获取高亮搜索结果
);

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

发表评论

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