T-SQL——數據透視和逆透視


志銘-2021年10月6日 22:50:00

0. 測試數據集及說明

0.1 准備測試數據

【測試數據1】

WITH Temp1 AS 
(
SELECT '張三' AS Name, '語文' AS Subject, 100 AS Scores
UNION ALL
SELECT '張三' AS Name, '數學' AS Subject, 90 AS Scores
UNION ALL
SELECT '張三' AS Name, '英語' AS Subject, 80 AS Scores
UNION ALL
SELECT '李四' AS Name, '語文' AS Subject, 90 AS Scores
UNION ALL
SELECT '李四' AS Name, '數學' AS Subject, 70 AS Scores
UNION ALL
SELECT '李四' AS Name, '英語' AS Subject, 60 AS Scores
)
SELECT * FROM  Temp1 

結果:
Name Subject Scores
---- ------- -----------
張三   語文      100
張三   數學      90
張三   英語      80
李四   語文      90
李四   數學      70
李四   英語      60

【測試數據2】

WITH Temp2 AS 
(
 SELECT '張三' AS Name, '八年級二班' AS Class, NULL AS 英語, NULL AS 數學, 100 AS 語文
 UNION ALL
 SELECT '李四' AS Name, '三年級二班' AS Class, 60 AS 英語, 70 AS 數學, 90 AS 語文
 UNION ALL
 SELECT '張三' AS Name, '三年級二班' AS Class, 80 AS 英語, 90 AS 數學, NULL AS 語文
 )
SELECT * FROM Temp2 

結果:
Name Class      英語          數學          語文
---- ---------- ----------- ----------- -----------
張三   八年級二班      NULL        NULL        100
李四   三年級二班      60          70          90
張三   三年級二班      80          90          NULL

0.2 對一維表和二維表理解

測試數據1:

  • 稱為:一維表
  • 特征:每條記錄的特定字段可能出現值重復
  • 一維表是數據記錄的最原始的樣子。
  • 一維表包含一列字段表示每條記錄的屬性。比如說測試數據1中的Subject列
  • 一維表的字段名稱即表示該字段下的數據的實際含義。比如說測試數據1中的Scores列下的數據表示成績

測試數據2:

  • 稱為:二維表
  • 特征:列字段名稱就是種類信息,每條記錄的
  • 二維表則是展示出分類匯總后的數據信息
  • 二維表若無說明,是無法明確數據含義的,比如測試數據2,若是不告訴你這是成績,你是無法理解數據值的真實含義的

一維表和二維表對比:

  • 按照數據庫第一范式:一個實體中不應該包含重復類型的特性,對應到表就是:類似的值不能在一個表的多個列上重復

    • 創建表應該按照一維表的格式創建,記錄產生信息。
    • 若是建表的時候創建為二維表,將所有的種類做為字段,則可能出現表的字段非常的多,當種類有所添加的時候,后續還要繼續添加字段。
  • 一維表方便存儲數據,同樣也是便於后期查詢

  • 二維表可以直觀的表現數據的信息,是用於展示的報表和報告



1. 透視轉換

1.1 使用標准SQL進行數據透視

--注意這里實現業務場景,在CASE WHEN語句中
--我們應該使用默認的ELSE NULL ,而不是ELSE 0。沒有數據是沒有數據,0是0
SELECT Name,
	   SUM(CASE WHEN Subject ='語文' THEN Temp1.Scores ElSE NULL END) AS 語文 ,
	   SUM(CASE WHEN Subject ='數學' THEN Temp1.Scores ELSE NULL END) AS 數學 ,
	   SUM(CASE WHEN Subject ='英語' THEN Temp1.Scores ELSE NULL END) AS 英語 
FROM Temp1  GROUP BY Temp1.Name

結果:
Name     英語          數學          語文
----   ----------- ----------- -----------
李四      60           70            90
張三      80           90            100

【說明】:使用標准的SQL語句進行數據的透視轉換,我們可以直白的發現透視轉換,是分為三個階段的:

分組-->擴展-->聚合

以上例說明:

  • 分組:我們按照Name字段進行分組,使每一個用戶產生一條記錄
    • 使用GROUP BY實現分組
  • 擴展:擴展是對列的擴展,使用case語句, 對三個科目(語文,數學,英語)擴展為列
    • 使用SELECT 配合CASE WHEN實現
  • 聚合:按照分組依據進行聚合,這里按照Name字段分組,使用SUM函數對Scores字段聚合
    • 使用任一聚合函數聚合

對照上述三個邏輯步驟:

  • Name字段:分組依據字段(分組元素)。指定要用於透視結果表的左側列,也稱為設置鍵
  • Subject字段:待擴展字段(待擴展元素)。透視結果表中的列頭所在的列,也稱為透視鍵,"語文,數學,英語"為目標列的列名的集合,又稱為透視輸出列
  • Scores字段:聚合字段(聚合元素)。透視結果表中的主要顯示的數據就是來自這個字段的值,又稱為透視值

1.2 使用T-SQL中pivot函數進行數據透視

