大家好 泥腿子安尼特又和大家見面了。不知道大家昨晚過的如何,容我再孤寡孤寡孤寡幾聲
我這個人比較懶,但是有些東西沒完結,總是有時候腦子裡挂念着,是以心心念念的想把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_sql
POST /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比如<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 ...)
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的從入門到還未精通的系列大家會看嗎?