• ElasticSearch DSL 查询


    公号:码农充电站pro
    主页:https://codeshellme.github.io

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

    1,分页

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

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

    • from:从第几个文档开始返回,默认为 0。
    • size:返回的文档数,默认为 10。

    示例:

    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 通过实时获取下一页的文档信息来实现,使用方法:

    • 第一步搜索需要指定 sort,并且保证值是唯一的(通过sort by id 来保证)。
    • 随后的搜索,都使用上一次搜索的最后一个文档的 sort 值进行搜索。

    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 Id

    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 种:

    • 普通查询(不使用分页):需要实时获取顶部的部分文档。
    • From-Size(普通分页):适用于非深度分页。
    • Search After:需要深度分页时使用。
    • Scroll:需要全部文档,比如导出全部数据。

    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 关键字。

    特点:

    • query 字段中不支持 AND OR NOT,会当成普通字符串。
      • AND+ 替代
      • OR| 替代
      • NOT- 替代
    • Term 之间默认的关系是 OR,可以指定 default_operator 来修改。

    示例:

    # 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 种处理方式,如下:

    • best_fields:最终得分为分数最高的那个字段,默认的处理方式。
    • most_fields:算分相加。不支持 AND 操作。
    • cross_fields:跨字段搜索,将一个查询字符串在多个字段(就像一个字段)上搜索。
    • phrase
    • phrase_prefix
    • bool_prefix

    示例:

    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 种子句:

    • must:必须匹配,属于查询,贡献算分。
    • filter:必须匹配,属于过滤器,不贡献算分。
    • should:选择性匹配,只要有一个条件匹配即可,属于查询,贡献算分。
    • must_not:必须不匹配,属于过滤器,不贡献算分。

    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 种算分函数:

    • script_score:自定义脚本。
    • weight:为文档设置一个权重。
    • random_score:随机算分排序。
    • field_value_factor:使用该数值来修改算分。
    • decay functions: gauss, linear, exp:以某个字段为标准,距离某个值越近,得分越高。
    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"
          }
        }
      }
    }
    

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

    • 投票数为 0
    • 投票数特别大

    查询示例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:

    • Multiply:算分与函数值的乘积。
    • Sum:算分与函数值的和。
    • Min / Max:算分与函数值的最小/最大值。
    • Replace:使用函数值替代算分。

    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   # 随机种子
          }
        }
      }
    }
    

    (本节完。)


    推荐阅读:

    ElasticSearch 查询

    ElasticSearch URI 查询

    ElasticSearch 文档及操作

    ElasticSearch 分词器

    ElasticSearch 搜索引擎概念简介


    欢迎关注作者公众号,获取更多技术干货。

    码农充电站pro

  • 相关阅读:
    MOF编译器无法连接VMI服务器。原因可能是语义错误的解决方案
    【原】Sql Server 2008---安装时卸载Visual Studio
    SQL SERVER 删除前判断指定的表或者存储过程是否存在
    NPOI 表头、页眉页脚重复设置
    在vs2010中显示代码的行数
    【summary】JQuery 相关css、ajax、数据操作函数或方法
    CSS自适应的占位符效果
    NPOI 生成 excel基本设置
    JavaSE——转换流和缓冲流
    javaSE——字符流
  • 原文地址:https://www.cnblogs.com/codeshell/p/14435120.html
Copyright © 2020-2023  润新知