# DSL 查询文档

# DSL 查询分类

Elasticsearch 提供了基于 JSON 的 DSL(Domain Specific Language)来定义查询。常见的查询类型包括:

  • 查询所有:查询出所有数据,一般测试用。例如:match_all
  • 全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:
    • match_query
    • multi_match_query
  • 精确查询:根据精确词条值查找数据,一般是查找 keyword、数值、日期、boolean 等类型字段。例如:
    • ids
    • range
    • term
  • 地理(geo)查询:根据经纬度查询。例如:
    • geo_distance
    • geo_bounding_box
  • 复合(compound)查询:复合查询可以将上述各种查询条件组合起来,合并查询条件。例如:
    • bool
    • function_score
GET /indexName/_search
{
  "query": {
    "查询类型": {
      "查询条件": "条件值"
    }
  }
}

# 全文检索查询

# 语法

# match 查询

单字段查询

GET /indexName/_search
{
  "query": {
    "match": {
      "FIELD": "TEXT"
    }
  }
}

# mulit_match 查询

多字段查询,任意一个字符符合条件就算符合查询条件

GET /indexName/_search
{
  "query": {
    "multi_match": {
      "query": "TEXT",
      "fields": ["FIELD1", " FIELD12"]
    }
  }
}

# 精确查询

# term 查询

# 说明

因为精确查询的字段搜是不分词的字段,因此查询的条件也必须是不分词的词条。查询时,用户输入的内容跟自动值完全匹配时才认为符合条件。如果用户输入的内容过多,反而搜索不到数据

# 语法

//term 查询
GET /indexName/_search
{
  "query": {
    "term": {
      "FIELD": {
        "value": "VALUE"
      }
    }
  }
}

# range 查询

# 说明

范围查询,一般应用在对数值类型做范围过滤的时候。比如做价格范围过滤

# 语法

//range 查询
GET /indexName/_search
{
  "query": {
    "range": {
      "FIELD": {
        "gte": 10, // 这里的 gte 代表大于等于,gt 则代表大于
        "lte": 20 //lte 代表小于等于,lt 则代表小于
      }
    }
  }
}

# 地理坐标查询

# 矩形范围查询

# 说明

矩形范围查询,也就是 geo_bounding_box 查询,查询坐标落在某个矩形范围的所有文档

查询时,需要指定矩形的左上右下两个点的坐标,然后画出一个矩形,落在该矩形内的都是符合条件的点

# 语法

//geo_bounding_box 查询
GET /indexName/_search
{
  "query": {
    "geo_bounding_box": {
      "FIELD": {
        "top_left": { // 左上点
          "lat": 31.1,
          "lon": 121.5
        },
        "bottom_right": { // 右下点
          "lat": 30.9,
          "lon": 121.7
        }
      }
    }
  }
}

# 附近查询

# 说明

附近查询,也叫做距离查询(geo_distance):查询到指定中心点小于某个距离值的所有文档

# 语法

//geo_distance 查询
GET /indexName/_search
{
  "query": {
    "geo_distance": {
      "distance": "15km", // 半径
      "FIELD": "31.21,121.5" // 圆心
    }
  }
}

# 复合查询

复合(compound)查询:复合查询可以将其它简单查询组合起来,实现更复杂的搜索逻辑。常见的有两种:

  • fuction score:算分函数查询,可以控制文档相关性算分,控制文档排名
  • bool query:布尔查询,利用逻辑关系组合多个其它的查询,实现复杂搜索

# 相关性算分

# 语法

[
  {
    "_score" : 17.850193,
    "_source" : {
      "name" : "虹桥如家酒店真不错",
    }
  },
  {
    "_score" : 12.259849,
    "_source" : {
      "name" : "外滩如家酒店真不错",
    }
  },
  {
    "_score" : 11.91091,
    "_source" : {
      "name" : "迪士尼如家酒店真不错",
    }
  }
]

# TF-IDF 算法

image-20210721190152134

# BM25 算法

image-20210721190416214

# 算分函数查询

# 语法

image-20210721191544750

# function score 查询中包含的四部分内容

  • 原始查询条件:query 部分,基于这个条件搜索文档,并且基于 BM25 算法给文档打分,原始算分(query score)
  • 过滤条件:filter 部分,符合该条件的文档才会重新算分
  • 算分函数:符合 filter 条件的文档要根据这个函数做运算,得到的函数算分(function score),有四种函数
    • weight:函数结果是常量
    • field_value_factor:以文档中的某个字段值作为函数结果
    • random_score:以随机数作为函数结果
    • script_score:自定义算分函数算法
  • 运算模式:算分函数的结果、原始查询的相关性算分,两者之间的运算方式,包括:
    • multiply:相乘
    • replace:用 function score 替换 query score
    • 其它,例如:sum、avg、max、min

