在資料函式庫應用中,高效的資料處理和報表生成至關重要。本文將介紹如何運用 SQL 實作移動加總和樞紐分析,有效地處理和呈現資料。移動加總能計算特定時間區間內的累積值,例如計算員工過去 90 天的薪資總和。樞紐分析則能將資料從行格式轉換為列格式,方便使用者閱讀和分析,例如將各部門的薪資資料按部門和經理進行彙總和展示。兩種技術都能提升資料分析的效率,並提供更具洞察力的報表。
資料匯總與報表重塑:移動加總與樞紐分析
在資料分析與報表生成過程中,經常需要對資料進行匯總、重塑與移動計算,以滿足不同的業務需求。本文將介紹如何使用 SQL 實作移動加總(Moving Sum)與樞紐分析(Pivoting),並給出具體的查詢範例。
移動加總(Moving Sum)
移動加總是一種常見的資料分析技術,用於計算某個時間視窗內的累計值。例如,計算過去 90 天內的薪水總和。
原始查詢
首先,我們使用一個自連線(Self-Join)查詢來找出每個員工入職日期前 90 天內入職的其他員工,並計算其薪水總和。
SELECT e.hiredate,
e.sal,
d.sal AS sal_to_sum,
d.hiredate AS within_90_days
FROM emp e, emp d
WHERE d.hiredate BETWEEN e.hiredate - 90 AND e.hiredate
ORDER BY 1;
內容解密:
SELECT e.hiredate, e.sal, d.sal AS sal_to_sum, d.hiredate AS within_90_days:選擇員工的入職日期、薪水,以及與其相關的其他員工的薪水和入職日期。FROM emp e, emp d:對emp表進行自連線,分別以e和d作為別名。WHERE d.hiredate BETWEEN e.hiredate - 90 AND e.hiredate:篩選出入職日期在當前員工入職日期前 90 天內的其他員工。ORDER BY 1:按第一列(入職日期)排序結果。
使用聚合函式最佳化查詢
為了得到更具表達力的結果,我們可以使用聚合函式 SUM 對每個員工計算過去 90 天內的薪水總和。
SELECT e.hiredate,
e.sal,
SUM(d.sal) AS spending_pattern
FROM emp e, emp d
WHERE d.hiredate BETWEEN e.hiredate - 90 AND e.hiredate
GROUP BY e.hiredate, e.sal
ORDER BY 1;
內容解密:
SUM(d.sal) AS spending_pattern:計算每個分組內d.sal的總和,並命名為spending_pattern。GROUP BY e.hiredate, e.sal:按員工的入職日期和薪水分組,以便計算每個分組的薪水總和。
子查詢實作移動加總
我們也可以使用子查詢來實作相同的功能。
SELECT e.hiredate,
e.sal,
(SELECT SUM(sal) FROM emp d WHERE d.hiredate BETWEEN e.hiredate - 90 AND e.hiredate) AS spending_pattern
FROM emp e
ORDER BY 1;
內容解密:
(SELECT SUM(sal) FROM emp d WHERE d.hiredate BETWEEN e.hiredate - 90 AND e.hiredate):子查詢計算每個員工入職日期前 90 天內其他員工的薪水總和。AS spending_pattern:將子查詢結果命名為spending_pattern。
樞紐分析(Pivoting)與小計
樞紐分析用於將資料從行格式轉換為列格式,以便更好地呈現資料。以下範例展示如何生成包含小計的報表,並將其轉換為更易讀的格式。
原始報表
假設我們有以下報表,顯示每個部門的管理者和薪水總和,以及部門小計和總計。
DEPTNO MGR SAL
---
---
---
-
---
-
10 7782 1300
10 7839 2450
10 3750
20 7566 6000
20 7788 1100
20 7839 2975
20 7902 800
20 10875
30 7698 6550
30 7839 2850
30 9400
24025
期望結果
我們希望將報表轉換為以下格式,使其更易讀。
MGR DEPT10 DEPT20 DEPT30 TOTAL
---
-
---
---
---
---
---
---
---
--
7566 0 6000 0
7698 0 0 6550
7782 1300 0 0
7788 0 1100 0
7839 2450 2975 2850
7902 0 800 0
3750 10875 9400 24025
SQL 解法
我們可以使用 ROLLUP 和 CASE 表示式來實作樞紐分析和生成小計。
SELECT mgr,
SUM(CASE deptno WHEN 10 THEN sal ELSE 0 END) AS dept10,
SUM(CASE deptno WHEN 20 THEN sal ELSE 0 END) AS dept20,
SUM(CASE deptno WHEN 30 THEN sal ELSE 0 END) AS dept30,
SUM(CASE flag WHEN '11' THEN sal ELSE NULL END) AS total
FROM (
SELECT deptno, mgr, SUM(sal) AS sal,
CAST(GROUPING(deptno) AS CHAR(1)) || CAST(GROUPING(mgr) AS CHAR(1)) AS flag
FROM emp
WHERE mgr IS NOT NULL
GROUP BY ROLLUP(deptno, mgr)
) x
GROUP BY mgr;
Plantuml 圖示說明:
圖表翻譯: 此圖示展示了從原始資料到最終報表的處理過程。首先,使用 ROLLUP 生成小計;然後,使用 CASE 表示式進行樞紐分析;最後,得到易讀的報表格式。
資料透視與小計報表製作技術解析
在資料分析與報表製作過程中,經常需要將資料進行適當的格式化與重塑,以滿足不同使用者的需求。本篇技術解析將探討如何利用 SQL 查詢技巧來實作資料的透視與小計報表製作。
基礎查詢與資料匯總
首先,我們需要對 emp 表進行查詢,並根據 deptno 和 mgr 進行資料匯總。以下是一個基本的查詢範例:
SELECT deptno, mgr, SUM(sal) AS sal
FROM emp
WHERE mgr IS NOT NULL
GROUP BY mgr, deptno
ORDER BY 1, 2;
內容解密:
- 該查詢首先篩選出
mgr不為空的員工記錄。 - 然後根據
mgr和deptno對員工的薪水進行匯總。 - 最後按照
deptno和mgr的順序輸出結果。
利用 ROLLUP 擴充套件進行小計計算
接下來,我們使用 ROLLUP 擴充套件來對資料進行小計計算:
SELECT deptno, mgr, SUM(sal) AS sal
FROM emp
WHERE mgr IS NOT NULL
GROUP BY deptno, mgr WITH ROLLUP;
內容解密:
ROLLUP操作會為每個deptno生成小計,並最終生成總計。- 透過
GROUPING函式可以識別哪些行是小計或總計。
結合 GROUPING 函式進行標識
為了區分普通匯總資料和小計資料,我們使用 GROUPING 函式:
SELECT deptno, mgr, SUM(sal) AS sal,
CONCAT(CAST(GROUPING(deptno) AS CHAR(1)),
CAST(GROUPING(mgr) AS CHAR(1))) AS flag
FROM emp
WHERE mgr IS NOT NULL
GROUP BY deptno, mgr WITH ROLLUP;
內容解密:
GROUPING(deptno)和GROUPING(mgr)分別檢查deptno和mgr是否為小計或總計行。- 將結果拼接成一個二進位制標識
flag,用於後續處理。
最終報表製作
最後,我們利用前一步的結果來製作最終的報表:
SELECT mgr,
SUM(CASE deptno WHEN 10 THEN sal ELSE 0 END) AS dept10,
SUM(CASE deptno WHEN 20 THEN sal ELSE 0 END) AS dept20,
SUM(CASE deptno WHEN 30 THEN sal ELSE 0 END) AS dept30,
SUM(CASE flag WHEN '11' THEN sal ELSE NULL END) AS total
FROM (
SELECT deptno, mgr, SUM(sal) AS sal,
CONCAT(CAST(GROUPING(deptno) AS CHAR(1)),
CAST(GROUPING(mgr) AS CHAR(1))) AS flag
FROM emp
WHERE mgr IS NOT NULL
GROUP BY deptno, mgr WITH ROLLUP
) x
GROUP BY mgr
ORDER BY COALESCE(mgr, 9999);
內容解密:
- 利用
CASE表示式對不同部門的薪水進行匯總,並生成相應的欄位。 - 使用子查詢結果中的
flag欄位來計算總計。 - 最終結果按
mgr排序,並使用COALESCE處理空值。
圖表翻譯:
此圖示說明瞭資料從原始表格經過多步驟處理,最終形成報表的過程。
13.1 呈現父子關係
問題描述
想要將父項資訊與子項資料一起呈現。例如,顯示每位員工的姓名及其經理的名字,預期結果集如下:
EMPS_AND_MGRS
---
-
---
-
---
-
---
-
---
-
---
-
---
---
FORD works for JONES
SCOTT works for JONES
JAMES works for BLAKE
TURNER works for BLAKE
MARTIN works for BLAKE
WARD works for BLAKE
ALLEN works for BLAKE
MILLER works for CLARK
ADAMS works for SCOTT
CLARK works for KING
BLAKE works for KING
JONES works for KING
SMITH works for FORD
解決方案
透過在 MGR 和 EMPNO 上進行自連線(self-join)來找到每位員工的經理姓名。然後,使用特定資料函式庫系統提供的字串串接功能來生成所需的結果集。
DB2、Oracle 和 PostgreSQL
使用雙豎線(||)作為串接運算子:
select a.ename || ' works for ' || b.ename as emps_and_mgrs
from emp a, emp b
where a.mgr = b.empno;
MySQL
使用 CONCAT 函式進行字串串接:
select concat(a.ename, ' works for ', b.ename) as emps_and_mgrs
from emp a, emp b
where a.mgr = b.empno;
SQL Server
使用加號(+)作為串接運算子:
select a.ename + ' works for ' + b.ename as emps_and_mgrs
from emp a, emp b
where a.mgr = b.empno;
詳解
所有解決方案的實作本質上是相同的,差異僅在於字串串接的方法。
關鍵在於 MGR 和 EMPNO 之間的連線。首先,建立一個笛卡爾積(Cartesian product),將 EMP 表與其自身連線:
select a.empno, b.empno
from emp a, emp b;
這會傳回所有可能的 EMPNO 組合。接下來,篩選結果,只保留每個員工及其經理的 EMPNO:
select a.empno, b.empno as mgr
from emp a, emp b
where a.mgr = b.empno;
現在,透過選擇 B.ENAME 而不是 B.EMPNO,可以傳回每位經理的名字。
如果難以理解這個過程,可以使用純量子查詢(scalar subquery)代替自連線:
select a.ename,
(select b.ename from emp b where b.empno = a.mgr) as mgr
from emp a;
純量子查詢版本等同於自連線,除了有一行例外:員工 KING 也出現在結果集中。這是因為自連線方案中使用了等值連線,篩選掉了 MGR 為 NULL 的員工。如果需要包含這些員工,可以使用外連線:
-- ANSI 外連線語法
select a.ename, b.ename as mgr
from emp a left join emp b on (a.mgr = b.empno);
-- Oracle 外連線語法
select a.ename, b.ename as mgr
from emp a, emp b
where a.mgr = b.empno (+);
13.2 呈現子-父-祖父關係
問題描述
考慮員工 CLARK,其長官是 KING,而 CLARK 又是 MILLER 的長官,形成一個三層關係。希望能夠表達出從 MILLER 到 KING 的完整階層關係,預期結果集如下:
LEAF___BRANCH___ROOT
---
-
---
-
---
-
---
-
---
--
MILLER-->CLARK-->KING
解決方案
對於支援遞迴查詢功能的資料函式庫系統(如 DB2、PostgreSQL 和 SQL Server),可以使用遞迴公用表表達式(Recursive Common Table Expressions, CTEs)來解決這個問題。
DB2、PostgreSQL 和 SQL Server
使用遞迴 WITH 子句來查詢 MILLER 的經理 CLARK,以及 CLARK 的經理 KING。這裡使用 SQL Server 的字串串接運算子 +:
with x (tree, mgr, depth)
as (
select cast(ename as varchar(100)),
mgr, 0
from emp
where ename = 'MILLER'
union all
select cast(x.tree + '-->' + e.ename as varchar(100)),
e.mgr, x.depth + 1
from emp e, x
where x.mgr = e.empno
)
select tree as leaf___branch___root
from x
where depth = (select max(depth) from x);
詳解
這個查詢首先找到 MILLER,然後遞迴地找到其長官,直到到達最頂層(KING)。透過遞迴 CTE,將每個員工及其長官的名字串接起來,形成所需的階層關係表達。
圖表翻譯:
此圖示呈現了遞迴查詢的過程,從 MILLER 開始,不斷向上查詢其長官,直到 KING。 圖表翻譯: 此圖表顯示了從 MILLER 到 KING 的階層關係,MILLER 的長官是 CLARK,而 CLARK 的長官是 KING。透過這種結構,我們可以清晰地瞭解整個組織架構中的上下級關係。
遞迴查詢在員工階層架構中的應用
在資料函式庫查詢中,處理具有階層結構的資料是一個常見的需求。本文將探討如何使用遞迴查詢(Recursive Query)來呈現員工階層架構,並以不同的資料函式庫系統(DB2、SQL Server、PostgreSQL、MySQL 和 Oracle)為例,展示具體的實作方法。
使用遞迴公用表表達式(Recursive CTE)
DB2、SQL Server、PostgreSQL
這些資料函式庫系統支援使用遞迴公用表表達式(Recursive Common Table Expressions, CTE)來處理階層查詢。以下是一個範例查詢,用於找出從特定員工(例如 ‘MILLER’)開始的階層結構:
WITH RECURSIVE x (tree, mgr, depth) AS (
SELECT CAST(ename AS VARCHAR(100)), mgr, 0
FROM emp
WHERE ename = 'MILLER'
UNION ALL
SELECT CAST(x.tree || '-->' || e.ename AS VARCHAR(100)), e.mgr, x.depth + 1
FROM emp e, x
WHERE x.mgr = e.empno
)
SELECT tree AS leaf___branch___root
FROM x
WHERE depth = 2;
MySQL
MySQL 的遞迴查詢語法與上述資料函式庫系統類別似,但需要使用 RECURSIVE 關鍵字:
WITH RECURSIVE x (tree, mgr, depth) AS (
SELECT CAST(ename AS CHAR(100)), mgr, 0
FROM emp
WHERE ename = 'MILLER'
UNION ALL
SELECT CAST(CONCAT(x.tree, '-->', e.ename) AS CHAR(100)), e.mgr, x.depth + 1
FROM emp e, x
WHERE x.mgr = e.empno
)
SELECT tree AS leaf___branch___root
FROM x
WHERE depth = 2;
程式碼解析:
- 遞迴CTE定義:首先定義一個遞迴CTE
x,包含欄位tree、mgr和depth。 - 錨點查詢:第一部分查詢選取起始員工 ‘MILLER’ 的資料,
depth初始化為 0。 - 遞迴查詢:第二部分查詢透過
UNION ALL與錨點查詢結合,遞迴地選取下屬員工的資料,並將depth加 1。 - 結果過濾:最終查詢根據
depth值過濾結果,傳回完整的階層路徑。
使用 CONNECT BY 子句(Oracle)
Oracle 資料函式庫提供了 CONNECT BY 子句來處理階層查詢。以下是一個範例查詢,用於實作相同的功能:
SELECT LTRIM(SYS_CONNECT_BY_PATH(ename, '-->'), '-->') AS leaf___branch___root
FROM emp
WHERE LEVEL = 3
START WITH ename = 'MILLER'
CONNECT BY PRIOR mgr = empno;
程式碼解析:
SYS_CONNECT_BY_PATH函式:用於串聯階層路徑中的員工姓名。START WITH子句:指定階層查詢的起始點。CONNECT BY子句:定義階層關係,透過PRIOR關鍵字存取前一筆記錄的欄位值。LEVEL偽列:用於過濾結果,傳回指定層級的資料。
圖表說明
@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle
title SQL 移動加總與樞紐分析技術解析
package "資料庫架構" {
package "應用層" {
component [連線池] as pool
component [ORM 框架] as orm
}
package "資料庫引擎" {
component [查詢解析器] as parser
component [優化器] as optimizer
component [執行引擎] as executor
}
package "儲存層" {
database [主資料庫] as master
database [讀取副本] as replica
database [快取層] as cache
}
}
pool --> orm : 管理連線
orm --> parser : SQL 查詢
parser --> optimizer : 解析樹
optimizer --> executor : 執行計畫
executor --> master : 寫入操作
executor --> replica : 讀取操作
cache --> executor : 快取命中
master --> replica : 資料同步
note right of cache
Redis/Memcached
減少資料庫負載
end note
@enduml
圖表翻譯: 此圖示呈現了從 MILLER 到 KING 的階層關係。MILLER 直接隸屬於 CLARK,而 CLARK 又隸屬於 KING,清晰地展示了員工之間的上下級關係。