文件首頁
MySQL 8.4 參考手冊
相關文件 下載本手冊
PDF (US Ltr) - 39.9Mb
PDF (A4) - 40.0Mb
Man Pages (TGZ) - 258.5Kb
Man Pages (Zip) - 365.5Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


MySQL 8.4 參考手冊  /  ...  /  GROUP BY 修飾詞

14.19.2 GROUP BY 修飾詞

GROUP BY 子句允許使用 WITH ROLLUP 修飾詞,使摘要輸出包含代表較高層級(即超彙總)摘要運算的額外列。ROLLUP 因此讓您可以使用單一查詢回答多個分析層級的問題。例如,ROLLUP 可用於提供 OLAP(線上分析處理)運算的支援。

假設一個 sales 資料表具有 yearcountryproductprofit 欄,用於記錄銷售利潤。

CREATE TABLE sales
(
    year    INT,
    country VARCHAR(20),
    product VARCHAR(32),
    profit  INT
);

若要摘要每年資料表內容,請使用簡單的 GROUP BY,如下所示:

mysql> SELECT year, SUM(profit) AS profit
       FROM sales
       GROUP BY year;
+------+--------+
| year | profit |
+------+--------+
| 2000 |   4525 |
| 2001 |   3010 |
+------+--------+

輸出會顯示每年的總(彙總)利潤。若要判斷所有年份的總利潤總和,您必須自行將個別值加總,或執行額外查詢。或者,您可以使用 ROLLUP,它可以使用單一查詢提供兩個分析層級。在 GROUP BY 子句中新增 WITH ROLLUP 修飾詞,會使查詢產生另一列(超彙總)列,顯示所有年份值的總計。

mysql> SELECT year, SUM(profit) AS profit
       FROM sales
       GROUP BY year WITH ROLLUP;
+------+--------+
| year | profit |
+------+--------+
| 2000 |   4525 |
| 2001 |   3010 |
| NULL |   7535 |
+------+--------+

year 欄中的 NULL 值會識別總計超彙總列。

當有多個 GROUP BY 欄時,ROLLUP 會產生更複雜的效果。在這種情況下,每次任何分組欄(最後一個分組欄除外)的值發生變更時,查詢都會產生額外的超彙總摘要列。

例如,若沒有 ROLLUP,則基於 yearcountryproductsales 資料表摘要可能會如下所示,其中輸出僅表示在年份/國家/產品分析層級的摘要值。

mysql> SELECT year, country, product, SUM(profit) AS profit
       FROM sales
       GROUP BY year, country, product;
+------+---------+------------+--------+
| year | country | product    | profit |
+------+---------+------------+--------+
| 2000 | Finland | Computer   |   1500 |
| 2000 | Finland | Phone      |    100 |
| 2000 | India   | Calculator |    150 |
| 2000 | India   | Computer   |   1200 |
| 2000 | USA     | Calculator |     75 |
| 2000 | USA     | Computer   |   1500 |
| 2001 | Finland | Phone      |     10 |
| 2001 | USA     | Calculator |     50 |
| 2001 | USA     | Computer   |   2700 |
| 2001 | USA     | TV         |    250 |
+------+---------+------------+--------+

新增 ROLLUP 後,查詢會產生數個額外列。

mysql> SELECT year, country, product, SUM(profit) AS profit
       FROM sales
       GROUP BY year, country, product WITH ROLLUP;
+------+---------+------------+--------+
| year | country | product    | profit |
+------+---------+------------+--------+
| 2000 | Finland | Computer   |   1500 |
| 2000 | Finland | Phone      |    100 |
| 2000 | Finland | NULL       |   1600 |
| 2000 | India   | Calculator |    150 |
| 2000 | India   | Computer   |   1200 |
| 2000 | India   | NULL       |   1350 |
| 2000 | USA     | Calculator |     75 |
| 2000 | USA     | Computer   |   1500 |
| 2000 | USA     | NULL       |   1575 |
| 2000 | NULL    | NULL       |   4525 |
| 2001 | Finland | Phone      |     10 |
| 2001 | Finland | NULL       |     10 |
| 2001 | USA     | Calculator |     50 |
| 2001 | USA     | Computer   |   2700 |
| 2001 | USA     | TV         |    250 |
| 2001 | USA     | NULL       |   3000 |
| 2001 | NULL    | NULL       |   3010 |
| NULL | NULL    | NULL       |   7535 |
+------+---------+------------+--------+

