ElasticSearch DSL 查询
in Develop with 0 comment
ElasticSearch DSL 查询
in Develop with 0 comment

ElasticSearch DSL 查询

DSL(Domain Specific Language)查询也叫做 Request Body 查询,它比 URI 查询更高阶,能支持更复杂的查询。

1,分页

默认情况下,查询按照算分排序,返回前 10 条记录。

ES 也支持分页,分页使用 from-size

示例:

    POST /index_name/_search
    {
      "from":10,
      "size":20,
      "query":{
        "match_all": {}
      }
    }

1.1,深度分页问题

ES 是一个分布式系统,数据保存在多个分片中,那么查询时就需要查询多个分片。

比如一个查询 from = 990; size = 10,那么 ES 需要在每个分片上都获取 1000 个文档:

然后通过 Coordinating 节点汇总结果,最后再通过排序获取前 1000 个文档。

这种方式,当页数很深的时候,就会占用很多内存,从而给 ES 集群带来很大的开销,这就是深度分页问题

因此,ES 为了避免此类问题带来的巨大开销,有个默认的限制 index.max\_result\_windowfrom + size 必须小于等于 10000,否则就会报错

比如:

    POST index_name/_search
    {
      "from": 10000,  # 报错
      "size": 1,
      "query": {
        "match_all": {}
      }
    }
    
    POST index_name/_search
    {
      "from": 0,  # 报错
      "size": 10001,
      "query": {
        "match_all": {}
      }
    }

为了解决深度分页问题,ES 有两种解决方案:Search AfterScroll

1.2,Search After

Search After 通过实时获取下一页的文档信息来实现,使用方法:

Search After 的方式不支持指定页数,只能一页一页的往下翻。

Search After 的原理:

在这里插入图片描述

示例:

    # 插入一些数据
    DELETE users
    
    POST users/_doc
    {"name":"user1","age":10}
    
    POST users/_doc
    {"name":"user2","age":11}
    
    POST users/_doc
    {"name":"user2","age":12}
    
    POST users/_doc
    {"name":"user2","age":13}
    
    # 第一次搜索
    POST users/_search
    {
        "size": 1,   # size 值
        "query": {
            "match_all": {}
        },
        "sort": [
            {"age": "desc"} ,
            {"_id": "asc"}  # sort by id  
        ]
    }
    
    # 此时返回的文档中有一个 sort 值
    # "sort" : [13, "4dR-IHcB71-f4JZcrL2z"]
    
    # 之后的每一次搜索都需要用到上一次搜索结果的最后一个文档的 sort 值
    POST users/_search
    {
        "size": 1,
        "query": {
            "match_all": {}
        },
        "search_after": [ # 上一次搜索结果的最后一个文档的 sort 值放在这里
            13, "4dR-IHcB71-f4JZcrL2z"], 
        "sort": [
            {"age": "desc"} ,
            {"_id": "asc"}    
        ]
    }

1.3,Scroll

Scroll 通过创建一个快照来实现,方法:

Scroll 方式的缺点是,当有新的数据写入时,新写入的数据无法被查到(第一次建立快照时有多少数据,就只能查到多少数据)。

示例:

    # 写入测试数据
    DELETE users
    POST users/_doc
    {"name":"user1","age":10}
    
    POST users/_doc
    {"name":"user2","age":20}
    
    # 第一次查询前,先建立快照,快照存在时间为 5 分钟,一般不要太长
    POST /users/_search?scroll=5m
    {
        "size": 1,
        "query": {
            "match_all" : {}
        }
    }
    
    # 返回的结果中会有一个 _scroll_id
    
    # 查询
    POST /_search/scroll
    {
        "scroll" : "1m", # 快照的生存时间,这里是 1 分钟
        "scroll_id" : "xxx==" # 上一次的 _scroll_id 值
    }
    
    # 每次的查询结果都会返回一个 _scroll_id,供下一次查询使用
    # 所有的数据被查完以后,再查询就得不到数据了

1.4,不同分页方式的使用场景

分页方式共 4 种:

2,排序

ES 默认使用算分进行排序,我们可以使用 sort-processor(不需要再计算算分)来指定排序规则;可以对某个字段进行排序,最好只对数字型日期型字段排序。

