DSL查询文档

## 1.DSL查询文档

1.1.DSL查询分类

## 1.1.DSL查询分类

Elasticsearch提供了基于JSON的DSL([Domain Specific Language](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html))来定义查询。常见的查询类型包括:

- **查询所有**:查询出所有数据,一般测试用。例如:match_all

 

GET /indexName/_search
{
  "query": {
    "查询类型": {
      "查询条件": "条件值"
    }
  }
}

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

 

 

- **全文检索(full text)查询**:

  利用分词器对用户输入内容分词,然后去倒排索引库中匹配。

#全文检索查询的基本流程如下:

  #- 对用户搜索的内容做分词,得到词条
  #- 根据词条去倒排索引库中匹配,得到文档id
  #- 根据文档id找到文档,返回给用户

例如:
- match_query
- multi_match_query

# 单字段查询
GET /hotel/_search
{
  "query": {
    "match": {
      "city": "北京"
    }
  }
}

# 多字段查询,任意一个字段符合条件就算符合查询条件
# 参与查询字段越多,查询性能越差
GET /hotel/_search
{
  "query": {
    "multi_match": {
      "query": "外滩",
      "fields": ["brand", "name", "business"]
    }
  }
}

  


- **精确查询**:

根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段。例如:
- ids

- term查询:根据词条精确匹配,一般搜索keyword类型、数值类型、布尔类型、日期类型字段
- range查询:根据数值范围查询,可以是数值、日期的范围

#term
GET /hotel/_search
{
  "query": {
    "term": {
      "city": {
        "value": "北京"
      }
    }
  }
}
#range
#这里的gte代表大于等于,gt则代表大于
#lte代表小于等于,lt则代表小于
GET /hotel/_search
{
  "query": {
    "range": {
      "price": {
        "gte":100,
        "lte":300 
      }
    }
  }
}

 


- **地理(geo)查询**:

根据经纬度查询。例如:
- geo_distance
- geo_bounding_box

# geo_distance 附近查询
# 附近查询,也叫做距离查询(geo_distance):查询到指定中心点小于某个距离值的所有文档。
GET /hotel/_search
{
  "query": {
    "geo_distance": {
      "distance": "15km",
      "location": "31.21,121.5"
    }
  }
}

# geo_bounding_box 矩形范围查询
GET /hotel/_search
{
  "query":{
    "geo_bounding_box":{
     "location":{
        "top_left":{
          "lat":31.1,
          "lon":121.5
        },
        "bottom_right":{
          "lat":30.9,
          "lon":121.7
        }
      }
    }
  }
}

 

- **复合(compound)查询**:

复合查询可以将上述各种查询条件组合起来,合并查询条件。例如:
- bool -

function_score

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)基于**运算模式**做运算,得到最终结果,作为相关性算分。

 

因此,其中的关键点是:

- 过滤条件:哪些文档要加分
- 算分函数:如何计算function score
- 加权方式:function score 与 query score如何运算

GET /hotel/_search
{
  "query":{
    "function_score": {
      "query": {"match": {
        "all": "外滩"
      }},
      "functions": [
        {
          "filter": {"term":{"brand":"如家"}},
          "weight": 10
        }
      ],
      "boost_mode": "multiply"
    }
  }
}

 

 

 

 

- TF-IDF算法
- BM25算法,elasticsearch5.1版本后采用的算法

 

 

 

 

 

 TF-IDF算法有一各缺陷,就是词条频率越高,文档得分也会越高,单个词条对文档影响较大。而BM25则会让单个词条的算分有一个上限,曲线更加平滑:

 

 

 

布尔查询

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

- 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}}}
      ]
    }
  }
}

 

 

##2.搜索结果处理

### 2.1.1.普通字段排序

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

**语法**:

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

 

排序条件是一个数组,也就是可以写多个排序条件。按照声明的顺序,当第一个条件相等时,再按照第二个条件排序,以此类推。

 

### 2.1.2.地理坐标排序

GET /hotel/_search
{
  "query":{
    "match_all": {}
  },
  "sort":{
    "_geo_distance":{
      "location": {
        "lat": "31.034661",
        "lon": "121.612282"
      },
      "order":"asc", 
      "unit":"km"
    }
  }
}

这个查询的含义是:

- 指定一个坐标,作为目标点

- 计算每一个文档中,指定字段(必须是geo_point类型)的坐标 到目标点的距离是多少
- 根据距离排序

 

## 2.2.分页

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

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

类似于mysql中的`limit ?, ?`

### 2.2.1.基本的分页

分页的基本语法如下:

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

 


现在,我要查询990~1000的数据,查询逻辑要这么写:

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

 

这里是查询990开始的数据,也就是 第990~第1000条 数据。

不过,elasticsearch内部分页时,必须先查询 0~1000条,然后截取其中的990 ~ 1000的这10条:

 

 

 

查询TOP1000,如果es是单点模式,这并无太大影响。

但是elasticsearch将来一定是集群,例如我集群有5个节点,我要查询TOP1000的数据,并不是每个节点查询200条就可以了。