現在,輸出包含四個分析層級的摘要資訊,而不僅僅是一個。

  • 在給定年份和國家/地區的每一組產品列之後,會出現額外的超彙總摘要列,顯示所有產品的總計。這些列的 product 欄設定為 NULL

  • 在給定年份的每一組列之後,會出現額外的超彙總摘要列,顯示所有國家/地區和產品的總計。這些列的 countryproducts 欄設定為 NULL

  • 最後,在所有其他列之後,會出現額外的超彙總摘要列,顯示所有年份、國家/地區和產品的總計。此列的 yearcountryproducts 欄設定為 NULL

當資料列傳送至用戶端時,會產生每個超彙總列中的 NULL 指示器。伺服器會查看 GROUP BY 子句中,在最左邊已變更值的欄之後命名的欄。對於結果集中任何具有符合這些名稱的名稱的欄,其值會設定為 NULL。(如果您依欄位置指定分組欄,伺服器會依位置識別要設定為 NULL 的欄)。

由於超彙總列中的 NULL 值是在查詢處理的後期階段放入結果集中,因此您只能在選取清單或 HAVING 子句中將它們測試為 NULL 值。您無法在聯結條件或 WHERE 子句中將它們測試為 NULL 值,以判斷要選取哪些列。例如,您無法將 WHERE product IS NULL 新增至查詢,以從輸出中排除除超彙總列以外的所有列。

NULL 值會在用戶端顯示為 NULL,並且可以使用任何 MySQL 用戶端程式設計介面進行測試。但是,此時,您無法區分 NULL 代表規則分組值還是超彙總值。若要測試差異,請使用稍後說明的 GROUPING() 函式。

對於 GROUP BY ... WITH ROLLUP 查詢,為了測試結果中的 NULL 值是否代表超彙總值,可以在選取清單、HAVING 子句和 ORDER BY 子句中使用 GROUPING() 函數。例如,當 year 欄位中的 NULL 出現在超彙總列時,GROUPING(year) 會傳回 1,否則傳回 0。類似地,GROUPING(country)GROUPING(product) 則會分別在 countryproduct 欄位中,針對超彙總的 NULL 值傳回 1。

mysql> SELECT
         year, country, product, SUM(profit) AS profit,
         GROUPING(year) AS grp_year,
         GROUPING(country) AS grp_country,
         GROUPING(product) AS grp_product
       FROM sales
       GROUP BY year, country, product WITH ROLLUP;
+------+---------+------------+--------+----------+-------------+-------------+
| year | country | product    | profit | grp_year | grp_country | grp_product |
+------+---------+------------+--------+----------+-------------+-------------+
| 2000 | Finland | Computer   |   1500 |        0 |           0 |           0 |
| 2000 | Finland | Phone      |    100 |        0 |           0 |           0 |
| 2000 | Finland | NULL       |   1600 |        0 |           0 |           1 |
| 2000 | India   | Calculator |    150 |        0 |           0 |           0 |
| 2000 | India   | Computer   |   1200 |        0 |           0 |           0 |
| 2000 | India   | NULL       |   1350 |        0 |           0 |           1 |
| 2000 | USA     | Calculator |     75 |        0 |           0 |           0 |
| 2000 | USA     | Computer   |   1500 |        0 |           0 |           0 |
| 2000 | USA     | NULL       |   1575 |        0 |           0 |           1 |
| 2000 | NULL    | NULL       |   4525 |        0 |           1 |           1 |
| 2001 | Finland | Phone      |     10 |        0 |           0 |           0 |
| 2001 | Finland | NULL       |     10 |        0 |           0 |           1 |
| 2001 | USA     | Calculator |     50 |        0 |           0 |           0 |
| 2001 | USA     | Computer   |   2700 |        0 |           0 |           0 |
| 2001 | USA     | TV         |    250 |        0 |           0 |           0 |
| 2001 | USA     | NULL       |   3000 |        0 |           0 |           1 |
| 2001 | NULL    | NULL       |   3010 |        0 |           1 |           1 |
| NULL | NULL    | NULL       |   7535 |        1 |           1 |           1 |
+------+---------+------------+--------+----------+-------------+-------------+