示例:

    POST /index_name/_search
    {
      "sort":[{"order_date":"desc"}], # 单字段排序
      "query":{
        "match_all": {}
      }
    }
    
    POST /index_name/_search
    {
      "query": {
        "match_all": {}
      },
      "sort": [ # 多字段排序
        {"order_date": {"order": "desc"}},
        {"_doc":{"order": "asc"}},
        {"_score":{ "order": "desc"}} # 如果不指定 _score,那么算分为 null
      ]
    }

text 类型的数据进行排序会发生错误,可以通过打开 fielddata 参数(一般不建议这么做),来对 text 类型进行排序:

    # 打开 text的 fielddata
    PUT index_name/_mapping
    {
      "properties": {
        "customer_full_name" : {       # 字段名称
              "type" : "text",
              "fielddata": true,       # 打开 fielddata
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            }
      }
    }

3,字段过滤

可以使用 _source 设置需要返回哪些字段。示例:

    POST /index_name/_search
    {
      "_source":["order_date", "xxxxx"],
      "query":{
        "match_all": {}
      }
    }

_source 中可以使用通配符,比如 ["name*", "abc*"]

4,脚本字段

可以使用脚本进行简单的表达式运算。

    POST /index_name/_search
    {
      "script_fields": { # 固定写法
        "new_field": {   # 新的字段名称
          "script": {    # 固定写法
            "lang": "painless", # 固定写法
            "source": "doc['order_date'].value+'hello'" # 脚本语句
          }
        }
      },
      "query": {
        "match_all": {}
      }
    }

5,查询与过滤

查询会有相关性算分;过滤不需要进行算分,可以利用缓存,性能更好。

参考这里

6,全文本查询

全文本(Full text)查询会对搜索字符串进行分词处理

全文本查询有以下 9 种:

  1. intervals 查询:可以对匹配项的顺序和接近度进行细粒度控制。
  2. match 查询:全文本查询中的标准查询,包括模糊匹配、短语和近似查询。
  3. match\_bool\_prefix 查询
  4. match\_phrase 查询
  5. match\_phrase\_prefix 查询
  6. multi\_match 查询
  7. common terms 查询
  8. query\_string 查询
  9. simple\_query\_string 查询

6.1,Match 查询

Match 查询是全文搜索的标准查询,与下面的几种查询相比,更加强大,灵活性也更大,最常使用。

Match 查询会先对输入字符串进行分词,然后对每个词项进行底层查询,最后将结果合并。

例如对字符串 “Matrix reloaded” 进行查询,会查到包含 “Matrix” 或者 “reloaded” 的所有结果。

在这里插入图片描述

示例:

    POST index_name/_search
    {
      "query": {
        "match": {
          "title": "last christmas" # 表示包含 last 或 christmas
        }
      }
    }
    
    POST index_name/_search
    {
      "query": {
        "match": {
          "title": { # 表示包含 last 且 包含 christmas,不一定挨着
            "query": "last christmas",
            "operator": "and"
          }
        }
      }
    }

6.2,Match Phrase 查询

使用 match\_phrase 关键字。示例:

    POST index_name/_search
    {
      "query": {
        "match_phrase": {
          "title":{
            "query": "one love" # "one love" 相当于一个单词
          }
        }
      }
    }
    
    POST index_name/_search
    {
      "query": {
        "match_phrase": {
          "title":{
            "query": "one love",
            "slop": 1 # "one" 和 "love" 之间可以有 1 个字符
          }
        }
      }
    }

6.3,Query String 查询

使用 query\_string 关键字。示例:

    POST index_name/_search
    {
      "query": {
        "query_string": {
          "default_field": "name",   # 默认查询字段,相当于 URI 查询中的 df
          "query": "Ruan AND Yiming" # 可以使用逻辑运算符
        }
      }
    }
    
    # 多 fields 与 分组
    POST index_name/_search
    {
      "query": {
        "query_string": {
          "fields":["name","about"], # 多个 fields
          "query": "(Ruan AND Yiming) OR (Java AND Elasticsearch)" # 支持分组
        }
      }
    }
    
    POST index_name/_search
    {
      "query":{
        "query_string":{
          "fields":["title","year"],
          "query": "2012"
         }
       }
    }

