Elasticsearch查询DSL语言:构建复杂搜索和高效检索的完全指南

时间:2023-02-19 13:12:36

一、概述

Elasticsearch 是一款全文搜索引擎,可以轻松地处理海量数据。它提供了一种查询语言,称为查询 DSL,用于在索引中搜索数据。查询 DSL 的语法是基于 JSON 的,允许您构建复杂的查询和聚合操作。

查询 DSL 语言的核心是查询语句(Query Clauses),它们定义了要匹配的查询条件。查询语句可以分为两类:

  • 查询子句(Query Clauses):用于确定哪些文档匹配查询条件。
  • 过滤子句(Filter Clauses):用于限制查询的结果集。

查询子句通常用于评分(Scoring)和排序(Sorting)操作,而过滤子句则更适合用于筛选数据。

以下是一些常见的查询子句和过滤子句:

  • term 查询(Term Query):匹配一个精确值,例如一个单词或一个数字。
  • match 查询(Match Query):匹配一个查询字符串中的一个或多个单词。
  • range 查询(Range Query):匹配一个数值范围。
  • bool 查询(Boolean Query):组合多个查询子句,可以使用 AND、OR 或 NOT 运算符。
  • exists 过滤(Exists Filter):匹配存在指定字段的文档。
  • missing 过滤(Missing Filter):匹配不存在指定字段的文档。
  • term 过滤(Term Filter):匹配一个精确值。
  • range 过滤(Range Filter):匹配一个数值范围。
  • bool 过滤(Boolean Filter):组合多个过滤子句,可以使用 AND、OR 或 NOT 运算符。

二、查询子句

term 查询

term 查询用于匹配一个精确值。它通常用于匹配文本字段或数字字段。以下是一个示例:

{
"term": {
"name": "John"
}
}

上面的查询将匹配所有 ​​name​​ 字段值为 "John" 的文档。请注意,term 查询是区分大小写的。

match 查询

match 查询用于匹配一个查询字符串中的一个或多个单词。它通常用于匹配文本字段。以下是一个示例

{
"match": {
"title": "Elasticsearch"
}
}

上面的查询将匹配所有 ​​title​​ 字段包含单词 "Elasticsearch" 的文档。请注意,match 查询是不区分大小写的。

match 查询还支持一些高级选项,例如模糊匹配、词组匹配和前缀匹配等。以下是一些示例:

{
"match": {
"title": {
"query": "Elasticsearch server",
"fuzziness": "AUTO"
}
}
}

上面的查询将匹配所有 ​​title​​ 字段包含 "Elasticsearch" 和 "server" 两个单词的文档,同时允许模糊匹配(fuzziness)。

{
"match_phrase": {
"title": {
"query": "Elasticsearch server",
"slop": 1
}
}
}

上面的查询将匹配所有 ​title​ 字段包含 "Elasticsearch server" 这个词组的文档,同时允许一个单词的间隔(slop)。

{
"match_phrase_prefix": {
"title": {
"query": "Elastic",
"max_expansions": 10
}
}
}

上面的查询将匹配所有 ​title​ 字段以 "Elastic" 开头的词组,同时允许最多 10 个扩展词。

range 查询

range 查询用于匹配一个数值范围。它通常用于匹配数字字段。以下是一个示例:

{
"range": {
"age": {
"gte": 18,
"lte": 30
}
}
}

上面的查询将匹配所有 ​​age​​ 字段值在 18 到 30 之间的文档。range 查询还支持其他选项,例如大于(gt)、小于(lt)等。

bool 查询

bool 查询用于组合多个查询子句,可以使用 AND、OR 或 NOT 运算符。以下是一个示例:

{
"bool": {
"must": [
{ "match": { "title": "Elasticsearch" } },
{ "match": { "description": "distributed" } }
],
"must_not": [
{ "match": { "category": "xxx" } }
],
"should": [
{ "match": { "author": "Smith" } },
{ "match": { "author": "John" } }
],
"minimum_should_match": 1
}
}

上面的查询将匹配所有 ​title​ 字段包含 "Elasticsearch",且 ​description​ 字段包含 "distributed",同时不包含 ​category​ 字段值为 "xxx" 的文档。此外,它应该(should)包含 "Smith" 或 "John" 作为 ​author​ 字段的值,但最少需要匹配一个(minimum_should_match)。