您可以不用直接顯示 GROUPING() 的結果,而是使用 GROUPING() 來為超彙總的 NULL 值替換標籤。

mysql> SELECT
         IF(GROUPING(year), 'All years', year) AS year,
         IF(GROUPING(country), 'All countries', country) AS country,
         IF(GROUPING(product), 'All products', product) AS product,
         SUM(profit) AS profit
       FROM sales
       GROUP BY year, country, product WITH ROLLUP;
+-----------+---------------+--------------+--------+
| year      | country       | product      | profit |
+-----------+---------------+--------------+--------+
| 2000      | Finland       | Computer     |   1500 |
| 2000      | Finland       | Phone        |    100 |
| 2000      | Finland       | All products |   1600 |
| 2000      | India         | Calculator   |    150 |
| 2000      | India         | Computer     |   1200 |
| 2000      | India         | All products |   1350 |
| 2000      | USA           | Calculator   |     75 |
| 2000      | USA           | Computer     |   1500 |
| 2000      | USA           | All products |   1575 |
| 2000      | All countries | All products |   4525 |
| 2001      | Finland       | Phone        |     10 |
| 2001      | Finland       | All products |     10 |
| 2001      | USA           | Calculator   |     50 |
| 2001      | USA           | Computer     |   2700 |
| 2001      | USA           | TV           |    250 |
| 2001      | USA           | All products |   3000 |
| 2001      | All countries | All products |   3010 |
| All years | All countries | All products |   7535 |
+-----------+---------------+--------------+--------+

當有多個運算式引數時,GROUPING() 會傳回一個代表位元遮罩的結果,該位元遮罩組合了每個運算式的結果,其中最低位元對應於最右邊運算式的結果。例如,GROUPING(year, country, product) 的評估方式如下:

  result for GROUPING(product)
+ result for GROUPING(country) << 1
+ result for GROUPING(year) << 2

如果任何運算式代表超彙總的 NULL,此類 GROUPING() 的結果將為非零值,因此您可以只傳回超彙總列,並篩除一般的群組列,如下所示:

mysql> SELECT year, country, product, SUM(profit) AS profit
       FROM sales
       GROUP BY year, country, product WITH ROLLUP
       HAVING GROUPING(year, country, product) <> 0;
+------+---------+---------+--------+
| year | country | product | profit |
+------+---------+---------+--------+
| 2000 | Finland | NULL    |   1600 |
| 2000 | India   | NULL    |   1350 |
| 2000 | USA     | NULL    |   1575 |
| 2000 | NULL    | NULL    |   4525 |
| 2001 | Finland | NULL    |     10 |
| 2001 | USA     | NULL    |   3000 |
| 2001 | NULL    | NULL    |   3010 |
| NULL | NULL    | NULL    |   7535 |
+------+---------+---------+--------+

sales 表格不包含 NULL 值,因此 ROLLUP 結果中的所有 NULL 值都代表超彙總值。當資料集中包含 NULL 值時,ROLLUP 摘要可能會在超彙總列和一般的群組列中都包含 NULL 值。GROUPING() 可讓您區分這些情況。假設表格 t1 包含一個簡單的資料集,其中一組數量值有兩個分組因子,其中 NULL 表示類似「其他」或「未知」之類的值。

mysql> SELECT * FROM t1;
+------+-------+----------+
| name | size  | quantity |
+------+-------+----------+
| ball | small |       10 |
| ball | large |       20 |
| ball | NULL  |        5 |
| hoop | small |       15 |
| hoop | large |        5 |
| hoop | NULL  |        3 |
+------+-------+----------+

簡單的 ROLLUP 操作會產生以下結果,其中很難區分超彙總列中的 NULL 值和一般群組列中的 NULL 值。

mysql> SELECT name, size, SUM(quantity) AS quantity
       FROM t1
       GROUP BY name, size WITH ROLLUP;
+------+-------+----------+
| name | size  | quantity |
+------+-------+----------+
| ball | NULL  |        5 |
| ball | large |       20 |
| ball | small |       10 |
| ball | NULL  |       35 |
| hoop | NULL  |        3 |
| hoop | large |        5 |
| hoop | small |       15 |
| hoop | NULL  |       23 |
| NULL | NULL  |       58 |
+------+-------+----------+

