第五节:使用DLS和.Net客户端进行叶子查询/复合查询/分页/高亮等实操
一. 基于DLS的高级操作
1 查询所有
说明:
-
GET /{索引库名}/_search:其中的_search
例如,我们以最简单的无条件查询为例,无条件查询的类型是:match_all,因此其查询语句如下:
PS:默认Kibana中显示10条数据,可以指定size,显示需要的数据,最多单次10000条
GET /t_book/_search
{
"query": {
"match_all": {}
},
"size": 1000
}
2 叶子查询
(1) 全文检索
分词器对用户输入搜索条件先分词,得到词条,然后再利用倒排索引搜索词条。只能查找text类型,例如:
-
match: -
# 单字段
GET /t_book/_search
{
"query": {
"match": {
"title": "Docker 实践"
}
}
}
# 多字段
GET /t_book/_search
{
"query": {
"multi_match": {
"query": "安全",
"fields": ["title","category","tags"]
}
}
}
(2) 精确查询
只能查找keyword、数值、日期、boolean类型的字段。例如:
-
idsterm
range是范围查询,对于范围筛选的关键字有:
-
gte:大于等于 -
gt:大于 -
lte:小于等于 -
lt:小于
# 精确查询
GET /t_book/_search
{
"query": {
"term": {
"category": {
"value": "算法"
}
}
}
}
# 范围查询
GET /t_book/_search
{
"query": {
"range": {
"price": {
"gte": 80,
"lte": 100
}
}
}
}
(3) 地理查询
-
geo_bounding_box:按矩形搜索 -
geo_distance
3 复合查询
bool查询,即布尔查询。就是利用逻辑运算来组合一个或多个查询子句的组合。bool查询支持的逻辑运算有:
-
must:必须匹配每个子查询,类似“与”
-
should:选择性匹配子查询,类似“或”
-
must_not:必须不匹配,不参与算分,类似“非”
-
filter:必须匹配,不参与算分
第二类:基于某种算法修改查询时的文档相关性算分,从而改变文档排名。例如:【了解即可】
-
function_score
GET /t_book/_search
{
"query": {
"bool": {
"must": [
{"match":{"category":"云计算"}}
],
"should": [
{"term":{"author":{"value":"赵杰"}}},
{"term":{"author":{"value":"王丽"}}}
],
"must_not": [
{"range":{"price":{"gte":100}}}
],
"filter": [
{"range":{"price":{"gt":85}}}
]
}
}
}
//比如,我们要搜索`手机`,但品牌必须是`华为`,价格必须是`900~1599`,那么可以这样写:
GET /items/_search
{
"query": {
"bool": {
"must": [
{"match": {"name": "手机"}}
],
"filter": [
{"term": {"brand": { "value": "华为" }}},
{"range": {"price": {"gte": 90000, "lt": 159900}}}
]
}
}
}
4 排序
_score)来排序,但是也支持自定义方式对搜索结果排序。不过分词字段无法排序,能参与排序字段类型有:keyword
# 先按照价格降序,价格相同,按照时间升序
GET /t_book/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"price": {
"order": "desc"
},
"published_date": {
"order": "asc"
}
}
]
}
5 分页
elasticsearch中通过修改from、size参数来控制要返回的分页结果:
-
from:从第几个文档开始 -
size:总共查询几个文档
GET /t_book/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"price": {
"order": "desc"
},
"published_date": {
"order": "asc"
}
}
]
,"from": 0
,"size": 5
}
6 深度分页【了解】
elasticsearch的数据一般会采用分片存储,也就是把一个索引中的数据分成N份,存储到不同节点上。这种存储方式比较有利于数据扩展,但给分页带来了一些麻烦。
GET /t_book/_search
{
"from": 990, // 从第990条开始查询
"size": 10, // 每页查询10条
"sort": [
{
"price": "asc"
}
]
}
从语句来分析,要查询第990~1000名的数据。
从实现思路来分析,肯定是将所有数据排序,找出前1000名,截取其中的990~1000的部分。但问题来了,我们如何才能找到所有数据中的前1000名呢?
要知道每一片的数据都不一样,第1片上的第900~1000,在另1个节点上并不一定依然是900~1000名。所以我们只能在每一个分片上都找出排名前1000的数据,然后汇总到一起,重新排序,才能找出整个索引库中真正的前1000名,此时截取990~1000的数据即可。
针对深度分页,elasticsearch提供了两种解决方案:
-
search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。(意思是要记录上一次查询的最后一条记录的排序值,然后携带到下一次查询。)官方推荐使用的方式。 -
scroll:原理将排序后的文档id形成快照,保存下来,基于快照做分页。官方已经不推荐使用。
大多数情况下,我们采用普通分页就可以了。查看百度、京东等网站,会发现其分页都有限制。例如百度最多支持77页,每页不足20条。京东最多100页,每页最多60条。
7 高亮 [重点]
(1) 什么是高亮显示?
我们在百度,京东搜索时,关键字会变成红色,比较醒目,这叫高亮显示

