前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >像SELECT*一样手撸Query DSL——ElasticSearch下篇

像SELECT*一样手撸Query DSL——ElasticSearch下篇

作者头像
老李秀
发布2020-08-31 09:44:40
1.4K0
发布2020-08-31 09:44:40
举报

大家好 泥腿子安尼特又和大家见面了。不知道大家昨晚过的如何,容我再孤寡孤寡孤寡几声

我这个人比较懒,但是有些东西没完结,总是有时候脑子里挂念着,所以心心念念的想把ElasticSearch系列完结,当然自己也不想水完一篇文章,希望大家看完这篇,就能“精通”ES的查询了。

当年我还在读大学的时候,尽管我经常上课玩手机,睡觉,但是我数据库的老师的一句话深深的印在了我的脑海里,原话大概是这样的——这个世界上有一门编程语言,出来到现在几十年了,语法简单,基本没怎么变过,各种通用,从业人员的职业生涯也很持久,工资也高。大家猜猜是啥- - 那就是无所不能的sql。仔细想想,是不是很有道理,从普通的取数BI到数据分析师、策略师再到大数据之类的spark sql、hive sql、flink sql,就算你是个业务仔,不管数据库用的mysql、oracle、pgsql、tidb...等 sql基本上长的差不多。是不是发现sql这门语言有多无敌了,想想现在争论java好还是go好,真是too young too simple。

所以,我一开始摸到ElasticSearch的时候,我就想,这个是不是也能用sql语句来查询,一搜,果然是有ElasticSearch SQL,不过因为它并不支持完整的sql语法,所以如果你只是简单的查一查,又不想学习复杂的ES查询语句,那还是非常好用的!

数据源

代码语言:javascript
复制
PUT lib_sql
POST /lib_sql/_doc
{
  "name":"anit",
  "age":27,
  "interests":"lol, lol, lol"
}

POST /_sql?format=txt
{
  "query":"SELECT * from lib_sql"
}

结果

代码语言:javascript
复制
      age      |       interests        |     name      
---------------+------------------------+---------------
18             |chang, tiao, rap, lanqiu|cxk            
27             |lol, lol, lol           |anit

是不是感觉已经精通ES的查询了,我们再插入一条这样的数据。

代码语言:javascript
复制
POST /lib_sql/_doc
{
  "name":"main shen",
  "age":18,
  "interests":"qiao dai ma",
  "language":["php", "java", "go", "c"]
}

再次执行上述查询

代码语言:javascript
复制
{
  "error": {
    "root_cause": [
      {
        "type": "sql_illegal_argument_exception",
        "reason": "Arrays (returned by [language]) are not supported"
      }
    ],
    "type": "sql_illegal_argument_exception",
    "reason": "Arrays (returned by [language]) are not supported"
  },
  "status": 500
}

假如你是个刚接触ES的新手,如果做一些简单的业务,可以使用这种sql语法直接查,简单粗暴。

当然,ElasticSearch SQL的局限性不仅仅如此,比如你要查一些相关度 匹配程度的问题,有些dsl语句是没办法完全用sql展示出来的。直接进入我们今天的正题,手把手教你像写sql一样手撸query dsl.

dsl语句都是一个json串,然后通过一些关键词,不断构造对象、嵌套对象,最后拼成符合条件的查询json。我当时刚开始用的时候,就很疑惑,各个关键词有没有层级关系,我到底该怎么拼接我的dsl语句,这次查询该用什么关键词,感觉两个关键词都可以查出我要的结果,我该用哪个,所以这就把很多想直接用dsl语句来查询的老哥们给困惑住了,感觉这东西有点难用。

dsl语句的基本结构

代码语言:javascript
复制
{
  "query": {}, //具体的查询语句对象
  "from": 0,   //从第几条数据开始返回
  "size": 100, //返回的条数 默认ES最多返回10000条
  "highlight": { //高亮
    "pre_tags": {}, //高亮内容的前面标签 一般都是html比如<b> <p>这种
    "post_tags": {},//高亮内容的后面标签 一般都是html比如</b> </p>这种
    "fields": { //需要高亮的字段
    }
  },
  "sort": [{ //排序
    "FIELD": { //排序的字段(需要填上具体的字段名)
      "order": "desc"
    }
  }],
  "_source": "{field}" //指定返回的字段
}

1. 精确查询

select * from table where fileds=xx

select * from table where fileds in (x1,x2 ...)

代码语言:javascript
复制
POST /lib_sql/_search
{
  "query":{
    "term": {
      "name": {
        "value": "cxk"
      }
    }
  }
}

POST /lib_sql/_search
{
  "query":{
    "terms": {
      "name": [
        "main",
        "cxk"
      ]
    }
  }
}

term 跟terms 就基本上代表了我上面两条sql的意思。需要注意的事,默认情况下,一本文本类型的字段,mapping自动给analysis分词了 用term可能是直接查不出的。对于数值型的字段,是可以直接用term的。如果要对文本字段用term精准匹配,最好把字段设置成not analysis。还有ES里还有一种keyword字段类型,默认是1000长度,它也是支持term精确查询的,所以有些场景下我们可以手动设置mapping,把text字段设成keyword类型。

2.多条件

select * from table where a=xx and b=xxx

select * from table where a=xx or b=xxx

select * from table where a!=xx and (b=xxx or c=xxxx)

代码语言:javascript
复制
{
  "query": {
    "bool": {
      "should": [{}], //满足其中一个对象查询条件就行 想sql里的or
      "must": [{}],   //必须满足所有对象的查询条件 就像sql里的and
      "must_not": [{}] //必须不满足所有对象的查询条件 就像sql里的and !=
    }
  }
}

多条件的查询必须要用bool包在外层,然后再根据具体的业务来拼接。

举个例子比如我要查询name为cxk或者anit的

代码语言:javascript
复制
{
  "query": {
    "bool": {
      "should": [{
          "term": {
            "name": {
              "value": "cxk"
            }
          }
        },
        {
          "term": {
            "name": {
              "value": "anit"
            }
          }
        }
      ]
    }
  }
}

再比如我要查询name包含main并且年龄是18的

代码语言:javascript
复制
{
  "query": {
    "bool": {
      "must": [{
          "match": {
            "name": "main"
          }
        },
        {
          "term": {
            "age": {
              "value": 18
            }
          }
        }
      ]
    }
  }
}

3.distinct

select distinct(id) from table

代码语言:javascript
复制
{
  "query":{},
  "collapse": {
      "field": "age" //你需要distinct的字段
   }, 
}

4.order by

实现orderby很简单 就是前面有个提到过有个sort字段 直接就能实现了。

需要注意的是 ,日期格式、数值格式的字段才支持排序,文本类自动分词了的是不支持的直接排序的,如果你要排也可以,解决办法就是多增加一个相同的字段,把这个字段设置为not analysis

5.group by

select count(id) from table groyp by b

ES没有专门的group关键词,但是它也是支持聚合查询的,这里就要用到关键词aggs

代码语言:javascript
复制
{
  "query":{},//这里省略你的查询条件
  "aggs": {
    "age_group": {//这个是指你要返回字段名
      "terms": { //这里还可以用其它关键词 这里的terms才能实现group by效果
        "field": "age",//groupby的字段
        "size":1 //返回的条数 相当于group by limit
      }
    }
  }
}

问题来了,sql是支持group by多字段的 ES里的话 就得在aggs里再嵌套一个aggs这样也能达到聚合多字段的目的

当然,aggs关键词还能支持avg sum min max cardinality(求基数)之类的操作

如果想要执行像类似sql那种having count的 aggs里面也是支持的 需要子aggs里使用bucket_selector,但是这个东西我也基本没用过,所以就不举例了。

6.分页

从刚开始讲的form 跟size字段,我们就能实现简单的分页了。但是就像mysql的limit offset一样,数据量少的时候没啥问题 但是数据量很大的时候,傻逼了,分页速度超级慢。没关系,ES还支持一种类似游标的叫scroll,这样就可以一直加载更多了

