第五节:使用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

  • multi_match

# 单字段
GET /t_book/_search
{
    "query": {
        "match": {
          "title": "Docker 实践"
        }
    }
}
# 多字段
GET /t_book/_search
{
    "query": {
        "multi_match": {
          "query": "安全",
          "fields": ["title","category","tags"]
        }
    }
}

 

(2) 精确查询

  不对用户输入搜索条件分词,根据字段内容精确值匹配。但只能查找keyword、数值、日期、boolean类型的字段。例如:

  • idstermrange

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查询,即布尔查询。就是利用逻辑运算来组合一个或多个查询子句的组合。bool查询支持的逻辑运算有:

  • must:必须匹配每个子查询,类似“与”

  • should:选择性匹配子查询,类似“或”

  • must_not:必须不匹配,不参与算分,类似“非”

  • filter:必须匹配,不参与算分

 第二类:基于某种算法修改查询时的文档相关性算分,从而改变文档排名。例如:【了解即可】

 

  • function_score    dis_max

 

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 排序

  elasticsearch默认是根据相关度算分(_score)来排序,但是也支持自定义方式对搜索结果排序。不过分词字段无法排序,能参与排序字段类型有:keyword类型、数值类型、地理坐标类型、日期类型等

# 先按照价格降序,价格相同,按照时间升序
GET /t_book/_search
{
    "query": {
        "match_all": {}
    },
    "sort": [
      {
        "price": {
          "order": "desc"
        },
        "published_date": {
            "order": "asc"
        }
      }
    ]
}

5 分页

elasticsearch中通过修改fromsize参数来控制要返回的分页结果:

  • from:从第几个文档开始

  • size:总共查询几个文档

GET /t_book/_search
{
    "query": {
        "match_all": {}
    },
    "sort": [
      {
        "price": {
          "order": "desc"
        },
        "published_date": {
            "order": "asc"
        }
      }
    ]
    ,"from": 0
    ,"size": 5
}

6 深度分页【了解】

 elasticsearch的数据一般会采用分片存储,也就是把一个索引中的数据分成N份,存储到不同节点上。这种存储方式比较有利于数据扩展,但给分页带来了一些麻烦。

 比如一个索引库中有100000条数据,分别存储到4个分片,每个分片25000条数据。现在每页查询10条,查询第99页。那么分页查询的条件如下

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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
posted @ 2025-07-18 15:38  Yaopengfei  阅读(68)  评论(1)    收藏  举报