[TOC] # GROUP BY GROUP BY 算子主要用于在 SQL 中进行分组聚合计算操作。 用于对数据进行分组的算法有 HASH 算法和 MERGE 算法,因此根据算法可以将 GROUP BY 算子分为两种:HASH GROUP BY 和 MERGE GROUP BY。执行计划生成时根据 SQL 优化器对于两种算子的代价评估,来选择使用哪种 GROUP BY 算子。 对于普通的聚合函数(SUM/MAX/MIN/AVG/COUNT/STDDEV)也是通过分配 GROUP BY 算子来完成,而对于只有聚合函数而不含有 GROUP BY 的 SQL,分配的是 SCALAR GROUP BY 算子,因此 GROUP BY 算子又可以分为三种:SCALAR GROUP BY、HASH GROUP BY 和 MERGE GROUP BY。 ## SCALAR GROUP BY 示例 1:含 SCALAR GROUP BY 算子的执行计划 ~~~ obclient>CREATE TABLE t1(c1 INT, c2 INT); Query OK, 0 rows affected (0.12 sec) obclient>INSERT INTO t1 VALUES(1, 1); Query OK, 1 rows affected (0.12 sec) obclient>INSERT INTO t1 VALUES(2, 2); Query OK, 1 rows affected (0.12 sec) obclient>INSERT INTO t1 VALUES(3, 3); Query OK, 1 rows affected (0.12 sec) Q1: obclient> EXPLAIN SELECT SUM(c1) FROM t1\G; *************************** 1. row *************************** Query Plan: | ======================================== |ID|OPERATOR |NAME|EST. ROWS|COST| ---------------------------------------- |0 |SCALAR GROUP BY| |1 |37 | |1 | TABLE SCAN |T1 |3 |37 | ======================================== Outputs & filters: ------------------------------------- 0 - output([T_FUN_SUM(T1.C1)]), filter(nil), group(nil), agg_func([T_FUN_SUM(T1.C1)]) 1 - output([T1.C1]), filter(nil), access([T1.C1]), partitions(p0) ~~~ 上述示例中,Q1 查询的执行计划展示中的 outputs & filters 中详细列出了 SCALAR GROUP BY 算子的输出信息如下: <table data-tag="table" id="table-7a6-1sz-2cb" class="table"><colgroup span="1" width="139" data-tag="col" id="col-f8i-34l-cqo" colwidth="1*" colnum="1" colname="col1" style="width:50%" class="col"></colgroup><colgroup span="1" width="609" data-tag="col" id="col-jsp-wjx-gsk" colwidth="1*" colnum="2" colname="col2" style="width:50%" class="col"></colgroup><thead id="thead-ono-z4t-u8f" class="thead"><tr id="tr-9n0-hl1-mc8"><th id="td-l7e-wf2-w79"><p id="p-3av-zat-o3z"><b>信息名称</b></p></th><th id="td-k7u-sbz-rac"><p id="p-l9z-ihn-0xo"><b>含义</b></p></th></tr></thead><tbody data-tag="tbody" id="tbody-75y-jmu-nbw" class="tbody"><tr data-tag="tr" id="tr-7kb-cl5-pl1" class="tr"><td data-tag="td" id="td-yme-rko-9pz" class="td"><p id="p-hj9-p7y-owd">output</p></td><td data-tag="td" id="td-k0z-ord-uoj" class="td"><p id="p-mom-kyl-2xi">该算子输出的表达式。</p></td></tr><tr data-tag="tr" id="tr-u8b-v6n-t7h" class="tr"><td data-tag="td" id="td-mmf-hp9-xir" class="td"><p id="p-vub-206-ium">filter</p></td><td data-tag="td" id="td-irp-cc7-5pj" class="td"><p id="p-r20-dbv-ajr">该算子上的过滤条件。</p><p id="p-grx-mfk-ri6">由于示例中 SCALAR GROUP BY 算子未设置 filter,所以为 nil。</p></td></tr><tr data-tag="tr" id="tr-rzq-r83-0gr" class="tr"><td data-tag="td" id="td-xas-94a-0ra" class="td"><p data-tag="p" id="p-hnd-jy4-qa9" class="p">group</p></td><td data-tag="td" id="td-9vn-c07-sbr" class="td"><p data-tag="p" id="p-vde-jxn-54i" class="p">需要进行分组的列。</p><p data-tag="p" id="p-ybv-sdj-x6x" class="p">例如,Q1 查询中是 SCALAR GROUP BY 算子,所以为 nil。</p></td></tr><tr data-tag="tr" id="tr-jb3-zqr-58d" class="tr"><td data-tag="td" id="td-mcz-arg-j8p" class="td"><p data-tag="p" id="p-1qj-cbh-fk3" class="p">agg_func</p></td><td data-tag="td" id="td-qpm-f8f-qom" class="td"><p data-tag="p" id="p-p6n-7kg-be3" class="p">所涉及的聚合函数。</p><p data-tag="p" id="p-j38-hfx-mnu" class="p">例如,Q1 查询是计算表 t1 的 c1 列数据之和,因此为 <code data-tag="code" class="code">T_FUN_SUM(t1.c1)</code>。</p></td></tr></tbody></table> ## HASH GROUP BY 示例 2:含 HASH GROUP BY 算子的执行计划 ~~~ Q2: obclient>EXPLAIN SELECT SUM(c2) FROM t1 GROUP BY c1 HAVING SUM(c2) > 2\G; *************************** 1. row *************************** Query Plan: | ====================================== |ID|OPERATOR |NAME|EST. ROWS|COST| -------------------------------------- |0 |HASH GROUP BY| |1 |40 | |1 | TABLE SCAN |T1 |3 |37 | ====================================== Outputs & filters: ------------------------------------- 0 - output([T_FUN_SUM(T1.C2)]), filter([T_FUN_SUM(T1.C2) > 2]), group([T1.C1]), agg_func([T_FUN_SUM(T1.C2)]) 1 - output([T1.C1], [T1.C2]), filter(nil), access([T1.C1], [T1.C2]), partitions(p0) ~~~ 上述示例中,Q2 查询的执行计划展示中的 outputs & filters 详细列出了 HASH GROUP BY 算子的输出信息如下: <table data-tag="table" id="table-uh2-o26-j7m" class="table"><colgroup span="1" width="134" data-tag="col" id="col-pqg-bp9-mtl" colwidth="1*" colnum="1" colname="col1" style="width:50%" class="col"></colgroup><colgroup span="1" width="608" data-tag="col" id="col-wdn-nif-ndu" colwidth="1*" colnum="2" colname="col2" style="width:50%" class="col"></colgroup><thead id="thead-3md-hjt-gid" class="thead"><tr id="tr-bjy-gah-fa5"><th id="td-p6p-k93-i0n"><p id="p-3cn-kf8-d2o"><b>信息名称</b></p></th><th id="td-5yl-vew-h8u"><p id="p-jb0-su7-629"><b>含义</b></p></th></tr></thead><tbody data-tag="tbody" id="tbody-dn0-rz8-zhm" class="tbody"><tr data-tag="tr" id="tr-z56-lc3-ebl" class="tr"><td data-tag="td" id="td-m2d-txf-8cb" class="td"><p id="p-g1d-3wh-0vj">output</p></td><td data-tag="td" id="td-5jp-2ut-ph5" class="td"><p id="p-rs2-v87-u2n">该算子输出的表达式。</p></td></tr><tr data-tag="tr" id="tr-du5-fo8-ihe" class="tr"><td data-tag="td" id="td-ojc-lr4-ro9" class="td"><p id="p-nid-roh-oan">filter</p></td><td data-tag="td" id="td-6mm-ki1-bpm" class="td"><p id="p-vkr-dg0-tnf">该算子上的过滤条件。</p><p id="p-8mz-fnd-b3c">由于设置要求分组后的 c2 列求和大于 2,因此为 <code data-tag="code" class="code">T_FUN_SUM(t1.c2) &gt; 2</code>。</p></td></tr><tr data-tag="tr" id="tr-5z6-d51-nxr" class="tr"><td data-tag="td" id="td-kxb-8yb-juv" class="td"><p data-tag="p" id="p-qgd-194-mh1" class="p">group</p></td><td data-tag="td" id="td-oak-sdi-muj" class="td"><p data-tag="p" id="p-0rm-nxu-lnq" class="p">需要进行分组的列。</p><p data-tag="p" id="p-w1c-h1p-g2w" class="p">例如,Q2 查询是 HASH GROUP BY 算子,所以为 nil。</p></td></tr><tr data-tag="tr" id="tr-yjl-r5t-ahg" class="tr"><td data-tag="td" id="td-h1k-b55-emu" class="td"><p data-tag="p" id="p-yfh-gfw-jqa" class="p">agg_func</p></td><td data-tag="td" id="td-flw-jpn-rpc" class="td"><p data-tag="p" id="p-lx7-vjg-lzv" class="p">所涉及的聚合函数。</p><p data-tag="p" id="p-wbd-jaw-rjv" class="p">例如,Q2 查询中计算表 t1 的 c1 列之和,因此为 <code data-tag="code" class="code">T_FUN_SUM(t1.c1)</code>。</p></td></tr></tbody></table> **说明** HASH GROUP BY 算子将会保证在执行时采用 HASH 算法进行分组。 ## MERGE GROUP BY 示例 3:含 MERGE GROUP BY 算子的执行计划 ~~~ Q3: obclient>EXPLAIN SELECT /*+NO_USE_HASH_AGGREGATION*/SUM(c2) FROM t1 GROUP BY c1 HAVING SUM(c2) > 2\G; *************************** 1. row *************************** Query Plan: | ======================================= |ID|OPERATOR |NAME|EST. ROWS|COST| --------------------------------------- |0 |MERGE GROUP BY| |1 |45 | |1 | SORT | |3 |44 | |2 | TABLE SCAN |T1 |3 |37 | ======================================= Outputs & filters: ------------------------------------- 0 - output([T_FUN_SUM(T1.C2)]), filter([T_FUN_SUM(T1.C2) > 2]), group([T1.C1]), agg_func([T_FUN_SUM(T1.C2)]) 1 - output([T1.C1], [T1.C2]), filter(nil), sort_keys([T1.C1, ASC]) 2 - output([T1.C1], [T1.C2]), filter(nil), access([T1.C1], [T1.C2]), partitions(p0) ~~~ 上述示例中,Q3 查询的执行计划展示中的 outputs & filters 中详细列出了 MERGE GROUP BY 算子的信息,可以看出相同的 SQL 生成执行计划时选择了 MERGE GROUP BY 算子,其算子基本信息都是相同的,最大的区别是在执行的时候选择的分组算法不一样。同时,这里的 2 号算子 TABLE SCAN 返回的结果是一个无序结果,而 GROUP BY 算法采用的是 MERGE GROUP BY,因此必须分配一个 SORT 算子。 **注意** NO\_USE\_HASH\_AGGREGATION 和 USE\_HASH\_AGGREGATION 的 HINT 可以用于控制 GROUP BY 算子选择何种算法进行分组。