• Elasticsearch系列---实战搜索语法


    概要

    本篇介绍Query DSL的语法案例,查询语句的调试,以及排序的相关内容。

    基本语法

    空查询

    最简单的搜索命令,不指定索引和类型的空搜索,它将返回集群下所有索引的所有文档(默认显示10条):

    GET /_search
    {}
    
    搜索多个索引
    GET /index1,index2/_doc/_search
    {}
    
    指定分页搜索
    GET /_search
    {
      "from": 0,
      "size": 10
    }
    
    get带request body

    HTTP协议,GET请求带body是不规范的做法,但由于ES搜索的复杂性,加上HTTP协议GET/POST方法表述的语义,GET更适合用来表述查询的动作,虽然不规范,但还是这么用了。现在大多数浏览器也支持GET+request body,如果遇到不支持的,换成POST即可。了解一下就行,不用太慌张。

    查询表达式Query DSL

    Query DSL是一种非常灵活、可读性高的查询语言,body为JSON格式,绝大部分功能都可以用它来展现,并且这种查询语句更纯粹,让学习者更专注于本身的功能,避免Client API的干扰。

    上一节的空查询,等价于这个:

    GET /_search
    {
        "query": {
            "match_all": {}
        }
    }
    
    基本语法
    # 查询语句结构
    {
        QUERY_NAME: {
            ARGUMENT: VALUE,
            ARGUMENT: VALUE,...
        }
    }
    
    # 针对某个字段的查询
    {
        QUERY_NAME: {
            FIELD_NAME: {
                ARGUMENT: VALUE,
                ARGUMENT: VALUE,...
            }
        }
    }
    
    合并查询语句

    再复杂的查询语句,也是由一个一个的查询条件叠加而成的,查询语句有两种形式:

    • 叶子语句:单个条件组成的语句,如match语句,类似mysql的"id = 1"这种。
    • 复合语句:有多个条件,需要合并在一起才能组成一个完整的语句,需要使用bool进行组合,里面的条件可以用must必须匹配、must not必须不匹配、should可以匹配修饰,也可以包含过滤器filter。类似mysql的"(status = 1 && language != 'french' && (author = 'John' || author = 'Tom'))"这种。

    举个例子:

    {
        "bool": {
            "must":     { "match": { "status": 1 }},
            "must_not": { "match": { "language":  "french" }},
            "should":   { "match": { "author": "John Tom" }},
            "filter":   { "range": { "length" : { "gt" : 30 }} }
        }
    }
    

    复合语句可以嵌套,来实现更复杂的查询需求,在上面的例子上简单延伸一下:

    "bool": {
            "must":     { "match": { "status": 1 }},
            "must_not": { "match": { "language":  "french" }},
            "should":   [
                {"match": { "author": "John Tom" }},
                {"bool": {
                    "must":     { "match": { "name": "friend" }},
                    "must_not": { "match": { "content":  "star" }}
                }}
            ],
            "filter":   { "range": { "length" : { "gt" : 30 }} }
        }
    
    复合语句相关性分数计算

    每一个子查询都独自地计算文档的相关性得分。一旦他们的得分被计算出来,bool 查询就将这些得分进行合并并且返回一个代表整个布尔操作的得分,得分高的显示在前面,filter内的条件不参与分数计算。

    过滤器filter

    我们还是以英文儿歌的索引为案例,看一个搜索需求:歌词内容包含friend,同时歌长大于30秒的记录

    GET /music/children/_search
    {
      "query": {
        "bool": {
          "must": [
            {
              "match": {
                "content": "friend"
              }
            }
          ],
          "filter": {
            "range": {
              "length": {
                "gte": 30
              }
            }
          }
        }
      }
    }
    

    filter与query

    • 过滤情况filtering context

    仅按照搜索条件把需要的数据筛选出来,不计算相关度分数。

    • 查询情况query context

    匹配条件的数据,会根据搜索条件的相关度,计算每个document的分数,然后按照分数进行排序,这个才是全文搜索的情况。

    性能差异

    filter只做过滤,不作排序,并且会缓存结果到内存中,性能非常高。
    query匹配条件,要做评分,没有缓存,性能要低一些。

    应用场景

    filter一个非常重要的作用就是减少不相关数据对query的影响,提升query的性能,二者常常搭配在一起使用。
    组合使用的时候,把期望符合条件的document的搜索条件放在query里,把要滤掉的条件放在filter里。

    constant_score查询

    如果一个查询只有filter过滤条件,可以用constant_score来替代bool查询,这样的查询语句更简洁、更清晰,只是没有评分,示例如下:

    GET /music/children/_search
    {
      "query": {
        "constant_score": {
          "filter": {
            "term": { "content": "gymbo"}
          }
        }
      }
    }
    

    filter内不支持terms语法,注意一下。

    最常用的查询

    再复杂的查询语句,也是由最基础的查询变化而来的,而最常用的查询其实也就那么几个。

    1. match_all查询

    查询简单的匹配所有文档

    GET /_search
    {
        "query": {
            "match_all": {}
        }
    }
    
    1. match查询

    无论是全文搜索还是精确查询,match查询是最基本的标准

    # 全文搜索例子
    { "match": { "content": "loves smile" }}
    
    # 精确搜索
    { "match": { "likes":    15           }}
    { "match": { "date":   "2019-12-05" }}
    { "match": { "isOwner": true         }}
    { "match": { "keyword":    "love you"  }}
    

    对于精确值的查询,我们可以使用filter来替代,filter有缓存的效果。

    1. multi_match查询

    可以在多个字段上执行相同的match查询

    {
        "multi_match": {
            "query":    "my sunshine",
            "fields":   [ "name", "content" ]
        }
    }
    
    1. range查询

    查询指定区间内的数字或时间,query和filter都支持,一般是filter用得多,允许的操作符如下:

    • gt 大于
    • gte 大于或等于
    • lt 小于
    • lte 小于或等于
    {
        "range": {
            "length": {
                "gte":  45,
                "lt":   60
            }
        }
    }
    
    1. term查询

    用于精确值匹配,精确值可以是数字,日期,boolean或keyword类型的字符串

    { "term": { "likes":    15           }}
    { "term": { "date":   "2019-12-05" }}
    { "term": { "isOwner": true         }}
    { "term": { "keyword":    "love you"  }}
    

    建立索引时mapping设置为not_analyzed时,match等同于term,用得多的是match和range。

    1. terms查询

    跟term类似,只是允许一次指定多个值进行匹配,只要有任何一个匹配上,都满足条件

    { "terms": { "content": [ "love", "gymbo", "sunshine" ] }}
    

    查询语句调试

    复杂的查询语句,可能会有几百行,可以先使用调试工具检测一下查询语句,定位不合法的搜索及原因,完整语法如下:

    GET /index/type/_validate/query?explain
    {
      "query": {
        ...
      }
    }
    

    explain参数可以提供更详细的查询不合法的信息,便于问题定位。写一个错误的例子,比如使用中文标点符号:

    GET /music/children/_validate/query?explain
    {
      "query": {
        "terms": { "content“: [ "love", "gymbo", "sunshine" ] }
      }
    }
    

    错误提示如下:

    {
      "valid": false,
      "error": """
    ParsingException[Failed to parse]; nested: JsonParseException[Unexpected character ('l' (code 108)): was expecting a colon to separate field name and value
     at [Source: org.elasticsearch.transport.netty4.ByteBufStreamInput@5e57280e; line: 3, column: 33]];; com.fasterxml.jackson.core.JsonParseException: Unexpected character ('l' (code 108)): was expecting a colon to separate field name and value
     at [Source: org.elasticsearch.transport.netty4.ByteBufStreamInput@5e57280e; line: 3, column: 33]
    """
    }
    

    valid关键字,true为验证通过,false为不通过,如上提示信息,会指明3行33列错误,原因是使用了中文的引号。将语法修正后,得到的正确响应如下:

    {
      "_shards": {
        "total": 1,
        "successful": 1,
        "failed": 0
      },
      "valid": true,
      "explanations": [
        {
          "index": "music",
          "valid": true,
          "explanation": "+content:(gymbo love sunshine) #*:*"
        }
      ]
    }
    

    排序

    查询请求得到的结果,默认排序是相关性得分降序。如果我们只使用filter过滤,符合filter条件的文档,评分都是一样的(bool的filter得分是null,constant_score得分是1),结果文档还是随机返回,显然这样的排序不符合我们的预期。

    sort排序规则

    为此,我们可以使用sort属性,对文档进行排序,sort的用法与mysql如出一辙,示例如下:

    GET /music/children/_search
    {
      "query": {
        "bool": {
            "filter":   { "range": { "length" : { "gt" : 30 }} }
        }
      },
      "sort": [
        {
          "length": {
            "order": "desc"
          }
        }
      ]
    }
    

    sort内可以同时指定多个排序字段,一旦使用sort排序后,_score得分将变成null,因为我们指定了排序规则,_score没有实际意义了,就不用耗费精力再去计算它。

    字符串排序问题

    我们知道text类型的字段,会有关键词分词处理,对这样的字段进行排序,结果往往都不准确,6.x版本以后的text类型,会再自动建立一个keyword类型的字段,这个字段是不分词的,所以这样就有了分工,text类型的负责搜索,keyword类型则负责排序。
    我们回顾一下music索引的mapping信息(节选):

    {
      "music": {
        "mappings": {
          "children": {
            "properties": {
              "content": {
                "type": "text",
                "fields": {
                  "keyword": {
                    "type": "keyword",
                    "ignore_above": 256
                  }
                }
              },
              "name": {
                "type": "text",
                "fields": {
                  "keyword": {
                    "type": "keyword",
                    "ignore_above": 256
                  }
                }
              }
            }
          }
        }
      }
    }
    

    例如name字段,有一个text类型的,里面fields还有一个类型为keyword,名称也为keyword的字段,所以在排序的场景中,我们应该使用name.keyword,示例如下:

    GET /music/children/_search
    {
      "sort": [
        {
          "name.keyword": {
            "order": "asc"
          }
        }
      ]
    }
    

    小结

    本篇介绍Query DSL的语法及基础实战内容,顺带点了一下filter与query的区别,面对复杂查询语句时,建议先用验证工具进行排查,最后介绍了一下排序方面的知识,基础语法、上机案例多实践即可。

    专注Java高并发、分布式架构,更多技术干货分享与心得,请关注公众号:Java架构社区
    Java架构社区

  • 相关阅读:
    sys模块
    反射
    动态导入模块
    类的静态属性
    多态
    继承
    组合
    linux系统各个子目录的内容
    mysql安装
    Docker 数据卷操作
  • 原文地址:https://www.cnblogs.com/huangying2124/p/12129049.html
Copyright © 2020-2023  润新知