Item_sum類用于SQL聚合函數的特殊表達式基類。
這些表達式是在聚合函數(sum、max)等幫助下形成的。item_sum類也是window函數的基類。
聚合函數(Aggregate Function)實現的大部分代碼在item_sum.h和item_sum.cc
聚合函數限制
不能在表達式的所有位置使用聚合函數,使用聚合函數應該有一些明確的限制。
在沒有嵌套的查詢中,select列表的表達式中,having自居中使用聚合函數是有效的,在where子句、form子句或group by子句則是無效的。
關于解釋,這篇文章可以看看:https://blog.csdn.net/weixin_33725515/article/details/85997761
在帶有嵌套子查詢的查詢中檢測聚合函數是否有效的規則比較復雜:
如下列查詢:
SELECT t1.a FROM t1 GROUP BY t1.a HAVING t1.a > ALL (SELECT t2.c FROM t2 WHERE SUM(t1.b) < t2.c).
在子查詢的where子句中使用了聚合函數sum(),但是由于它包含在外部查詢的having子句中,因此它是有效的。將針對主查詢中定義的每個組而不是子查詢的組來評估表達式sum。
又如下列查詢:
SELECT t1.a FROM t1 GROUP BY t1.a HAVING t1.a > ALL(SELECT t2.c FROM t2 GROUP BY t2.c HAVING SUM(t1.a) < t2.c)
聚合函數可以在外部查詢塊和內部查詢塊中進行評估,如果我們為外部查詢評估sum,則將得到t1.a乘上t1組中的基數。在這種情況下,sum(t1.a)被用作每個相關子查詢中的常數。但是,也可以為內部查詢評估sum。此時t1.a將是每個相關子查詢的常數,并且對表t2的每個組執行求和。
因此,根據向哪個查詢快分配聚合函數,可以獲得不同的結果。
檢測聚合函數的查詢塊的一般規則如下:
考慮一個聚合函數S(E),其中E是一個包含列引用C1,…,Cn的表達式。針對包含聚合函數S(E)的查詢塊Qi會解析所有列引用Ci。令Q為所有查詢塊Qi中最內部的查詢塊。(注意,S(E)絕對不能在包含子查詢Q的查詢的查詢塊聚合,否則S(E)將引用至少一個未綁定的列引用)。如果在允許聚合函數的Q的構造中使用函數S(E),則我們在Q中聚合S(E)。
否則:如果啟用了ANSI SQL模式,則報告錯誤。如果沒有啟用,在允許使用S(E)的地方查找包含S(E)的最內部的查詢塊。聚合的位置取決于子查詢包含在哪個子句中。當包含在where子句中,包含在選擇列表中或包含在having子句時,結果不同。
一些成員說明
成員base_select
包含對其中包含了聚合函數的查詢塊的引用。
成員aggr_select
包含對其中使用了聚合函數的查詢塊的引用。
max_aggr_level
字段保留聚合函數中包含的未綁定列引用的最大嵌套級別。在嵌套級別較小的子查詢中不能包含聚合函數比max_aggr_level
高。可以聚合在嵌套級別大于max_aggr_level
的子查詢中。
如果聚合函數中不包含任何列引用,如count(*),則max_aggr_level
為-1。
字段max_sum_func_level
將包含用作給定聚合函數的參數的子表達式的聚合函數的嵌套級別的最大值,但不會在此聚合函數內的任何子查詢塊中聚合。僅當s1.max_sum_func_level < s0.max_sum_func_level
時,嵌套的聚合函數s1才能在聚合函數s0中使用。如果未在s0內的任何子查詢中計算s1,則將聚合函數s1視為嵌套于集合函數s0。
當我們使用遞歸方法fix_fields
遍歷查詢子表達式時,將檢查使用集合函數的條件。當我們將此方法用于Item_sum類的對象時,首先,在下降時(下降不知道指什么意思),調用init_sum_func_check
方法,該方法初始化檢查時使用的成員。然后在上升過程中,調用方法check_sum_func
,該方法驗證設置的函數的用法,并在無效時報告錯誤。方法check_sum_func
用于鏈接在包含的查詢塊中聚合的聚合函數的item。此類函數的循環鏈通過字段inner_sum_func_list
附加到相應的select_lex
結構。
窗口函數
關于什么是窗口函數,參考鏈接通俗易懂的學會:SQL窗口函數
大部分的聚合函數如(sum、count、avg)也可以用作窗口函數。此時有如下限制:
1、不使用任何聚合器
2、不支持distinct
3、val_* ( ) 的作用不只是返回函數的當前值:它首先將函數的參數累加到函數的狀態。例如處理end_write_wf()
包含WF輸入的臨時表。每個輸入行都傳遞給copy_funcs(),后者調用WF的 val_ *()對其進行累加。
類型判斷
很多時候我們要判斷聚合函數的類型,那么首先要先判斷當前的操作是否是聚合操作,然后再判斷聚合操作的類型。
Item類有一個純虛函數: virtual enum Type type() const =0;
其對應的Type在該類中定義為:
enum Type {INVALID_ITEM = 0,FIELD_ITEM,FUNC_ITEM,SUM_FUNC_ITEM,STRING_ITEM,INT_ITEM,REAL_ITEM,NULL_ITEM,VARBIN_ITEM,COPY_STR_ITEM,FIELD_AVG_ITEM,DEFAULT_VALUE_ITEM,PROC_ITEM,COND_ITEM,REF_ITEM,FIELD_STD_ITEM,FIELD_VARIANCE_ITEM,INSERT_VALUE_ITEM,SUBSELECT_ITEM,ROW_ITEM,CACHE_ITEM,TYPE_HOLDER,PARAM_ITEM,TRIGGER_FIELD_ITEM,DECIMAL_ITEM,XPATH_NODESET,XPATH_NODESET_CMP,VIEW_FIXER_ITEM,FIELD_BIT_ITEM,VALUES_COLUMN_ITEM};
Item_sum從Item派生出來,除了type函數外,他還帶有一個函數virtual enum Sumfunctype sum_func () const=0;
在Item_sum中定義了Sumfunctype的類型。
enum Sumfunctype {COUNT_FUNC, // COUNTCOUNT_DISTINCT_FUNC, // COUNT (DISTINCT)SUM_FUNC, // SUMSUM_DISTINCT_FUNC, // SUM (DISTINCT)AVG_FUNC, // AVGAVG_DISTINCT_FUNC, // AVG (DISTINCT)MIN_FUNC, // MINMAX_FUNC, // MAXSTD_FUNC, // STD/STDDEV/STDDEV_POPVARIANCE_FUNC, // VARIANCE/VAR_POP and VAR_SAMPSUM_BIT_FUNC, // BIT_AND, BIT_OR and BIT_XORUDF_SUM_FUNC, // user defined functionsGROUP_CONCAT_FUNC, // GROUP_CONCATJSON_AGG_FUNC, // JSON_ARRAYAGG and JSON_OBJECTAGGROW_NUMBER_FUNC, // Window functionsRANK_FUNC,DENSE_RANK_FUNC,CUME_DIST_FUNC,PERCENT_RANK_FUNC,NTILE_FUNC,LEAD_LAG_FUNC,FIRST_LAST_VALUE_FUNC,NTH_VALUE_FUNC,ROLLUP_SUM_SWITCHER_FUNC};
可以通過這些類型來判斷讀當前具體的聚合操作。除此之外,還可以利用lex的一些helper函數來進行輔助判斷,如is_single_grouped
bool is_single_grouped() const {return m_agg_func_used && group_list.elements == 0 &&m_having_cond == nullptr;
}
如果該函數返回true,則說明沒有group by 也沒有have
主要的聚合函數具體在代碼中的類結構和繼承關系
COUNT/SUM/AVG/STD/VAR_POP函數:
MIN/MAX函數:
BIT_OR/BIT_AND/BIT_XOR函數:
聚合過程(不帶group by)
不帶group by的聚合會使用輔助類Aggregator
,而group by并不使用該輔助類。
在優化階段需要進行setup,比如初始化distinct或者sorting需要臨時表或者臨時Tree結構,方便下階段的聚合。
JOIN::optimize-->
JOIN::make_tmp_tables_info-->
setup_sum_funcs-->
Item_sum::aggregator_setup-->
Aggregator_simple::setup-->
Item_sum::setup-->
在執行階段
AggregateIterator::Read()->
reset_and_add()->
aggregator_clear()、aggregator_add()->
Aggregator::clear()、Aggregator::add()->
Item_sum_xxx::add()
在計算distinct聚合時,還需要實現aggregator::endup(),因為distinct_aggregator::add()只是通過某種方式采集了unique的行,但是并未保存,需要在這個階段進行保存。這個過程可以理解為在distinct的聚合過程中(add)無法判斷是否唯一。
注意group by場景下本身是通過臨時表解決唯一問題的。
聚合過程(帶group by)
MySQL對于帶GROUP BY的聚合,通常采用了Temp table的方式保存了(GROUP BY KEY, AGGR VALUE)
具體交給迭代器TemptableAggregateIterator實現。
TemptableAggregateIterator::Init()->
init_tmptable_sum_functions()、update_tmptable_sum_func()->
Item_sum::reset_field()、Item_sum::update_field()->
Item_sum繼承于Item_result_field,意味著該類作為計算函數的同時也保存輸出結果。具體可以看每個Item_sum子類的val_xxx實現,該函數負責對上層結果或者客戶端結果進行輸出。
對于特殊聚合函數如AVG\STD\VAR_POP等函數,在累加過程中,臨時保存的變量值有多個,實際的輸出結果必須通過加工處理,尤其是在group by場景下,多個臨時變量需要保存到temp table中,下次累加時取出來,直到最終結果輸出。所以需要額外的輔助類Item_result_field,幫助該聚合函數進行最終結果輸出。
舉例,對于Item_avg_field類的最終結果,需要通過Item_avg_field::val_xxx計算后進行輸出。
調用順序如下:
ExecuteIteratorQuery()->
Query_result_send::send_data()->
THD::send_result_set_row()->
Item::send()->
Item_avg_field::val_xxx
小TIPS:如果內核需要實現多線程并行計算聚合函數的時候,可以通過改造對中間結果輸出save_in_field_inner函數,讓每個中間結果如按照設計保存到相應的field->ptr中,保留到臨時表中。
一些函數(待補全)
Item_sum::check_sum_func
驗證聚合函數的語義要求。檢查聚合函數的上下文是否允許對其進行聚合,并且當它是另一個聚合函數的參數時,需要直接或間接確保該函數將這兩個聚合函數聚合在不同的查詢塊中。如果將聚合函數聚集在某個外部查詢塊中,則會將其添加到附加的聚集塊查詢item鏈inner_sum_func_list
中。
tips:解析表達式時,必須為所有的聚合函數調用此函數,而且是以后綴順序調用。
Item_sum::cleanup
使用后調用每個item。作用是釋放所有分配的資源,例如動態內存。通過清除緩存的值為新的執行做準備。它不會刪除準備期間分配的值,那些資源由析構函數釋放。
參考
http://mysql.taobao.org/monthly/2019/05/02/
https://dev.mysql.com/doc/dev/mysql-server/latest/classItem__sum.html
MySQL 8.0.22源碼