elasticsearch聚合:script agg 和 filter agg 的實際使用

背景介紹

需要對索引中的做聚合,但是聚合的條件會比較復雜,并非從單一字段進行聚合。參考如下數(shù)據(jù)結構

{
        //空格分詞字段
    "feild_a": "1  2 3 4 5",
        //keyword字段
    "feild_b": "2"
}

要做的事:我們有 1 2 3 …… 等id,需要統(tǒng)計索引里面,a和b字段分別含有1 2 3對應的數(shù)量。文字難以表述的需求我們轉化成數(shù)據(jù)結構來看

{
    "data": [{
        "id": "1",
        "count": 1
    }, {
        "id": "2",
        "count": 2
    }, {
        "id": "3",
        "count": 1
    }]
}

如果只有上面那一條數(shù)據(jù)的話,那么應該得出下面這個統(tǒng)計結果。

問題分析

從表面剖析這個需求的話,似乎是個多字段的聚合。相對于單字段聚合來說問題還是比較棘手的。并不能簡單的通過單字段的聚合來解決問題,我們先從最簡單的情況開始處理問題。以統(tǒng)計1 2 3這3個id來舉例子。

1.單字段情況下聚合

假設只需要對一個字段聚合,比如b字段,b字段是keyword類型,需要考慮的情況最為簡單,當要對b字段聚合時語句很好寫,如下即可

{
    "from": 0,
    "size": 0,
    "query": {
        "bool": {
            "must": [{
                "bool": {
                    "should": [{
                        "terms": {
                            "field_a": ["1", "2", "3"],
                            "boost": 1.0
                        }
                    }, {
                        "terms": {
                            "field_b": ["1", "2", "3"],
                            "boost": 1.0
                        }
                    }],
                    "adjust_pure_negative": true,
                    "minimum_should_match": "1",
                    "boost": 1.0
                }
            }],
            "adjust_pure_negative": true,
            "boost": 1.0
        }
    },
    "aggregations": {
        "my_agg": {
            "terms": {
                "field": "field_b"
            }
        }
    }
}

這是完整的query,后面的查詢會省略掉query部分。query部分的用處也很明顯:只把需要做聚合的部分過濾出來做聚合,我們需要統(tǒng)計的數(shù)據(jù)就在這部分中,而不是整個索引庫。這樣有兩個好處:
1.提高效率,減少需要聚合的數(shù)據(jù)的數(shù)量
2.剔除需要考慮的意外情況,降低語句的復雜度
而聚合部分就非常簡單了,僅僅對field_b聚合即可,但是很遺憾,離我們最終目標很遠,這樣只能統(tǒng)計出b字段的數(shù)據(jù)分布情況。

2.多字段情況的聚合

相對于上面的那種,接下來把另外一個字段也考慮進來看看。所以我們寫下了這樣的請求語句:

  "aggregations": {
    "my_agg1": {
      "terms": {
        "field": "tag_brand_id"
      }
    },
    "my_agg2": {
      "terms": {
        "field": "brand_cid_array"
      }
    }
  }

勉強的可以看到確實也是“統(tǒng)計了兩個字段的情況”,但是是分開的,意味著要自己去解析返回結果并做計算來得到最終的返回結果。這確實是很令人惡心的事,那還有沒有其他辦法呢。但是觀察語句的結構發(fā)現(xiàn),似乎并沒有過多可以更改的余地,所以需要尋求其他靈活的解決辦法。

3.script agg的聚合

簡單的單聚合無法表達出多字段聚合的需求,在谷歌過后我尋找到了這樣一種解決方案:使用script,即腳本來描述我的需求。下面這段agg就是為了表達我想要根據(jù)我的需求靈活處理的一個方式:

  "aggregations": {
    "my_agg1": {
      "terms": {
        "script": " if (doc['field_a'].values.contains('1') || doc['field_b'].values.contains('1')){1};if (doc['field_a'].values.contains('2') || doc['field_b'].values.contains('2')){2};
if (doc['field_a'].values.contains('3') || doc['field_b'].values.contains('3')){3};"
      }
    }
  }

這一段腳本的作用很明顯,就是告訴es:當a字段或者b字段包括1的時候,扔到桶1;當a字段或者b字段包括2的時候,扔到桶2;……以此類推。看上去確實似乎完全解決了開頭提出來的問題,驗證后效率還能接受,不是特別慢。但是正當我沾沾自喜以為解決了問題的時候,隨手驗證了另外一個case,就直接冷水潑頭了:
a字段和b字段是可能包含同一個id比如2,但是對于統(tǒng)計結果來說要求算作一條。
用上面這個腳本并無法體現(xiàn)出這個區(qū)別,而且還會有一個問題:
請求123和請求321時會返回不同統(tǒng)計結果
因為ifelse語句的關系,和||的性質(zhì),在滿足條件1后便會扔到桶1,而無法在去后續(xù)條件中判斷。這個腳本有很明顯的bug存在。但是painless畢竟是腳本,可以使用的API和關鍵字都非常有限,寫的復雜了還會很嚴重影響效率,無奈這個方案也只能pass,即使它看上去差點解決了我的問題。

4.filter agg的聚合

在重新看了官方文檔后,我發(fā)現(xiàn)了agg中的一個用法,filter agg。
filter agg的用法其實很簡單,但是全意外的和我的需求很契合。之前忽視掉這個用法的主要原因是看到的示例都是對單字段做聚合。那如何同時聚合多個字段呢?從API入手驗證是否可以使用比較靈活的寫法

        public KeyedFilter(String key, QueryBuilder filter) {
            if (key == null) {
                throw new IllegalArgumentException("[key] must not be null");
            }
            if (filter == null) {
                throw new IllegalArgumentException("[filter] must not be null");
            }
            this.key = key;
            this.filter = filter;
        }

這是es提供的javaapi中filter agg的構造函數(shù),key就是過濾名稱,filter就是過濾條件。而且很友好的是,filter類型為QueryBuilder,也就是說,可以做成比較復雜的過濾方式。

    "aggregations": {
        "batch_count": {
            "filters": {
                "filters": {
                    "1": {
                        "bool": {
                            "should": [{
                                "term": {
                                    "field_a": {
                                        "value": "1",
                                        "boost": 1.0
                                    }
                                }
                            }, {
                                "term": {
                                    "field_b": {
                                        "value": "1",
                                        "boost": 1.0
                                    }
                                }
                            }],
                            "adjust_pure_negative": true,
                            "boost": 1.0
                        }
                    },
                    "2": {
                        "bool": {
                            "should": [{
                                "term": {
                                    "field_a": {
                                        "value": "2",
                                        "boost": 1.0
                                    }
                                }
                            }, {
                                "term": {
                                    "field_b": {
                                        "value": "2",
                                        "boost": 1.0
                                    }
                                }
                            }],
                            "adjust_pure_negative": true,
                            "boost": 1.0
                        }
                    },
                    "3": {
                        "bool": {
                            "should": [{
                                "term": {
                                    "field_a": {
                                        "value": "3",
                                        "boost": 1.0
                                    }
                                }
                            }, {
                                "term": {
                                    "field_b": {
                                        "value": "3",
                                        "boost": 1.0
                                    }
                                }
                            }],
                            "adjust_pure_negative": true,
                            "boost": 1.0
                        }
                    }
                },
                "other_bucket": false,
                "other_bucket_key": "-1"
            }
        }
    }

這就是最后成型的agg塊

問題總結

agg模塊的開發(fā)是比較麻煩的,首先性能問題比較困擾,其次語句編寫遠沒有query模塊的靈活。這次順利解決需求,記錄。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容