三、过滤子句

过滤子句用于过滤文档,与查询不同,过滤不会影响文档的评分。因此,如果您只是想筛选出一些文档,而不需要对其进行排序和评分,过滤子句是更好的选择。

exists 过滤

exists 过滤用于筛选出包含指定字段的文档。以下是一个示例:

{
"exists": {
"field": "title"
}
}

上面的过滤将返回所有包含 ​​title​​ 字段的文档。

term 过滤

term 过滤用于精确匹配指定字段的值。以下是一个示例:

{
"term": {
"status": "published"
}
}

上面的过滤将返回所有 ​​status​​ 字段值为 "published" 的文档。

range 过滤

range 过滤用于匹配一个数值范围。它通常用于匹配数字字段。以下是一个示例:

{
"range": {
"age": {
"gte": 18,
"lte": 30
}
}
}

上面的过滤将返回所有 ​​age​​ 字段值在 18 到 30 之间的文档。range 过滤还支持其他选项,例如大于(gt)、小于(lt)等。

bool 过滤

bool 过滤用于组合多个过滤子句,可以使用 AND、OR 或 NOT 运算符。以下是一个示例:

{
"bool": {
"must": [
{ "term": { "status": "published" } },
{ "range": { "age": { "gte": 18 } } }
],
"must_not": [
{ "term": { "category": "xxx" } }
],
"should": [
{ "term": { "author": "Smith" } },
{ "term": { "author": "John" } }
],
"minimum_should_match": 1
}
}

上面的过滤将返回所有 ​​status​​​ 字段值为 "published",且 ​​age​​​ 字段值大于等于 18,同时不包含 ​​category​​​ 字段值为 "xxx" 的文档。此外,它应该(should)包含 "Smith" 或 "John" 作为 ​​author​​ 字段的值,但最少需要匹配一个(minimum_should_match)。

四、排序

排序用于对文档进行排序,可以按一个或多个字段进行排序。以下是一个示例:

{
"sort": [
{ "created_at": { "order": "desc" } },
{ "views": { "order": "desc" } }
]
}

上面的排序将按 ​​created_at​​​ 字段降序排序,如果有相同的值,则按 ​​views​​ 字段降序排序。

五、聚合

聚合用于对文档进行统计分析,例如计算某个字段的平均值、最大值、最小值、总和等。Elasticsearch 支持多种聚合类型,包括指标聚合、桶聚合、管道聚合等。

指标聚合

指标聚合用于计算某个字段的值,例如平均值、最大值、最小值、总和等。以下是一个示例:

{
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}

上面的聚合将计算 ​​price​​​ 字段的平均值,并将结果存储在 ​​avg_price​​ 中。

桶聚合

桶聚合用于将文档分配到不同的桶中,例如根据某个字段的值分组。以下是一个示例:

{
"aggs": {
"genres": {
"terms": {
"field": "genre"
}
}
}
}

上面的聚合将根据 ​​genre​​ 字段的值将文档分配到不同的桶中,并将每个桶的文档数量返回。

管道聚合

管道聚合用于在多个聚合之间构建一个聚合流水线,将前一个聚合的结果作为后一个聚合的输入。以下是一个示例:

{
"aggs": {
"sales_by_year": {
"date_histogram": {
"field": "created_at",
"interval": "year"
},
"aggs": {
"total_sales": {
"sum": {
"field": "price"
}
},
"avg_sales": {
"avg_bucket": {
"buckets_path": "total_sales"
}
}
}
}
}
}

上面的聚合将按照 ​​created_at​​ 字段的年份分组,并计算每年的总销售额和平均销售额。

六、Java API

除了使用 REST API 外,您还可以使用 Elasticsearch 的 Java API 进行查询、过滤、排序和聚合操作。以下是一些示例代码:

创建索引

IndexRequest request = new IndexRequest("my-index");
request.id("1");
request.source("field1", "value1", "field2", "value2");

IndexResponse response = client.index(request, RequestOptions.DEFAULT);

上面的代码将创建一个名为 ​​my-index​​ 的索引,并插入一条文档。

查询文档

SearchRequest request = new SearchRequest("my-index");
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.matchQuery("title", "Elasticsearch"));
request.source(builder);

SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();

for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}

上面的代码将查询 ​​my-index​​​ 索引中所有 ​​title​​ 字段包含 "Elasticsearch" 的文档,并打印它们的 JSON 格式。

过滤文档

SearchRequest request = new SearchRequest("my-index");
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.matchQuery("title", "Elasticsearch"));
builder.postFilter(QueryBuilders.rangeQuery("price").gte(10).lte(100));
request.source(builder);

SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();

for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}

上面的代码将查询 ​​my-index​​​ 索引中所有 ​​title​​​ 字段包含 "Elasticsearch",且 ​​price​​ 字段的值在 10 和 100 之间的文档,并打印它们的 JSON 格式。

排序文档

SearchRequest request = new SearchRequest("my-index");
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.matchQuery("title", "Elasticsearch"));
builder.sort("price", SortOrder.ASC);
request.source(builder);

SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();

for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}

上面的代码将查询 ​​my-index​​​ 索引中所有 ​​title​​​ 字段包含 "Elasticsearch" 的文档,并按照 ​​price​​ 字段的值进行升序排序,并打印它们的 JSON 格式。

聚合文档

SearchRequest request = new SearchRequest("my-index");
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.aggregation(AggregationBuilders.avg("avg_price").field("price"));
request.source(builder);

SearchResponse response = client.search(request, RequestOptions.DEFAULT);
Aggregations aggregations = response.getAggregations();

Avg avg = aggregations.get("avg_price");
double value = avg.getValue();

System.out.println("Average price: " + value);

上面的代码将查询 ​​my-index​​​ 索引中 ​​price​​ 字段的平均值,并打印结果。

总结

本文介绍了 Elasticsearch 的查询 DSL 语言,包括查询、过滤、排序和聚合操作,以及 Java API 的使用方法。了解 Elasticsearch 的查询语言可以帮助您更好地理解和使用 Elasticsearch,提高数据检索的效率和准确性。

常见问题

1. 查询性能如何优化?

查询性能的优化是 Elasticsearch 中最重要的优化之一。以下是一些优化查询性能的方法:

  • 索引的字段数量应该保持在适当的范围内,过多的字段会增加索引的大小,降低查询的速度。
  • 将经常被查询的字段存储为关键字字段,可以提高查询的速度。
  • 通过过滤器过滤掉不需要的文档,可以减少查询的范围,提高查询的速度。
  • 使用正确的查询语句,如 term 查询比 match 查询更快。

也可以参考我的往期文章 ​​Elasticsearch索引优化指南:分片、副本、mapping和analyzer​

2. 如何进行模糊搜索?

在 Elasticsearch 中,可以使用模糊搜索来找到与搜索词相似的文档。以下是一些常见的模糊搜索方法:

  • 使用通配符搜索,如使用 * 号匹配任意字符,使用 ? 号匹配单个字符。
  • 使用模糊搜索,如使用 ~ 符号指定模糊搜索的程度。
  • 使用距离搜索,如使用距离参数指定搜索词的编辑距离。

3. 如何实现全文搜索?

全文搜索是 Elasticsearch 中最常用的搜索之一,以下是一些实现全文搜索的方法:

  • 将需要搜索的字段定义为文本类型,这样 Elasticsearch 就会对这些字段进行分词处理。
  • 使用 match 查询进行全文搜索。
  • 使用多字段搜索来搜索多个字段。
  • 使用高亮显示来突出显示匹配的文本。

4. 如何实现分页搜索?

分页搜索是 Elasticsearch 中常用的搜索之一,以下是一些实现分页搜索的方法:

  • 使用 from 和 size 参数来指定需要返回的文档范围。
  • 使用 scroll 查询来获取所有文档,并进行分页处理。
  • 使用 search_after 查询来实现基于游标的分页。

5. 如何实现复杂的搜索逻辑?

在实际应用中,很多搜索逻辑都比较复杂,以下是一些实现复杂的搜索逻辑的方法:

  • 使用复合查询,如 bool 查询、dis_max 查询、function_score 查询等。
  • 使用多个查询组合在一起进行搜索。
  • 使用过滤器过滤掉不需要的文档。

也可参考我的往期文章​​从入门到进阶:Elasticsearch高级查询技巧详解​