【函數】:pivot
【作用】:行轉列函數,SQL Server2005
【語法】:pivot(任一聚合函數 for 需轉列的值所在列名 in (需轉為列名的值))
【用法】:select …from table pivot(……) as T

【示例1】:
現有一張成績表,表中記錄了每個學生每科目的成績,如下。
現在需要將科目作為列頭,統計每個學生的成績。

WITH Temp1 AS 
(
SELECT '張三' AS Name, '語文' AS Subject, 100 AS Scores
UNION ALL
SELECT '張三' AS Name, '數學' AS Subject, 90 AS Scores
UNION ALL
SELECT '張三' AS Name, '英語' AS Subject, 80 AS Scores
UNION ALL
SELECT '李四' AS Name, '語文' AS Subject, 90 AS Scores
UNION ALL
SELECT '李四' AS Name, '數學' AS Subject, 70 AS Scores
UNION ALL
SELECT '李四' AS Name, '英語' AS Subject, 60 AS Scores
)
SELECT T.Name, T.英語, T.數學, T.語文 FROM Temp1 PIVOT(SUM(Scores) FOR [Subject] IN (語文,數學,英語)) T;

結果:
Name  英語          數學          語文
---- ----------- ----------- -----------
李四   60            70          90
張三   80            90          100

【注意1】:
我們是按照以下格式:
select …from table pivot(任意聚合函數(聚合字段) for 待擴展字段名 in (待擴展元素集)) as T
使用pivot對table進行數據透視

若是table中的字段既不是聚合字段,又不是待擴展字段,則會默認為pivot函數中的分組依據的字段。

所以使用pivot函數進行數據透視的結果集一般不會是數據庫中的原表,而是一個查詢出的特定字段的結果集

【注意2】:
若待擴展元素集是非常規標識符,比如說日期或數字開頭的其他類型數據,則我們需要使用方括號將每一個待擴展展元素括起。

【示例2】:

現在在測試數據1中添加一個Class字段,使用pivot函數透視數據

WITH Temp1 AS 
(
SELECT '張三' AS Name, '語文' AS Subject, 100 AS Scores,'八年級一班' AS Class
UNION ALL
SELECT '張三' AS Name, '數學' AS Subject, 90 AS Scores,'三年級二班' AS Class
UNION ALL
SELECT '張三' AS Name, '英語' AS Subject, 80 AS Scores,'三年級二班' AS Class
UNION ALL
SELECT '李四' AS Name, '語文' AS Subject, 90 AS Scores,'三年級二班' AS Class
UNION ALL
SELECT '李四' AS Name, '數學' AS Subject, 70 AS Scores,'三年級二班' AS Class
 UNION ALL
 SELECT '李四' AS Name, '英語' AS Subject, 60 AS Scores,'三年級二班' AS Class
)
SELECT T.Name,T.Class, T.英語, T.數學, T.語文 FROM Temp1 PIVOT(SUM(Scores) FOR [Subject] IN (語文,數學,英語)) T;

結果:

Name      Class      英語          數學          語文
---- ---------- ----------- ----------- -----------
張三   八年級一班      NULL        NULL        100
李四   三年級二班      60          70          90
張三   三年級二班      80          90          NULL

【說明】:結果集Temp1中的class既不是聚合字段,也不是待擴展字段,所以會被默認為是分組依據!

而往往,我們期望的分組依據只要Name字段,

所以一般都是建議不要直接對數據庫中的基礎表進行透視操作,而是將特定字段查詢出來作為一個表結果集,之后在對這個結果集進行透視操作。

1.3 關於 待擴展元素集合獲取的方式



2. 逆透視轉換

【逆透視】:逆透視轉換將來自單個記錄中多個列的值擴展為單個列中具有同樣值的多個記錄,使得非規范的數據集成為較規范的版本。

2.1 使用標准SQL進行數據逆透視

數據的逆透視也是分為三步:請客,斬首,收下當狗

啊唾,不對,應該是是:生成副本-->提取元素-->刪除不相干交叉


WITH Temp AS 
(
	--測試數據集
 	SELECT '張三' AS Name, '八年級二班' AS Class, NULL AS 英語, NULL AS 數學, 100 AS 語文
 	UNION ALL
 	SELECT '李四' AS Name, '三年級二班' AS Class, 60 AS 英語, 70 AS 數學, 90 AS 語文
 	UNION ALL
 	SELECT '張三' AS Name, '三年級二班' AS Class, 80 AS 英語, 90 AS 數學, NULL AS 語文
 )
 ,Temp1 AS 
 (
	--為每一行生成副本
	--與需要逆透視的列交叉連接獲取笛卡爾積
 	SELECT * FROM  Temp CROSS JOIN( VALUES('英語'),('數學'),('語文')) AS T(SubjectName)
	--結果
	--Name	Class	   英語	   數學	   語文	    SubjectName
	--張三	八年級二班	NULL	NULL	100	    英語
	--張三	八年級二班	NULL	NULL	100	    數學
	--張三	八年級二班	NULL	NULL	100	    語文
	--李四	三年級二班	60	    70	    90	    英語
	--李四	三年級二班	60	    70	    90	    數學
	--李四	三年級二班	60	    70	    90	    語文
	--張三	三年級二班	80	    90   	NULL	英語
	--張三	三年級二班	80	    90  	NULL	數學
	--張三	三年級二班	80	    90  	NULL	語文
 )
 ,Temp2 AS
 (
	 --當前行SubjectName為英語則取英語列
	SELECT Name,Class,Temp1.SubjectName,
        CASE Temp1.SubjectName
           WHEN '英語' THEN
               英語
           WHEN '數學' THEN
               數學
           WHEN '語文' THEN
               語文
        END AS Scores
    FROM Temp1
	--結果
	--Name	Class	SubjectName	Scores
	--張三	八年級二班	英語	NULL
	--張三	八年級二班	數學	NULL
	--張三	八年級二班	語文	100
	--李四	三年級二班	英語	60
	--李四	三年級二班	數學	70
	--李四	三年級二班	語文	90
	--張三	三年級二班	英語	80
	--張三	三年級二班	數學	90
	--張三	三年級二班	語文	NULL
)
--刪除不相關交叉行(排除NULL值行)
SELECT * FROM Temp2 WHERE  Temp2.Scores IS NOT NULL