使用 GROUPING() 來為超彙總的 NULL 值替換標籤,可讓結果更容易理解。

mysql> SELECT
         IF(GROUPING(name) = 1, 'All items', name) AS name,
         IF(GROUPING(size) = 1, 'All sizes', size) AS size,
         SUM(quantity) AS quantity
       FROM t1
       GROUP BY name, size WITH ROLLUP;
+-----------+-----------+----------+
| name      | size      | quantity |
+-----------+-----------+----------+
| ball      | NULL      |        5 |
| ball      | large     |       20 |
| ball      | small     |       10 |
| ball      | All sizes |       35 |
| hoop      | NULL      |        3 |
| hoop      | large     |        5 |
| hoop      | small     |       15 |
| hoop      | All sizes |       23 |
| All items | All sizes |       58 |
+-----------+-----------+----------+

使用 ROLLUP 時的其他考量事項

以下討論列出 MySQL 實作 ROLLUP 的一些特定行為。

ORDER BYROLLUP 可以一起使用,這樣就可以使用 ORDER BYGROUPING() 來達到群組結果的特定排序。例如:

mysql> SELECT year, SUM(profit) AS profit
       FROM sales
       GROUP BY year WITH ROLLUP
       ORDER BY GROUPING(year) DESC;
+------+--------+
| year | profit |
+------+--------+
| NULL |   7535 |
| 2000 |   4525 |
| 2001 |   3010 |
+------+--------+

在這兩種情況下,超彙總摘要列都會與計算它們的列一起排序,且其位置取決於排序順序(升序排序時在結尾,降序排序時在開頭)。

LIMIT 可用於限制傳回用戶端的列數。LIMIT 會在 ROLLUP 之後套用,因此限制會套用至 ROLLUP 新增的額外列。例如:

mysql> SELECT year, country, product, SUM(profit) AS profit
       FROM sales
       GROUP BY year, country, product WITH ROLLUP
       LIMIT 5;
+------+---------+------------+--------+
| year | country | product    | profit |
+------+---------+------------+--------+
| 2000 | Finland | Computer   |   1500 |
| 2000 | Finland | Phone      |    100 |
| 2000 | Finland | NULL       |   1600 |
| 2000 | India   | Calculator |    150 |
| 2000 | India   | Computer   |   1200 |
+------+---------+------------+--------+

LIMITROLLUP 搭配使用可能會產生較難以理解的結果,因為理解超彙總列的背景資訊較少。

MySQL 延伸功能允許在選取清單中指定未出現在 GROUP BY 清單中的欄位。(如需有關未彙總欄位和 GROUP BY 的資訊,請參閱 第 14.19.3 節,「MySQL GROUP BY 的處理」。)在此情況下,伺服器可以自由地從摘要列中的這個未彙總欄位選擇任何值,這包括 WITH ROLLUP 新增的額外列。例如,在以下查詢中,country 是一個未彙總的欄位,沒有出現在 GROUP BY 清單中,且為此欄位選擇的值是不確定的。

mysql> SELECT year, country, SUM(profit) AS profit
       FROM sales
       GROUP BY year WITH ROLLUP;
+------+---------+--------+
| year | country | profit |
+------+---------+--------+
| 2000 | India   |   4525 |
| 2001 | USA     |   3010 |
| NULL | USA     |   7535 |
+------+---------+--------+

當未啟用 ONLY_FULL_GROUP_BY SQL 模式時,允許此行為。如果啟用該模式,伺服器會因為 country 未列在 GROUP BY 子句中而拒絕此查詢,視為非法。啟用 ONLY_FULL_GROUP_BY 後,您仍然可以針對不確定值的欄位使用 ANY_VALUE() 函數來執行查詢。

mysql> SELECT year, ANY_VALUE(country) AS country, SUM(profit) AS profit
       FROM sales
       GROUP BY year WITH ROLLUP;
+------+---------+--------+
| year | country | profit |
+------+---------+--------+
| 2000 | India   |   4525 |
| 2001 | USA     |   3010 |
| NULL | USA     |   7535 |
+------+---------+--------+

彙總欄位不能當作 MATCH() 的引數(且會因錯誤而遭拒),除非在 WHERE 子句中呼叫。如需詳細資訊,請參閱第 14.9 節,「全文檢索函數」