观察页面源码,你会发现两件事情:
-
高亮词条都被加了``标签
-
em标签都添加了红色样式
css样式肯定是前端实现页面的时候写好的,但是前端编写页面的时候是不知道页面要展示什么数据的,不可能给数据加标签。而服务端实现搜索功能,要是有elasticsearch做分词搜索,是知道哪些词条需要高亮的。
因此词条的高亮标签肯定是由服务端提供数据的时候已经加上的。
因此实现高亮的思路就是:
-
1 用户输入搜索关键字搜索数据
-
2 服务端根据搜索关键字到elasticsearch搜索,并给搜索结果中的关键字词条添加
html标签(比如 <em>标签) -
3 前端提前给约定好的
html标签添加CSS样式
(2) 代码实操
GET /t_book/_search
{
"query": {
"match": {
"title": "Docker 实践"
}
},
"highlight": {
"fields": {
"title": {
"pre_tags": "<em>",
"post_tags": "<em>"
}
}
}
}
注意:
-
搜索必须有查询条件,而且是全文检索类型的查询条件,例如
match -
参与高亮的字段必须是
text类型的字段 -
默认情况下参与高亮的字段要与搜索字段一致,比如这里都是 title,除非添加:
required_field_match=false

二. 基于.Net高级操作
1 查询所有
/// <summary>
/// 01-查询所有文档
/// </summary>
/// <returns></returns>
[HttpPost]
public async Task<List<Book>> GetAllBooksAsync()
{
var response = await _client.SearchAsync<Book>(s =>
s.Index(indexName)
.Query(q => q.MatchAll(new MatchAllQuery()))
.Size(10000) // 确保获取所有文档,但注意深度分页限制, 单词查询最多1万条
);
// 处理响应并返回书籍列表
if (!response.IsValidResponse)
{
// 记录错误或抛出异常
throw new Exception($"查询失败: {response.DebugInformation}");
}
return response.Documents.ToList();
}
2 叶子查询
(1) 全文检索
/// <summary>
/// 02-全文检索--分词单字段
/// </summary>
/// <param name="searchTerm">分词内容</param>
/// <returns></returns>
[HttpPost]
public async Task<List<Book>> MatchSingleFieldQuery(string searchTerm = "Docker 实践")
{
var response = await _client.SearchAsync<Book>(s => s
.Index(indexName)
.Query(q => q
.Match(m => m
.Field(f => f.title)
.Query(searchTerm)
)
).Size(10000)
);
// 处理响应并返回书籍列表
if (!response.IsValidResponse)
{
// 记录错误或抛出异常
throw new Exception($"查询失败: {response.DebugInformation}");
}
return response.Documents.ToList();
}
/// <summary>
///03-全文检索 - 多字段
/// </summary>
/// <param name="searchTerm"></param>
/// <returns></returns>
[HttpPost]
public async Task<List<Book>> MultiMatchQuery(string searchTerm = "安全")
{
var response = await _client.SearchAsync<Book>(s => s
.Index(indexName)
.Query(q => q
.MultiMatch(mm => mm
.Query(searchTerm)
.Fields(new Field[] { "title", "category", "tags" })
)
).Size(10000)
);
// 处理响应并返回书籍列表
if (!response.IsValidResponse)
{
// 记录错误或抛出异常
throw new Exception($"查询失败: {response.DebugInformation}");
}
return response.Documents.ToList();
}
(2) 精确查询
/// <summary>
/// 04-精确查询 - 类别匹配
/// </summary>
/// <param name="category"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
[HttpPost]
public async Task<List<Book>> TermQuery(string category = "算法")
{
var response = await _client.SearchAsync<Book>(s => s
.Index(indexName)
.Query(q => q
.Term(t => t
.Field(f => f.category)
.Value(category)
)
).Size(10000)
);
// 处理响应并返回书籍列表
if (!response.IsValidResponse)
{
// 记录错误或抛出异常
throw new Exception($"查询失败: {response.DebugInformation}");
}
return response.Documents.ToList();
}
/// <summary>
/// 05-范围查询 - 价格范围
/// </summary>
/// <param name="minPrice">最小值</param>
/// <param name="maxPrice">最大值</param>
/// <returns></returns>
[HttpPost]
public async Task<List<Book>> RangeQuery(double minPrice = 80, double maxPrice = 100)
{
var response = await _client.SearchAsync<Book>(s => s
.Index(indexName)
.Query(q => q
.Range(r => r
.NumberRange(nr => nr
.Field(f => f.price)
.Gte(minPrice)
.Lte(maxPrice)
)
)
).Size(10000)
);
// 处理响应并返回书籍列表
if (!response.IsValidResponse)
{
// 记录错误或抛出异常
throw new Exception($"查询失败: {response.DebugInformation}");
}
return response.Documents.ToList();
}
3 复合查询
/// <summary>
/// 06-复合查询 - Bool查询
/// </summary>
/// <returns></returns>
[HttpPost]
public async Task<List<Book>> BoolQueryExample()
{
var response = await _client.SearchAsync<Book>(s => s
.Index(indexName)
.Query(q => q
.Bool(b => b
.Must(mu => mu.Match(m => m.Field(f => f.category).Query("云计算"))
)
.Should(sh => sh.Term(t => t.Field(f => f.author).Value("赵杰")),
sh => sh.Term(t => t.Field(f => f.author).Value("王丽"))
)
.MustNot(mn => mn.Range(r => r.NumberRange(nr => nr.Field(f => f.price).Gte(100)))
)
.Filter(fi => fi.Range(r => r.NumberRange(nr => nr.Field(f => f.price).Gt(85)))
)
)
).Size(10000)
);
// 处理响应并返回书籍列表
if (!response.IsValidResponse)
{
// 记录错误或抛出异常
throw new Exception($"查询失败: {response.DebugInformation}");
}
return response.Documents.ToList();
}
4 排序
/// <summary>
/// 07-排序
/// </summary>
/// <returns></returns>
[HttpPost]
public async Task<List<Book>> SortQuery()
{
var response = await _client.SearchAsync<Book>(s => s
.Index(indexName)
.Query(q => q.MatchAll(new MatchAllQuery()))
.Sort(so => so
.Field(f => f.price, new FieldSort { Order = SortOrder.Desc })
).Size(1000)
);
//多条件排序存在问题?
// 处理响应并返回书籍列表
if (!response.IsValidResponse)
{
// 记录错误或抛出异常
throw new Exception($"查询失败: {response.DebugInformation}");
}
return response.Documents.ToList();
}
5 分页
/// <summary>
/// 08-分页
/// </summary>
/// <returns></returns>
[HttpPost]
public async Task<List<Book>> PaginationQuery(int page = 1, int pageSize = 5)
{
var response = await _client.SearchAsync<Book>(s => s
.Index(indexName)
.Query(q => q.MatchAll(new MatchAllQuery()))
.Sort(so => so
.Field(f => f.price, new FieldSort { Order = SortOrder.Desc })
)
.From((page - 1) * pageSize)
.Size(pageSize)
);
// 处理响应并返回书籍列表
if (!response.IsValidResponse)
{
// 记录错误或抛出异常
throw new Exception($"查询失败: {response.DebugInformation}");
}
return response.Documents.ToList();
}
6 高亮
/// <summary>
/// 09-分词搜索+高亮显示
/// </summary>
/// <param name="searchTerm">搜索内容</param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
[HttpPost]
public async Task<List<Book>> HighlightQuery(string searchTerm = "Docker 实践")
{
var response = await _client.SearchAsync<Book>(s => s
.Index(indexName)
.Query(q => q.Match(m => m
.Field(f => f.title)
.Query(searchTerm)
))
.Highlight(h => h
.Fields(fields => fields
.Add("title", new HighlightFieldDescriptor<Book>()
.PreTags(["<em>"])
.PostTags(["<em>"])
)
)
)
);
// 处理响应并返回书籍列表
if (!response.IsValidResponse)
{
// 记录错误或抛出异常
throw new Exception($"查询失败: {response.DebugInformation}");
}
// 高亮结果处理
foreach (var hit in response.Hits)
{
if (hit.Highlight.TryGetValue("title", out var highlights) && hit.Source != null)
{
// 将高亮结果存入自定义属性
hit.Source.highlightTitle = highlights.FirstOrDefault();
}
}
return response.Documents.ToList();
}
前端直接使用返回的highlightTitle字段做高亮显示即可,只需给 <em> 标签加个红色即可

!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。

浙公网安备 33010602011771号