# function score 的运行流程

  1. 根据原始条件查询搜索文档,并且计算相关性算分,称为原始算分(query score)
  2. 根据过滤条件,过滤文档
  3. 符合过滤条件的文档,基于算分函数运算,得到函数算分(function score)
  4. 原始算分(query score)和函数算分(function score)基于运算模式做运算,得到最终结果,作为相关性算分

# 代码

GET /hotel/_search
{
  "query": {
    "function_score": {
      "query": {  .... }, // 原始查询,可以是任意条件
      "functions": [ // 算分函数
        {
          "filter": { // 满足的条件,品牌必须是如家
            "term": {
              "brand": "如家"
            }
          },
          "weight": 2 // 算分权重为 2
        }
      ],
      "boost_mode": "sum" // 加权模式,求和
    }
  }
}

# 布尔查询

# 说明

布尔查询是一个或多个查询子句的组合,每一个子句就是一个子查询。子查询的组合方式有:

  • must:必须匹配每个子查询,类似 “与”
  • should:选择性匹配子查询,类似 “或”
  • must_not:必须不匹配,不参与算分,类似 “非”
  • filter:必须匹配,不参与算分

# 语法

GET /hotel/_search
{
  "query": {
    "bool": {
      "must": [
        {"term": {"city": "上海" }}
      ],
      "should": [
        {"term": {"brand": "皇冠假日" }},
        {"term": {"brand": "华美达" }}
      ],
      "must_not": [
        { "range": { "price": { "lte": 500 } }}
      ],
      "filter": [
        { "range": {"score": { "gte": 45 } }}
      ]
    }
  }
}

# 搜索结果处理

# 排序

# 普通字段排序

# 说明

keyword、数值、日期类型排序的语法基本一致

# 语法

GET /indexName/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "FIELD": "desc"  // 排序字段、排序方式 ASC、DESC
    }
  ]
}

# 地理坐标排序

# 说明

地理坐标排序略有不同

# 语法

GET /indexName/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "_geo_distance" : {
          "FIELD" : "纬度,经度", // 文档中 geo_point 类型的字段名、目标坐标点
          "order" : "asc", // 排序方式
          "unit" : "km" // 排序的距离单位
      }
    }
  ]
}

# 分页

# 介绍

elasticsearch 默认情况下只返回 top10 的数据。而如果要查询更多数据就需要修改分页参数了。elasticsearch 中通过修改 from、size 参数来控制要返回的分页结果:

  • from:从第几个文档开始
  • size:总共查询几个文档

# 基本的分页

# 语法

GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0, // 分页开始的位置,默认为 0
  "size": 10, // 期望获取的文档总数
  "sort": [
    {"price": "asc"}
  ]
}

# 深度分页

# 语法

GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "from": 990, // 分页开始的位置,默认为 0
  "size": 10, // 期望获取的文档总数
  "sort": [
    {"price": "asc"}
  ]
}

# 比较

  • from + size
    • 优点:支持随机翻页
    • 缺点:深度分页问题,默认查询上限(from + size)是 10000
    • 场景:百度、京东、谷歌、淘宝这样的随机翻页搜索
  • after search
    • 优点:没有查询上限(单次查询的 size 不超过 10000)
    • 缺点:只能向后逐页查询,不支持随机翻页
    • 场景:没有随机翻页需求的搜索,例如手机向下滚动翻页
  • scroll
    • 优点:没有查询上限(单次查询的 size 不超过 10000)
    • 缺点:会有额外内存消耗,并且搜索结果是非实时的
    • 场景:海量数据的获取和迁移。从 ES7.1 开始不推荐,建议用 after search 方案

# 高亮

# 高亮原理

  1. 给文档中的所有关键字都添加一个标签,例如 <em> 标签
  2. 页面给 <em> 标签编写 CSS 样式

# 实现高亮

# 语法

GET /hotel/_search
{
  "query": {
    "match": {
      "FIELD": "TEXT" // 查询条件,高亮一定要使用全文检索查询
    }
  },
  "highlight": {
    "fields": { // 指定要高亮的字段
      "FIELD": {
        "pre_tags": "<em>",  // 用来标记高亮字段的前置标签
        "post_tags": "</em>" // 用来标记高亮字段的后置标签
      }
    }
  }
}

# 注意

  • 高亮是对关键字高亮,因此搜索条件必须带有关键字,而不能是范围这样的查询。
  • 默认情况下,高亮的字段,必须与搜索指定的字段一致,否则无法高亮
  • 如果要对非搜索字段高亮,则需要添加一个属性:required_field_match=false

