Elasticsearch聚合查詢概念及字段類型示例
Elasticsearch聚合
Elasticsearch中的聚合是一種以結(jié)構(gòu)化的方式提取和展示數(shù)據(jù)的機(jī)制。可以把它視為SQL中的GROUP BY語句,但是它更加強(qiáng)大和靈活。
Elasticsearch支持很多類型的聚合,包括:
- Metrics Aggregations:這類聚合基于文檔字段的數(shù)值進(jìn)行計(jì)算并返回一個(gè)單一的數(shù)值結(jié)果。例如最大值(max)、最小值(min)、平均值(average)、總和(sum)、統(tǒng)計(jì)信息(stats,包含了上述幾種操作),以及其他復(fù)雜的聚合如百分?jǐn)?shù)(percentiles)、基數(shù)(cardinality)等。
- Bucket Aggregations:這類聚合會創(chuàng)建一組buckets,每個(gè)bucket對應(yīng)一個(gè)特定的條件或范圍,然后文檔會根據(jù)這些條件或范圍被分類到相應(yīng)的bucket中。常見的包括區(qū)間(range)、日期區(qū)間(date range)、直方圖(histogram)、日期直方圖(date histogram)、地理哈希網(wǎng)格(geohash grid)等。
- Pipeline Aggregations:這類聚合可以基于其他聚合的結(jié)果進(jìn)行二次計(jì)算。比如計(jì)算差異、比例、移動平均等。
Elasticsearch的聚合操作支持嵌套,即一個(gè)聚合內(nèi)部可以包含別的子聚合,從而實(shí)現(xiàn)非常復(fù)雜的數(shù)據(jù)挖掘和統(tǒng)計(jì)需求。
聚合(aggs)不同于普通查詢,是目前學(xué)到的第二種大的查詢分類,第一種即“query”,因此在代碼中的第一層嵌
套由“query”變?yōu)榱?ldquo;aggs”。用于進(jìn)行聚合的字段必須是exact value,分詞字段不可進(jìn)行聚合,對于text字段如
果需要使用聚合,需要開啟fielddata,但是通常不建議,因?yàn)閒ielddata是將聚合使用的數(shù)據(jù)結(jié)構(gòu)由磁盤
(doc_values)變?yōu)榱硕褍?nèi)存(field_data),大數(shù)據(jù)的聚合操作很容易導(dǎo)致OOM。
doc values 和 fielddata
在 Elasticsearch 中,聚合操作主要依賴于 doc values 或 fielddata 來進(jìn)行。
- Doc values:對于大多數(shù)字段類型,Elasticsearch 使用 doc values 進(jìn)行排序和聚合。doc values 是一種在磁盤上的、列式存儲的數(shù)據(jù)結(jié)構(gòu),適用于稀疏字段,也就是字段中有很多不同的值。它們默認(rèn)開啟,并且不能被禁用。
- Fielddata:對于TEXT字段,doc values 默認(rèn)是關(guān)閉的,因?yàn)槲谋咀侄瓮ǔ0芏嗖煌闹?,使?doc values 會消耗大量內(nèi)存。這時(shí)候,如果需要對文本字段進(jìn)行聚合或排序,Elasticsearch 使用 fielddata。fielddata 是一個(gè)將所有文檔的字段值加載到內(nèi)存的數(shù)據(jù)結(jié)構(gòu),使用它可以使得聚合、排序和腳本運(yùn)行更快,但代價(jià)是消耗更多的內(nèi)存。
當(dāng)執(zhí)行聚合操作時(shí),Elasticsearch 需要訪問所有匹配文檔的字段值。對于非文本字段,默認(rèn)情況下Elasticsearch 使用 doc values 來實(shí)現(xiàn)。對于文本字段,必須首先啟用 fielddata。然而,由于 fielddata 占用大量內(nèi)存,Elasticsearch 默認(rèn)禁用了它。
對于文本字段,fielddata 默認(rèn)是禁用的。如果你確實(shí)需要對一個(gè)文本字段啟用 fielddata(雖然大多數(shù)場景下不推薦這么做,因?yàn)榭赡軐?dǎo)致內(nèi)存消耗過大),你可以通過更新映射(mapping)來實(shí)現(xiàn)。以下是如何在 my_field 字段上啟用 fielddata 的示例:
PUT my-index/_mapping
{
"properties": {
"my_field": {
"type": "text",
"fielddata": true
}
}
}注意,更改 fielddata 設(shè)置只會影響新的數(shù)據(jù),已經(jīng)索引的數(shù)據(jù)不會受到更改。如果你想讓更改生效,需要重新索引(reindex)你的數(shù)據(jù)。
另外,一般情況下,建議你使用 mapping 中的 keyword 類型來進(jìn)行聚合、排序或腳本,而不是啟用 text 類型的 fielddata。這是因?yàn)?nbsp;keyword 類型字段默認(rèn)開啟了 doc values,比在 text 上啟用 fielddata 更加高效且節(jié)省內(nèi)存。
multi-fields(多字段)類型
在 Elasticsearch 中,一個(gè)字段有可能是 multi-fields(多字段)類型,這意味著同一份數(shù)據(jù)可以被索引為不同類型的字段。常見的情況就是,一個(gè)字段既被索引為 text 類型用于全文搜索,又被索引為 keyword 類型用于精確值搜索、排序和聚合。
當(dāng)你在一個(gè)字段名后面加上 .keyword(例如 field.keyword),這說明你是在引用這個(gè)字段的 keyword 子字段。這個(gè) keyword 子字段在索引時(shí)并不會被分詞器拆分成單獨(dú)的詞條,而是作為一個(gè)完整的字符串被存儲。這樣,你就可以對這個(gè)字段進(jìn)行精確值匹配、排序或者聚合操作。
舉例來說,如果你有一個(gè) message 字段并且想要對其進(jìn)行聚合,你應(yīng)該使用 message.keyword 而非 message。因?yàn)槿绻阒苯訉?nbsp;message 進(jìn)行聚合,Elasticsearch 就會嘗試對每一個(gè)獨(dú)立的詞條進(jìn)行聚合,而不是對整個(gè)字段值進(jìn)行聚合。
如果你的字段沒有 .keyword 子字段,那可能是在定義 mapping 時(shí)沒有包含這一部分,或者這個(gè)字段的類型本身就是 keyword。
聚合分類
- 分桶聚合(Bucket agregations):類比SQL中的group by的作用,主要用于統(tǒng)計(jì)不同類型數(shù)據(jù)的數(shù)量。
- 指標(biāo)聚合(Metrics agregations):主要用于最大值、最小值、平均值、字段之和等指標(biāo)的統(tǒng)計(jì)。
- 管道聚合(Pipeline agregations):用于對聚合的結(jié)果進(jìn)行二次聚合,如要統(tǒng)計(jì)綁定數(shù)量最多的標(biāo)簽bucket,就是要先按照標(biāo)簽進(jìn)行分桶,再在分桶的結(jié)果上計(jì)算最大值。
分桶聚合
分桶(Bucket)聚合是一種特殊類型的聚合,它將輸入文檔集合中的文檔分配到一個(gè)或多個(gè)桶中,每個(gè)桶都對應(yīng)于一個(gè)鍵(key)。
下面是一些常用的分桶聚合類型:
terms:基于文檔中某個(gè)字段的值,將文檔分組到各個(gè)桶中。date_histogram:基于日期字段,將文檔按照指定的時(shí)間間隔分組到各個(gè)桶中。histogram:基于數(shù)值字段,將文檔按照指定的數(shù)值范圍分組到各個(gè)桶中。range:根據(jù)設(shè)置的范圍,將數(shù)據(jù)分為不同的桶。
以下是一個(gè)使用 terms 分桶聚合的例子:
假設(shè)你有一個(gè)包含博客文章的 blog 索引,你想知道每個(gè)作者寫了多少篇文章,可以使用以下查詢:
GET /blog/_search
{
"size": 0,
"aggs": {
"authors": {
"terms": { "field": "author.keyword" }
}
}
}在這個(gè)查詢中:
size: 0表示我們只對聚合結(jié)果感興趣,不需要返回任何具體的搜索結(jié)果。"aggs"(或者"aggregations") 塊定義了我們的聚合。"authors"是我們自己為這個(gè)聚合命名的標(biāo)簽,你可以用任何你喜歡的標(biāo)簽名。"terms": { "field": "author.keyword" }定義了我們要進(jìn)行聚合的方式和字段。這里,我們告訴 Elasticsearch 使用terms聚合,并且使用author.keyword字段的值作為分桶的依據(jù)。
Elasticsearch 將返回一個(gè)包含每個(gè)作者以及他們所寫的文章數(shù)量的列表。注意,由于 Elasticsearch 默認(rèn)只返回前十個(gè)桶,如果你的數(shù)據(jù)中有更多的作者,可能需要設(shè)置 size 參數(shù)來獲取更多的結(jié)果。
Histogram 聚合
histogram 是一個(gè)類型的桶聚合,它可以按照指定的間隔將數(shù)字字段的值劃分為一系列桶。每個(gè)桶代表了這個(gè)區(qū)間內(nèi)的所有文檔。
以下是一個(gè)例子,我們根據(jù)價(jià)格字段創(chuàng)建一個(gè)間隔為 50 的直方圖:
GET /products/_search
{
"size": 0,
"aggs" : {
"prices" : {
"histogram" : {
"field" : "price",
"interval" : 50
}
}
}
}在這個(gè)例子中,“prices” 是一個(gè) histogram 聚合,它以 50 為間隔將產(chǎn)品的價(jià)格劃分為一系列的桶。
指標(biāo)聚合
在 Elasticsearch 中,指標(biāo)聚合是對數(shù)據(jù)進(jìn)行統(tǒng)計(jì)計(jì)算的一種方式,例如求和、平均值、最小值、最大值等。以下是一些常用的指標(biāo)聚合類型:
avg:計(jì)算字段的平均值。sum:計(jì)算字段的總和。min:查找字段的最小值。max:查找字段的最大值。count:計(jì)算匹配文檔的數(shù)量。stats:提供了 count、sum、min、max 和 avg 的基本統(tǒng)計(jì)。
下面是一個(gè)示例,假設(shè)我們有一個(gè)包含售賣商品的 “sales” 索引,我們想要知道所有銷售記錄中的平均價(jià)格,可以使用 avg 聚合如下操作:
GET /sales/_search
{
"size": 0,
"aggs": {
"average_price": {
"avg": { "field": "price" }
}
}
}在這個(gè)查詢中:
"size": 0表示我們只對聚合結(jié)果感興趣,不需要返回任何具體的搜索結(jié)果。"aggs"(或者"aggregations") 塊定義了我們的聚合。"average_price"是我們自己為這個(gè)聚合命名的標(biāo)簽,可以用任何你喜歡的標(biāo)簽名。"avg": { "field": "price" }定義了我們執(zhí)行的聚合類型以及對哪個(gè)字段進(jìn)行聚合。在這里,我們告訴 Elasticsearch 使用avg聚合,并且對price字段的值進(jìn)行計(jì)算。Elasticsearch 將返回一個(gè)包含所有銷售記錄平均價(jià)格的結(jié)果。
Percentiles 聚合
percentiles 是指標(biāo)聚合的一種,它用于計(jì)算數(shù)值字段的百分位數(shù)。給定一個(gè)列表百分比,Elasticsearch 可以計(jì)算每個(gè)百分比下的數(shù)值。
以下是一個(gè)例子,我們計(jì)算價(jià)格字段的 1st, 5th, 25th, 50th, 75th, 95th, and 99th 百分位數(shù):
GET /products/_search
{
"size": 0,
"aggs" : {
"price_percentiles" : {
"percentiles" : {
"field" : "price",
"percents" : [1, 5, 25, 50, 75, 95, 99]
}
}
}
}在這個(gè)例子中,“price_percentiles” 是一個(gè) percentiles 聚合,它計(jì)算了價(jià)格在各個(gè)百分位點(diǎn)的數(shù)值。
注意,對于大數(shù)據(jù)集,計(jì)算精確的百分位數(shù)可能需要消耗大量資源。因此,Elasticsearch 默認(rèn)使用一個(gè)名為 TDigest 的算法來提供近似的計(jì)算結(jié)果,同時(shí)還能保持內(nèi)存使用的可控性。
cardinality聚合
如果你想在 Elasticsearch 中進(jìn)行去重操作,可以使用 terms 聚合加上 cardinality 聚合。這是一個(gè)示例,假設(shè)我們有一個(gè)包含user_id的 "users" 索引,并且我們想要知道有多少唯一的 user_id:
GET /users/_search
{
"size": 0,
"aggs": {
"distinct_user_ids": {
"cardinality": {
"field": "user_id.keyword"
}
}
}
}在這個(gè)查詢中:
"distinct_user_ids"是我們自己為這個(gè)聚合命名的標(biāo)簽。"cardinality": { "field": "user_id.keyword" }使用了cardinality聚合,該聚合會返回指定字段(在這里是user_id.keyword)的不同值的數(shù)量。
Elasticsearch 將返回一個(gè)結(jié)果,告訴我們有多少個(gè)不同的 user_id。請注意,cardinality 聚合可能并不總是完全精確,特別是對于大型數(shù)據(jù)集,因?yàn)樗趦?nèi)部使用了一種叫做 HyperLogLog 的算法來近似計(jì)算基數(shù),這種算法會在保持內(nèi)存消耗相對較小的情況下提供接近準(zhǔn)確的結(jié)果。如果你需要完全精確的結(jié)果,可能需要考慮其他方法,例如使用腳本或者將數(shù)據(jù)導(dǎo)出到外部系統(tǒng)進(jìn)行處理。
管道聚合
在 Elasticsearch 中,管道聚合(pipeline aggregations)是指這樣一種聚合:它以其他聚合的結(jié)果作為輸入,并進(jìn)行進(jìn)一步處理。常見的管道聚合包括:
avg_bucketsum_bucketmin_bucketmax_bucketstats_bucketextended_stats_bucketpercentiles_bucket
這些都是 bucket 級別的管道聚合,它們會在一組數(shù)據(jù)桶上操作。
下面給出一個(gè)示例,假設(shè)我們有一個(gè)銷售記錄索引 "sales",每個(gè)銷售記錄都有售價(jià) "price" 和銷售日期 "date" 字段。如果我們想要計(jì)算每月平均銷售價(jià)格,并找出所有月份中平均價(jià)格最高的月份,可以使用 date_histogram 聚合加上 avg 以及 max_bucket 聚合來實(shí)現(xiàn):
GET /sales/_search
{
"size": 0,
"aggs": {
"sales_per_month": {
"date_histogram": {
"field": "date",
"calendar_interval": "month"
},
"aggs": {
"avg_price": {
"avg": { "field": "price" }
}
}
},
"max_avg_price": {
"max_bucket": {
"buckets_path": "sales_per_month>avg_price"
}
}
}
}在這個(gè)查詢中:
"sales_per_month"是一個(gè)按月聚合銷售記錄的 date_histogram 聚合。"avg_price"是一個(gè)嵌套在"sales_per_month"下的 avg 聚合,用于計(jì)算每月的平均銷售價(jià)格。"max_avg_price"是一個(gè) max_bucket 聚合,它會找出"sales_per_month"中所有子桶的"avg_price"最大值。
注意到 "max_avg_price" 中的 "buckets_path": "sales_per_month>avg_price"。buckets_path 參數(shù)指定了此管道聚合的輸入來源,> 符號表示路徑層次,即先取 "sales_per_month" 聚合的結(jié)果,再取其中的 "avg_price" 聚合的結(jié)果作為輸入。
返回的結(jié)果中會包含每個(gè)月的平均銷售價(jià)格,以及所有月份中平均銷售價(jià)格的最大值。
嵌套聚合
嵌套聚合就是在聚合內(nèi)使用聚合,在 Elasticsearch 中,嵌套聚合通常用于處理 nested 類型的字段。nested 類型允許你將一個(gè)文檔中的一組對象作為獨(dú)立的文檔進(jìn)行索引和查詢,這對于擁有復(fù)雜數(shù)據(jù)結(jié)構(gòu)(例如數(shù)組或列表中的對象)的場景非常有用。
假設(shè)我們有一個(gè) users 索引,每個(gè) user 文檔都有一個(gè) purchases 字段,該字段是一個(gè)列出用戶所有購買記錄的數(shù)組,每個(gè)購買記錄包含 product_id 和 price。如果我們想要找出價(jià)格超過 100 的所有產(chǎn)品的 ID,可以使用 nested 聚合:
GET /users/_search
{
"size": 0,
"aggs": {
"all_purchases": {
"nested": {
"path": "purchases"
},
"aggs": {
"expensive_purchases": {
"filter": { "range": { "purchases.price": { "gt": 100 } } },
"aggs": {
"product_ids": { "terms": { "field": "purchases.product_id" } }
}
}
}
}
}
}在這個(gè)查詢中:
"all_purchases"是一個(gè) nested 聚合,指定了 nested 對象的路徑purchases。"expensive_purchases"是一個(gè)嵌套在"all_purchases"下的 filter 聚合,它會過濾出price大于 100 的購買記錄。"product_ids"是一個(gè)嵌套在"expensive_purchases"下的 terms 聚合,它會提取出所有滿足條件的product_id。
返回的結(jié)果將包含所有 price 大于 100 的產(chǎn)品的 ID 列表。
請注意,在處理 nested 數(shù)據(jù)時(shí),你需要確保 mapping 中相應(yīng)的字段已經(jīng)被設(shè)置為 nested 類型,否則該查詢可能無法按預(yù)期工作。
基于查詢結(jié)果和聚合 & 基于聚合結(jié)果的查詢
基于查詢結(jié)果的聚合:在這種情況下,我們首先執(zhí)行一個(gè)查詢,然后對查詢結(jié)果進(jìn)行聚合。例如,如果我們要查詢所有包含某關(guān)鍵字的文檔,并計(jì)算它們的平均價(jià)格,可以這樣做:
GET /products/_search
{
"query": {
"match": {
"description": "laptop"
}
},
"aggs": {
"average_price": {
"avg": {
"field": "price"
}
}
}
}在上述例子中,我們首先通過 match 查詢找到描述中包含 "laptop" 的所有產(chǎn)品,然后對這些產(chǎn)品的價(jià)格進(jìn)行平均值聚合。
基于聚合結(jié)果的查詢(Post-Filter):這種情況下,我們先執(zhí)行聚合,然后基于聚合的結(jié)果執(zhí)行過濾操作。這通常用于在聚合結(jié)果中應(yīng)用一些額外的過濾條件。例如,如果我們想對所有產(chǎn)品進(jìn)行銷售數(shù)量聚合,然后從結(jié)果中過濾出銷售數(shù)量大于10的產(chǎn)品,可以這樣做:
GET /sales/_search
{
"size": 0,
"aggs": {
"sales_per_product": {
"terms": {
"field": "product_id"
}
}
},
"post_filter": {
"bucket_selector": {
"buckets_path": {
"salesCount": "sales_per_product._count"
},
"script": {
"source": "params.salesCount > 10"
}
}
}
}在上述例子中,我們首先執(zhí)行了一個(gè) terms 聚合,按產(chǎn)品ID匯總銷售記錄。然后我們使用 bucket_selector post-filter 進(jìn)一步篩選出銷售數(shù)量大于10的桶(每個(gè)桶對應(yīng)一個(gè)產(chǎn)品)。
聚合排序
_count
在 Elasticsearch 中,聚合排序允許你基于某一聚合的結(jié)果來對桶進(jìn)行排序。例如,你可能希望查看銷售量最高的10個(gè)產(chǎn)品,可以使用 terms 聚合以及其 size 和 order 參數(shù)來實(shí)現(xiàn):
GET /sales/_search
{
"size": 0,
"aggs": {
"top_products": {
"terms": {
"field": "product_id",
"size": 10,
"order": { "_count": "desc" }
}
}
}
}在這個(gè)例子中,top_products 是一個(gè) terms 聚合,用于按 product_id 對銷售記錄進(jìn)行分組。
"size": 10的意思是只返回銷售量最高的前10個(gè)產(chǎn)品(即只返回前10個(gè)桶)。"order": { "_count": "desc" }表示按桶中文檔的數(shù)量(也就是銷售量)降序排序。_count是一個(gè)內(nèi)置的排序鍵,代表桶中文檔的數(shù)量。
返回的結(jié)果將包含銷售量最高的前10個(gè)產(chǎn)品的 ID 列表。
需要注意的是,由于 Elasticsearch 默認(rèn)會對桶進(jìn)行優(yōu)化,所以在使用 size 參數(shù)時(shí)可能無法得到完全準(zhǔn)確的結(jié)果。如果需要更精確的結(jié)果,可以在請求中設(shè)置 "size": 0 ,然后使用 composite 聚合來分頁獲取所有結(jié)果。
_term
_term 在 Elasticsearch 的聚合排序中用來指定按照詞條(即桶的鍵)來排序。
GET /sales/_search
{
"size": 0,
"aggs": {
"products": {
"terms": {
"field": "product_id",
"order": { "_term": "asc" }
}
}
}
}在這個(gè)例子中,通過 "order": { "_term": "asc" } 指定了按照 product_id 的值升序排序這些桶。
返回的結(jié)果將包含按照 product_id 升序排列的產(chǎn)品 ID 列表,每個(gè)產(chǎn)品 ID 對應(yīng)一個(gè)桶,并且每個(gè)桶內(nèi)包含對應(yīng)產(chǎn)品的銷售記錄。
需要注意的是,在新版本的 Elasticsearch 中(7.0 以后),_term 已經(jīng)被 key 替代用于排序。
GET /sales/_search
{
"size": 0,
"aggs" : {
"products" : {
"terms" : {
"field" : "product_id",
"order" : { "_key" : "asc" }
}
}
}
}以上就是Elasticsearch聚合查詢概念及字段類型示例的詳細(xì)內(nèi)容,更多關(guān)于Elasticsearch聚合查詢的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java 中如何使用 JavaFx 庫標(biāo)注文本顏色
這篇文章主要介紹了在 Java 中用 JavaFx 庫標(biāo)注文本顏色,在本文中,我們將了解如何更改標(biāo)簽的文本顏色,并且我們還將看到一個(gè)必要的示例和適當(dāng)?shù)慕忉?,以便更容易理解該主題,需要的朋友可以參考下2023-05-05
詳解JAVA中的Collection接口和其主要實(shí)現(xiàn)的類
這篇文章主要介紹了JAVA中的Collection接口和其主要實(shí)現(xiàn)的類,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
使用@RequestBody 接收復(fù)雜實(shí)體類集合
這篇文章主要介紹了使用@RequestBody 接收復(fù)雜實(shí)體類集合方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
springboot 啟動時(shí)初始化數(shù)據(jù)庫的步驟
這篇文章主要介紹了springboot 啟動時(shí)初始化數(shù)據(jù)庫的步驟,幫助大家更好的理解和使用springboot框架,感興趣的朋友可以了解下2021-01-01
Spring?boot?整合RabbitMQ實(shí)現(xiàn)通過RabbitMQ進(jìn)行項(xiàng)目的連接
RabbitMQ是一個(gè)開源的AMQP實(shí)現(xiàn),服務(wù)器端用Erlang語言編寫,支持多種客戶端,這篇文章主要介紹了Spring?boot?整合RabbitMQ實(shí)現(xiàn)通過RabbitMQ進(jìn)行項(xiàng)目的連接,需要的朋友可以參考下2022-10-10
Jackson2的JsonSchema實(shí)現(xiàn)java實(shí)體類生成json方式
這篇文章主要介紹了Jackson2的JsonSchema實(shí)現(xiàn)java實(shí)體類生成json,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11

