大家好 泥腿子安尼特又和大家见面了。不知道大家昨晚过的如何,容我再孤寡孤寡孤寡几声
我这个人比较懒,但是有些东西没完结,总是有时候脑子里挂念着,所以心心念念的想把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查询语句,那还是非常好用的!
数据源
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语法直接查,简单粗暴。
当然,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" } } } } } } }}
随便聊聊
基本上我这一系列算是小完结了,后期如果还有研究ES,或者工作实际有更深入用到ES的话我可能还会再出。
我只是个为了用各种姿势查log的工具人,然后学会了这些查询,可能讲的不全,或者有部分是错的,欢迎公众号直接发消息指出,当然有疑问也可以提,如果在我力所能及保证基本正确答案的前提下,我会回复。
后面会写什么,我也不知道,不知道,如果我写点Java的从入门到还未精通的系列大家会看吗?