# RestClient 查询文档

# match 查询

# 介绍

全文检索的 match 和 multi_match 查询与 match_all 的 API 基本一致。差别是查询条件,也就是 query 的部分

# 代码

@Test
void testMatch() throws IOException {
    // 1. 准备 Request
    SearchRequest request = new SearchRequest("hotel");
    // 2. 准备 DSL
    request.source()
        .query(QueryBuilders.matchQuery("all", "如家"));
    // 3. 发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 4. 解析响应
    handleResponse(response);
}

# 精确查询

# 介绍

  • term:词条精确匹配
  • range:范围查询

# API

image-20210721220305140

# 布尔查询

# 介绍

布尔查询是用 must、must_not、filter 等方式组合其它查询

# API

image-20210721220927286

# 代码

@Test
void testBool() throws IOException {
    // 1. 准备 Request
    SearchRequest request = new SearchRequest("hotel");
    // 2. 准备 DSL
    // 2.1. 准备 BooleanQuery
    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
    // 2.2. 添加 term
    boolQuery.must(QueryBuilders.termQuery("city", "杭州"));
    // 2.3. 添加 range
    boolQuery.filter(QueryBuilders.rangeQuery("price").lte(250));
    request.source().query(boolQuery);
    // 3. 发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 4. 解析响应
    handleResponse(response);
}

# 排序分页

# 介绍

搜索结果的排序和分页是与 query 同级的参数,因此同样是使用 request.source () 来设置

# API

image-20210721221121266

# 代码

@Test
void testPageAndSort() throws IOException {
    // 页码,每页大小
    int page = 1, size = 5;
    // 1. 准备 Request
    SearchRequest request = new SearchRequest("hotel");
    // 2. 准备 DSL
    // 2.1.query
    request.source().query(QueryBuilders.matchAllQuery());
    // 2.2. 排序 sort
    request.source().sort("price", SortOrder.ASC);
    // 2.3. 分页 from、size
    request.source().from((page - 1) * size).size(5);
    // 3. 发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 4. 解析响应
    handleResponse(response);
}

# 高亮

# 介绍

  • 查询的 DSL:其中除了查询条件,还需要添加高亮条件,同样是与 query 同级。
  • 结果解析:结果除了要解析_source 文档数据,还要解析高亮结果

# 高亮请求 API

image-20210721221744883

# 代码

@Test
void testHighlight() throws IOException {
    // 1. 准备 Request
    SearchRequest request = new SearchRequest("hotel");
    // 2. 准备 DSL
    // 2.1.query
    request.source().query(QueryBuilders.matchQuery("all", "如家"));
    // 2.2. 高亮
    request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));
    // 3. 发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 4. 解析响应
    handleResponse(response);
}

# 高亮结果解析

# 步骤

  • 第一步:从结果中获取 source。hit.getSourceAsString (),这部分是非高亮结果,json 字符串。还需要反序列为 HotelDoc 对象
  • 第二步:获取高亮结果。hit.getHighlightFields (),返回值是一个 Map,key 是高亮字段名称,值是 HighlightField 对象,代表高亮值
  • 第三步:从 map 中根据高亮字段名称,获取高亮字段值对象 HighlightField
  • 第四步:从 HighlightField 中获取 Fragments,并且转为字符串。这部分就是真正的高亮字符串了
  • 第五步:用高亮的结果替换 HotelDoc 中的非高亮结果

# 代码

private void handleResponse(SearchResponse response) {
    // 4. 解析响应
    SearchHits searchHits = response.getHits();
    // 4.1. 获取总条数
    long total = searchHits.getTotalHits().value;
    System.out.println("共搜索到" + total + "条数据");
    // 4.2. 文档数组
    SearchHit[] hits = searchHits.getHits();
    // 4.3. 遍历
    for (SearchHit hit : hits) {
        // 获取文档 source
        String json = hit.getSourceAsString();
        // 反序列化
        HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
        // 获取高亮结果
        Map<String, HighlightField> highlightFields = hit.getHighlightFields();
        if (!CollectionUtils.isEmpty(highlightFields)) {
            // 根据字段名获取高亮结果
            HighlightField highlightField = highlightFields.get("name");
            if (highlightField != null) {
                // 获取高亮值
                String name = highlightField.getFragments()[0].string();
                // 覆盖非高亮结果
                hotelDoc.setName(name);
            }
        }
        System.out.println("hotelDoc = " + hotelDoc);
    }
}
更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Baozi 微信支付

微信支付

Baozi 支付宝

支付宝

Baozi 微信

微信