--結果
--Name	Class	SubjectName	Scores
--張三	八年級二班	語文	100
--李四	三年級二班	英語	60
--李四	三年級二班	數學	70
--李四	三年級二班	語文	90
--張三	三年級二班	英語	80
--張三	三年級二班	數學	90

2.2 使用T-SQL中unpivot函數進行數據逆透視

【函數】:unpivot
【作用】:列轉行函數,SQL Server2005
【語法】:unpivot(作為屬性值列的列名 for 屬性名 in (同一屬性的列名))
【用法】:select …from table unpivot(……) as T

【示例1】


WITH Temp2 AS 
(
 SELECT '張三' AS Name, '八年級二班' AS Class, NULL AS 英語, NULL AS 數學, 100 AS 語文
 UNION ALL
 SELECT '李四' AS Name, '三年級二班' AS Class, 60 AS 英語, 70 AS 數學, 90 AS 語文
 UNION ALL
 SELECT '張三' AS Name, '三年級二班' AS Class, 80 AS 英語, 90 AS 數學, NULL AS 語文
 )
SELECT * FROM Temp2 UNPIVOT(Socore FOR Subject IN(英語, 數學, 語文)) T;

結果:
Name    Class        Socore      Subject
----   ----------   --------    ----------
張三   八年級二班      100         語文
李四   三年級二班      60          英語
李四   三年級二班      70          數學
李四   三年級二班      90          語文
張三   三年級二班      80          英語
張三   三年級二班      90          數學


3. 透視之后再逆透視數據信息減少

上面的示例數據中,將原數據(測試數據1)進行透視操作,變為測試數據2。將測試數據2逆透視結果變回了測試數據1。

這只是巧合而已,因為我們的測試數據1中沒有出現相同屬性(同一個人相同科目)的多條記錄:
比如說,如果李四有兩條英語成績如下:

WITH Temp1 AS 
(
SELECT '張三' AS Name, '語文' AS Subject, 100 AS Scores
UNION ALL
SELECT '張三' AS Name, '數學' AS Subject, 90 AS Scores
UNION ALL
SELECT '張三' AS Name, '英語' AS Subject, NULL AS Scores--張三英語成績沒有記錄
UNION ALL
SELECT '李四' AS Name, '語文' AS Subject, 90 AS Scores
UNION ALL
SELECT '李四' AS Name, '數學' AS Subject, 70 AS Scores
UNION ALL
SELECT '李四' AS Name, '英語' AS Subject, 60 AS Scores--李四有兩條英語成績
UNION ALL
SELECT '李四' AS Name, '英語' AS Subject, 60 AS Scores--李四有兩條英語成績
)
,Temp2 AS --數據透視
(
SELECT * FROM  Temp1  PIVOT(SUM(Scores) FOR Subject IN (語文,數學,英語))AS T
)
SELECT * FROM Temp2 UNPIVOT(Scores FOR Subject IN (語文,數學,英語))AS T

透視結果:

Name    語文          數學          英語
----   ----------- ----------- -----------
李四     90           70            120
張三     100          90            NULL

對透視結果逆透視:

Name Scores      Subject
---- ----------- -------------
李四   90          語文
李四   70          數學
李四   120         英語--這里逆透視就無法還原為原始數據(這里就是聚合成一條數據了)
張三   100         語文
張三   90          數學
					   --張三英語為NULL的記錄在原始數據中是存在,而透視再逆透視后則沒有了

之前詳述了透視包含三個邏輯步驟,分組-->擴展-->聚合。

因為透視包含了聚合,表的信息量減少了,所以在做逆透視無法還原成原始數據。

簡單的說:

UNPIVOT 並不完全是 PIVOT 的逆操作。
PIVOT 執行聚合,並將多個可能的行合並為輸出中的一行。
UNPIVOT 不重現原始表值表達式的結果,因為行已被合並。

另外,UNPIVOT 輸入中的 NULL 值也在輸出中消失了。
如果值消失,表明在執行 PIVOT 操作前,輸入中可能就已存在原始 NULL 值。



4. 參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM