怎么把name一样的多个字段传到后台_像SELECT*一样手撸Query DSL——ElasticSearch下篇...

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

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

be770822bff0e498cfcb2c1030144ed4.png当年我还在读大学的时候,尽管我经常上课玩手机,睡觉,但是我数据库的老师的一句话深深的印在了我的脑海里,原话大概是这样的——这个世界上有一门编程语言,出来到现在几十年了,语法简单,基本没怎么变过,各种通用,从业人员的职业生涯也很持久,工资也高。大家猜猜是啥- -  那就是无所不能的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查询语句,那还是非常好用的!

数据源

PUT lib_sqlPOST /lib_sql/_doc{  "name":"anit",  "age":27,  "interests":"lol, lol, lol"}POST /_sql?format=txt{  "query":"SELECT * from lib_sql"}

结果

      age      |       interests        |     name      ---------------+------------------------+---------------18             |chang, tiao, rap, lanqiu|cxk            27             |lol, lol, lol           |anit

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

POST /lib_sql/_doc{  "name":"main shen",  "age":18,  "interests":"qiao dai ma",  "language":["php", "java", "go", "c"]}

再次执行上述查询

{  "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语法直接查,简单粗暴。

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

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

dsl语句的基本结构

{  "query": {}, //具体的查询语句对象  "from": 0,   //从第几条数据开始返回  "size": 100, //返回的条数 默认ES最多返回10000条  "highlight": { //高亮    "pre_tags": {}, //高亮内容的前面标签 一般都是html比如 

这种

    "post_tags": {},//高亮内容的后面标签 一般都是html比如 这种    "fields": { //需要高亮的字段 } },  "sort": [{ //排序    "FIELD": { //排序的字段(需要填上具体的字段名)      "order": "desc" } }],  "_source": "{field}" //指定返回的字段}

1. 精确查询 

select * from  table where fileds=xx

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

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)

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

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

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

{  "query": {    "bool": {      "should": [{          "term": {            "name": {              "value": "cxk"            }          }        },        {          "term": {            "name": {              "value": "anit"            }          }        }      ]    }  }}

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

{  "query": {    "bool": {      "must": [{          "match": {            "name": "main"          }        },        {          "term": {            "age": {              "value": 18            }          }        }      ]    }  }}

3.distinct

select distinct(id) from table 

{  "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

{  "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,这样就可以一直加载更多了

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

{  "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很多情况下不需要匹配程度这种。

{  "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的从入门到还未精通的系列大家会看吗?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值