6.4,Simple Query String 查询

使用 simple\_query\_string 关键字。

特点:

示例:

    # Simple Query 默认的 operator 是 OR
    POST index_name/_search
    {
      "query": {
        "simple_query_string": {
          "query": "Ruan AND Yiming", # 这里的 AND 会当成普通的字符串
          "fields": ["name"]
        }
      }
    }
    
    POST index_name/_search
    {
      "query": {
        "simple_query_string": {
          "query": "Ruan Yiming",
          "fields": ["name"],
          "default_operator": "AND"
        }
      }
    }
    
    GET index_name/_search
    {
        "query":{
            "simple_query_string":{
                "query":"Beautiful +mind",
                "fields":["title"]
            }
        }
    }

6.5,Multi-match 查询

一个字符串在多个字段中查询的情况,如何匹配最终的结果。(还有一个 dis-max 查询也是针对这种情况的)

一个字符串在多个字段中查询的情况,Multi-match 有 6 种处理方式,如下:

示例:

    POST index_name/_search
    {
      "query": {
        "multi_match" : {              # multi_match 查询
          "query":      "brown fox",   # 查询字符串
          "type":       "best_fields", # 处理方式
          "fields":     [ "subject", "message" ], # 在多个字段中查询,fields 是一个数组
          "tie_breaker": 0.3
        }
      }
    }

7,Term 查询

Term 查询全文本查询不同的是,Term 查询不会对查询字符串进行分词处理,Term 查询会在字段匹配精确值

Term 查询输入字符串作为一个整体,在倒排索引中查找匹配的词项,并且会计算相关性评分

Term 查询包括以下 11 种:

  1. exists 查询
  2. fuzzy 查询
  3. ids 查询
  4. prefix 查询
  5. range 查询
  6. regexp 查询
  7. term 查询:如果某个文档的指定字段包含某个确切值,则返回该文档。
  8. terms 查询
  9. terms\_set 查询
  10. type 查询
  11. wildcard 查询

7.0,结构化数据与查询

结构化查询是对结构化数据的查询,可以使用 Term 语句进行查询。

结构化数据有着固定的格式,包括:

结构化查询是对结构化数据的逻辑运算,运算结果只有“是”和“否”。

7.1,term 查询

如果某个文档的指定字段包含某个确切值,则返回该文档。

1,示例 1 精确匹配

下面举一个 term 查询的例子,首先插入一个文档:

POST /products/_bulk
{ "index": { "_id": 1 }}
{ "productID" : "XHDK-A-1293-#fJ3","desc":"iPhone" }

该文档插入时,会使用默认的分词器进行分词处理。

使用 term 查询:

    POST /products/_search
    {
      "query": {
        "term": {
          "desc": {
            # "value": "iPhone" # 会对 iPhone 精确匹配查询。
                                # 文档插入时,iPhone 变成了 iphone
                                # 所以查 iPhone 查不到任何内容
            "value":"iphone"    # 查 iphone 能查到
          }
        }
      }
    }

keyword 子字段

ES 默认会对 text 类型的数据建立一个 keyword 子字段,用于精确匹配,这称为 ES 的多字段属性

keyword 子字段将原始数据原封不动的存储了下来。

可以通过 mapping 查看,如下所示:

    "desc" : {          # 字段名称
      "type" : "text",  # text 数据类型
      "fields" : {
        "keyword" : {   # keyword 子字段
          "type" : "keyword",  # keyword 子类型
          "ignore_above" : 256
        }
      }
    }

下面使用 keyword 子字段进行查询:

    POST /products/_search
    {
      "query": {
        "term": {
          "desc.keyword": {     # 在 desc 字段的 keyword 子字段中查询
            "value": "iPhone"   # 能查到
            //"value":"iphone"  # 查不到
          }
        }
      }
    }
2,示例 2 查询布尔值

term 查询有算分:

    POST index_name/_search
    {
      "query": {    # 固定写法
        "term": {   # term 查询,固定写法
          "avaliable": true  # 查询 avaliable 字段的值为 true 的文档
        }
      }
    }

