SQL grouping

黃子毅發表於2022-04-06

SQL grouping 解決 OLAP 場景總計與小計問題,其語法分為幾類,但要解決的是同一個問題:

ROLLUP 與 CUBE 是封裝了規則的 GROUPING SETS,而 GROUPING SETS 則是最原始的規則。

為了方便理解,讓我們從一個問題入手,層層遞進吧。

底表

以上是示例底表,共有 8 條資料,城市1、城市2 兩個城市,下面各有地區1~4,每條資料都有該資料的人口數。

現在想計算人口總計,以及各城市人口小計。在沒有掌握 grouping 語法前,我們只能通過兩個 select 語句 union 後得到:

SELECT city, sum(people) FROM test GROUP BY city
union
SELECT '合計' as city, sum(people) FROM test

但兩條 select 語句聚合了兩次,效能是一個不小的開銷,因此 SQL 提供了 GROUPING SETS 語法解決這個問題。

GROUPING SETS

GROUP BY GROUPING SETS 可以指定任意聚合項,比如我們要同時計算總計與分組合計,就要按照空內容進行 GROUP BY 進行一次 sum,再按照 city 進行 GROUP BY 再進行一次 sum,換成 GROUPING SETS 描述就是:

SELECT 
city, area,
sum(people)
FROM test
GROUP BY GROUPING SETS((), (city, area))

其中 GROUPING SETS((), (city, area)) 表示分別按照 ()(city, area) 聚合計算總計。返回結果是:

可以看到,值為 NULL 的行就是我們要的總計,其值是沒有任何 GROUP BY 限制算出來的。

類似的,我們還可以寫 GROUPING SETS((), (city), (city, area), (area)) 等任意數量、任意組合的 GROUP BY 條件。

通過這種規則計算的資料我們稱為 “超級分組記錄”。我們發現 “超級分組記錄” 產生的 NULL 值很容易和真正的 NULL 值弄混,所以 SQL 提供了 GROUPING 函式解決這個問題。

函式 GROUPING

對於超級分組記錄產生的 NULL,是可以被 GROUPING() 函式識別為 1 的:

SELECT 
GROUPING(city),
GROUPING(area),
sum(people)
FROM test
GROUP BY GROUPING SETS((), (city, area))

具體效果見下圖:

可以看到,但凡是超級分組計算出來的欄位都會識別為 1,我們利用之前學習的 SQL CASE 表示式 將其轉換為總計、小計字樣,就可以得出一張資料分析表了:

SELECT 
CASE WHEN GROUPING(city) = 1 THEN '總計' ELSE city END,
CASE WHEN GROUPING(area) = 1 THEN '小計' ELSE area END,
sum(people)
FROM test
GROUP BY GROUPING SETS((), (city, area))

然後前端表格展示時,將第一行 “總計”、“小計” 單元格合併為 “總計”,就完成了總計這個 BI 視覺化分析功能。

ROLLUP

ROLLUP 是捲起的意思,是一種特定規則的 GROUPING SETS,以下兩種寫法是等價的:

SELECT sum(people) FROM test
GROUP BY ROLLUP(city)

-- 等價於
SELECT sum(people) FROM test
GROUP BY GROUPING SETS((), (city))

再看一組等價描述:

SELECT sum(people) FROM test
GROUP BY ROLLUP(city, area)

-- 等價於
SELECT sum(people) FROM test
GROUP BY GROUPING SETS((), (city), (city, area))

發現規律了嗎?ROLLUP 會按順序把 GROUP BY 內容 “一個個捲起來”。用 GROUPING 函式判斷超級分組記錄對 ROLLUP 同樣適用。

CUBE

CUBE 又有所不同,它對內容進行了所有可能性展開(所以叫 CUBE)。

類比上面的例子,我們再寫兩組等價的展開:

SELECT sum(people) FROM test
GROUP BY CUBE(city)

-- 等價於
SELECT sum(people) FROM test
GROUP BY GROUPING SETS((), (city))

上面的例子因為只有一項還看不出來,下面兩項分組就能看出來了:

SELECT sum(people) FROM test
GROUP BY CUBE(city, area)

-- 等價於
SELECT sum(people) FROM test
GROUP BY GROUPING SETS((), (city), (area), (city, area))

所謂 CUBE,是一種多維形狀的描述,二維時有 2^1 種展開,三維時有 2^2 種展開,四維、五維依此類推。可以想象,如果用 CUBE 描述了很多組合,複雜度會爆炸。

總結

學習了 GROUPING 語法,以後前端同學的你不會再糾結這個問題了吧:

產品開啟了總計、小計,我們是額外取一次數還是放到一起獲取啊?

這個問題的標準答案和原理都在這篇文章裡了。PS:對於不支援 GROUPING 語法資料庫,要想辦法遮蔽,就像前端 polyfill 一樣,是一種降級方案。至於如何遮蔽,參考文章開頭提到的兩個 SELECT + UNION。

討論地址是:精讀《SQL grouping》· Issue #406 · ascoders/weekly

如果你想參與討論,請 點選這裡,每週都有新的主題,週末或週一釋出。前端精讀 - 幫你篩選靠譜的內容。

關注 前端精讀微信公眾號

<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">

版權宣告:自由轉載-非商用-非衍生-保持署名(創意共享 3.0 許可證

相關文章