因为节点A的TOP200,在另一个节点可能排到10000名以外了。

因此要想获取整个集群的TOP1000,必须先查询出每个节点的TOP1000,汇总结果后,重新排名,重新截取TOP1000。

 

 

 

那如果我要查询9900~10000的数据呢?是不是要先查询TOP10000呢?那每个节点都要查询10000条?汇总到内存中?

 

当查询分页深度较大时,汇总数据过多,对内存和CPU会产生非常大的压力,因此elasticsearch会禁止from+ size 超过10000的请求。

 

针对深度分页,ES提供了两种解决方案,[官方文档](https://www.elastic.co/guide/en/elasticsearch/reference/current/paginate-search-results.html):

- search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。
- scroll:原理将排序后的文档id形成快照,保存在内存。官方已经不推荐使用。

- search after 例子:
GET /hotel/_search { "query": { "match_al": {} }, "size": 2, "from": 0, "search_after": [ 135,45 ], "sort": [ {"price": "asc"}, {"score": "asc"} ] }

 

### 2.2.3.小结

分页查询的常见实现方案以及优缺点:

- `from + size`:
- 优点:支持随机翻页
- 缺点:深度分页问题,默认查询上限(from + size)是10000
- 场景:百度、京东、谷歌、淘宝这样的随机翻页搜索
- `after search`:
- 优点:没有查询上限(单次查询的size不超过10000)
- 缺点:只能向后逐页查询,不支持随机翻页
- 场景:没有随机翻页需求的搜索,例如手机向下滚动翻页

- `scroll`:
- 优点:没有查询上限(单次查询的size不超过10000)
- 缺点:会有额外内存消耗,并且搜索结果是非实时的
- 场景:海量数据的获取和迁移。从ES7.1开始不推荐,建议用 after search方案。

 

## 2.3.高亮

### 2.3.1.高亮原理

什么是高亮显示呢?

我们在百度,京东搜索时,关键字会变成红色,比较醒目,这叫高亮显示:

 

 

 

高亮显示的实现分为两步:

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

 

### 2.3.2.实现高亮

**高亮的语法**:

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

 

GET /hotel/_search
{
  "query": {
    "match": {
      "all": "如家"
    }
  },
  "size": 2,
  "from": 0,
  "highlight": {
    "fields": {
      "name": {
        "require_field_match": "false"
      }
    }
  }
}

**注意:**

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

 

 

##3.聚合查询

### 3.1.1.聚合的种类

聚合常见的有三类:

- **桶(Bucket)**聚合:用来对文档做分组
- TermAggregation:按照文档字段值分组,例如按照品牌值分组、按照国家分组
- Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组

- **度量(Metric)**聚合:用以计算一些值,比如:最大值、最小值、平均值等
- Avg:求平均值
- Max:求最大值
- Min:求最小值
- Stats:同时求max、min、avg、sum等
- **管道(pipeline)**聚合:其它聚合的结果为基础做聚合

 

> **注意:**参加聚合的字段必须是keyword、日期、数值、布尔类型

## 3.1.2.DSL实现聚合

现在,我们要统计所有数据中的酒店品牌有几种,其实就是按照品牌对数据分组。此时可以根据酒店品牌的名称做聚合,也就是Bucket聚合。

### 3.2Bucket聚合语法

语法如下:

GET /hotel/_search
{
  "size":0,
  "query":{
    "term":
    {"city":"北京"}
  },
  "aggs":{
    "brandAgg": {
      "terms":{
        "field":"brand",
        "size":20,
        "order":{
          "_count": "asc"
        }
      }
    }
  }
}

### 3.3Metric聚合语法

上节课,我们对酒店按照品牌分组,形成了一个个桶。现在我们需要对桶内的酒店做运算,获取每个品牌的用户评分的min、max、avg等值。

这就要用到Metric聚合了,例如stat聚合:就可以获取min、max、avg等结果。

语法如下:

```json
GET /hotel/_search
{
  "size": 0, 
  "aggs": {
    "brandAgg": { 
      "terms": { 
        "field": "brand", 
        "size": 20
      },
      "aggs": { // 是brands聚合的子聚合,也就是分组后对每组分别计算
        "score_stats": { // 聚合名称
          "stats": { // 聚合类型,这里stats可以计算min、max、avg等
            "field": "score" // 聚合字段,这里是score
          }
        }
      }
    }
  }
}

这次的score_stats聚合是在brandAgg的聚合内部嵌套的子聚合。因为我们需要在每个桶分别计算。

另外,我们还可以给聚合结果做个排序,例如按照每个桶的酒店平均分做排序:

  

- 限定聚合的的文档范围

聚合必须的三要素:

- 聚合名称
- 聚合类型
- 聚合字段

聚合可配置属性有:

- size:指定聚合结果数量
- order:指定聚合结果排序方式
- field:指定聚合字段

 

posted @ 2022-05-22 23:49  year12  阅读(195)  评论(0编辑  收藏  举报