代码语言:javascript
复制
POST /lib_sql/_search?scroll=10m
{
  "query": {
    "match": {
      "name": "user" //name是文档中的一个字段
    }
  },
  "size": 1 //scroll返回的数据条数
}

GET /_search/scroll
{
  "scroll":"1m",
  "scroll_id":"DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAC51IWZmw4RzhoOEVUR2VWU2tsR1o4aWdaQQ=="
}

这里的scroll=10m指的是当然这次查询的快照保存10分钟 我们生成个游标后,后续只要用这个scroll_id不断查就行了 每次查询都会刷新快照的时间。这样虽然比from size这种方法快了很多,但是也有缺点,快照的数据实时性没那么高,所以具体用哪种还是得看具体业务场景。

再回过头讲讲match、filter、constant_score

上面讲了那么多,我感觉大家也对ES的查询语法有了基本了解,如果完全没了解过ES的,可能还有点懵逼。

大多数情况下,我们使用ES还是为了使用它的查询功能,大多数情况下,肯定不会是精准匹配,基本上都是用户输入一些内容,然后根据匹配程度 以及其它的权重来列出搜索结果。

代码语言:javascript
复制
{
  "query":{
    "match": { 
      "name": "user A"
    }
  }
}

{
  "query":{
    "match_phrase": {
      "name": "user A"
    }
  }
}

{
  "query":{
    "multi_match": {
      "query": "user A",
      "type": "cross_fields",
      "fields": ["interests", "name"]
    }
  }
}

如果只是单纯使用match的话 那就会把关键词分割掉 只要文档中有一个或者多个词匹配 都会返回结果

match_phrase比match严格,比如所有关键词全部匹配 并且顺序一样才会返回结果,但是实际场景中这种太严格了,搜出来的结果太少了。所以一般的解决方案就是外层用一个bool查询包一个should,然后should里面既有match跟match_phrase 然后使用boost来提升match_phrase的分数 让他排在前面。

multi_match是指匹配多个字段,所以它有个type,基本上可以满足各种查询需求

cross_fields

词是分配到不同字段中

best_fields

完全匹配词的文档占的评分高,会排在返回结果前面

most_fields

越多字段匹配的文档评分越高会排在返回结果前面

至于filter跟constant_score的应用场景,constant_score这个其实就跟它的字面意思一样,查询结果就不用计算分数了,ES有一大波计算量是统计文档的相关度,然后得出分数,这个分数其实挺耗性能的,所以有些查询如果你使用不到分数的话 外层包一个constant_score,会提升你的查询性能,并减少ES的负担。至于filter,比如我们直接可以在一个bool查询里面指定range,也能正常查出来结果,但是最好把这种数值类的range条件都放在filter里面。因为filter里过滤是不算评分的,同时filter的结果是可以被cache的。所以比你直接在查询里面过滤要高效的多。比如我日常搜索log基本结构就像下面这样,因为log很多情况下不需要匹配程度这种。

代码语言:javascript
复制
{
  "query": {
    "constant_score": {
      "filter": {
        "bool": {
          "must": [{
            "match": {}
          }],
          "filter": {
            "range": {
              "@timestamp": {
                "gte": "2020-08-25T07:48:24.000Z",
                "lt": "2020-08-25T15:48:24.000Z"
              }
            }
          }
        }
      }
    }
  }
}

随便聊聊

  1. 基本上我这一系列算是小完结了,后期如果还有研究ES,或者工作实际有更深入用到ES的话我可能还会再出。
  2. 我只是个为了用各种姿势查log的工具人,然后学会了这些查询,可能讲的不全,或者有部分是错的,欢迎公众号直接发消息指出,当然有疑问也可以提,如果在我力所能及保证基本正确答案的前提下,我会回复。
  3. 后面会写什么,我也不知道,不知道,如果我写点Java的从入门到还未精通的系列大家会看吗?
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-08-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 高性能API社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 SQL Server
腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档