如果不需要算分,可以使用 constant\_score 查询,示例:

    POST index_name/_search
    {
      "query": {
        "constant_score": {        # constant_score 查询,固定写法
          "filter": {              # 固定写法
            "term": {              # constant_score 包装一个 term 查询,就没有了算分
              "avaliable": true
            }
          }
        }
      }
    }

7.2,range 查询

range 查询中有几个常用的比较运算:

运算符含义
gt大于
gte大于等于
lt小于
lte小于等于
1,数字类型 range 查询

示例:

POST index_name/_search
{
  "query": {       # 固定写法
    "range": {     # range 查询
      "age": {     # 字段名称
        "gte": 10, # 10 <= age <= 20
        "lte": 20
      }
    }
  }
}

2,日期类型 range 查询

对于日期类型有几个常用的符号:

符号含义
y
M
w
d
H / h小时
m分钟
s
now现在

示例:

    POST index_name/_search
    {
      "query" : {      # 固定写法
        "range" : {    # range 查询
          "date" : {   # 字段名称
            "gte" : "now-10y"  # 10年之前
          }
        }
      }
    }

7.3,exists 查询

exists 语句可以判断文档是否存在某个字段

搜索存在某个字段的文档,示例:

    POST index_name/_search
    {
      "query" : {
        "exists": {  # 存在 date 字段的文档
            "field": "date"
          }
       }
    }

搜索不存在某个字段的文档,需要使用布尔查询

示例:

    POST index_name/_search
    {
      "query": {
        "bool": {       # 布尔查询
          "must_not": { # 不存在
            "exists": { # 不存在 date 字段的文档
              "field": "date"
            }
          }
        }
      }
    }

7.4,terms 查询

terms 语句用于处理多值查询,相当于一个多值版的 term 语句,可以一次查询多个值。

示例:

    POST index_name/_search
    {
      "query": {
        "terms": {  # terms 查询
          "productID.keyword": [  # 字段名称
            "QQPX-R-3956-#aD8",   # 多个值
            "JODL-X-1937-#pV7"
          ]
        }
      }
    }

8,复合查询

复合查询(Compound)能够包装其他复合查询叶查询,以组合其结果和分数,更改其行为或者将查询转成过滤

复合查询有以下 5 种:

8.1,bool 查询

bool 查询是一个或多个子查询的组合,共包含以下 4 种子句:

bool 查询的多个子句之间没有顺序之分,并且可以嵌套

示例:

    POST index_name/_search
    {
      "query": {
        "bool" : {
          "must" : {
            "term" : { "user.id" : "kimchy" }
          },
          "filter": {
            "term" : { "tags" : "production" }
          },
          "must_not" : {
            "range" : {
              "age" : { "gte" : 10, "lte" : 20 }
            }
          },
          "should" : [ # 是一个数组
            { "term" : { "tags" : "env1" } },
            { "term" : { "tags" : "deployed" } }
          ],
          "minimum_should_match" : 1,
          "boost" : 1.0
        }
      }
    }

8.2,boosting 查询

boosting 查询会给不同的查询条件分配不同的级别(positive / negative),不同的级别对算分有着不同的印象,从而影响最终的算分。

positive 级别会对算分有正面影响negative 级别会对算分有负面影响

我们可以使用 boosting 查询给某些文档降级(降低算分),而不是将其从搜索结果中排除

示例:

    GET index_name/_search
    {
      "query": {
        "boosting": {      # boosting 查询
          "positive": {    # positive 级别
            "term": {      # 匹配 apple 的会对算分有正面影响
              "text": "apple" 
            }
          },
          "negative": {   # negative 级别
            "term": {     # 匹配这个的会对算分有负面影响
              "text": "pie tart fruit crumble tree"
            }
          },
          "negative_boost": 0.5 # 降级的力度
        }
      }
    }

8.3,constant\_score 查询

constant\_score 查询可以将查询转成一个过滤,可以避免算分(降低开销),并有效利用缓存(提高性能)。

示例:

    POST /index_name/_search
    {
      "query": {
        "constant_score": {   # constant_score  查询
          "filter": {         # 过滤器,固定写法
            "term": {         # 包装了一个 term 查询,将 term 查询转成了过滤
              "productID.keyword": "XHDK-A-1293-#fJ3"
            }
          }
        }
      }
    }

8.4,dis\_max 查询

一个字符串在多个字段中查询的情况,如何匹配最终的结果。(还有一个 Multi-match 查询也是针对这种情况的)

示例:

    POST index_name/_search
    {
        "query": {
            "bool": {
                "should": [  # should 语句会综合所有的字段的分数,最终给出一个综合分数
                    { "match": { "title": "Brown fox" }},
                    { "match": { "body":  "Brown fox" }}
                ]
            }
        }
    }
    
    POST index_name/_search
    {
        "query": {
            "dis_max": {     # dis_max 语句不会综合所有字段的分数,而把每个字段单独来看
                "queries": [ # 最终结果是所有的字段中分数最高的
                    { "match": { "title": "Quick pets" }},
                    { "match": { "body":  "Quick pets" }}
                ]
            }
        }
    }

8.5,function\_score 查询

function\_score 查询可以在查询结束后,对每一个匹配的文档进行重新算分,然后再根据新的算分进行排序。

它提供了以下 5 种算分函数:

1,field\_value\_factor 示例

首先插入测试数据:

    DELETE blogs
    PUT /blogs/_doc/1
    {
      "title":   "About popularity",
      "content": "In this post we will talk about...",
      "votes":   0
    }
    
    PUT /blogs/_doc/2
    {
      "title":   "About popularity",
      "content": "In this post we will talk about...",
      "votes":   100
    }
    
    PUT /blogs/_doc/3
    {
      "title":   "About popularity",
      "content": "In this post we will talk about...",
      "votes":   1000000
    }

查询示例1:

新的算分 = 老的算分 * 投票数

    POST /blogs/_search
    {
      "query": {
        "function_score": {
          "query": {
            "multi_match": { # 该查询会有一个算分
              "query":    "popularity",
              "fields": [ "title", "content" ]
            }
          },
          "field_value_factor": {  # 最终的算分要乘以 votes 字段的值
            "field": "votes"
          }
        }
      }
    }

上面这种算法当出现这两种情况的时候,会出现问题:

查询示例2,引入平滑函数

新的算分 = 老的算分 * 平滑函数(投票数)

    POST /blogs/_search
    {
      "query": {
        "function_score": {
          "query": {
            "multi_match": {
              "query":    "popularity",
              "fields": [ "title", "content" ]
            }
          },
          "field_value_factor": {
            "field": "votes",
            "modifier": "log1p"  # 在原来的基础上加了一个平滑函数
          }                      # 新的算分 = 老的算分 * log(1 + 投票数)
        }
      }
    }

平滑函数有下面这些:

在这里插入图片描述

查询示例3,引入 factor

新的算分 = 老的算分 * 平滑函数(factor * 投票数)

    POST /blogs/_search
    {
      "query": {
        "function_score": {
          "query": {
            "multi_match": {
              "query":    "popularity",
              "fields": [ "title", "content" ]
            }
          },
          "field_value_factor": {
            "field": "votes",
            "modifier": "log1p" ,
            "factor": 0.1
          }
        }
      }
    }

引入 factor 之后的算分曲线:

在这里插入图片描述

2,Boost Mode 和 Max Boost 参数

Boost Mode:

Max Boost 可以将算分控制在一个最大值。

示例:

    POST /blogs/_search
    {
      "query": {
        "function_score": {
          "query": {
            "multi_match": {
              "query":    "popularity",
              "fields": [ "title", "content" ]
            }
          },
          "field_value_factor": {
            "field": "votes",
            "modifier": "log1p" ,
            "factor": 0.1
          },
          "boost_mode": "sum",
          "max_boost": 3
        }
      }
    }
3,random\_score 示例

示例:

    POST /blogs/_search
    {
      "query": {
        "function_score": {
          "random_score": {  # 将原来的查询结果随机排序
            "seed": 911119   # 随机种子
          }
        }
      }
    }

The article has been posted for too long and comments have been automatically closed.