天天看點

SqlServer-資料旋轉-知其然也指其是以然

    在日常開發中,尤其是資料統計制作報表的時候,我們經常需要一種把行轉換成列的技術,用于展示我們的統計結果,也許有人會說,我不能在設計表結構的時候就按照我想展示的結構進行設計麼?雖然這也是一種辦法,但日常的統計都是随着業務不斷的運作,而逐漸積累的動态資料,這時新的資料就會平凡導緻表結構的修改。

    例如:我們現在有一個需求,在資料庫中存儲每個人的名字、所掌握的專業技能與每項技能的熟練度,并且我想以一種簡單的統計方式,看每個人的每項技能的熟練度,統計結果的要求如下:

SqlServer-資料旋轉-知其然也指其是以然

    那麼我們應該如何考慮設計資料庫中的表結構?當然你可以就按照上面輸出的結果格式設計表結構,但這種結構在後期随着業務的變化會過度愈來愈資料定義語言(DDL),試想一下後面再來一位趙六,并且趙六以上技能都會的同時還會Flink,我們勢必要面對對表結構的修改,這将是我們的程式設計非常痛苦,是以我們通常會選擇設計一張包含:姓名,專業與熟練度着三個列的表結構來存儲基本資訊,這樣我們以後隻需要對表中的資料進行增删改的操作即可滿足要求,當然這在統計的時候也會自然變得稍微複雜一些,不過,當你權衡每種方法的利弊時,我選擇兩害相較取其輕。

    閑話少叙,接下來就開始我們的正題“資料旋轉”,前面說了資料旋轉我們可以了解為一種把行轉換成列的技術,結合我們上面的例子,我們基礎資料的表結構設計如下:

SqlServer-資料旋轉-知其然也指其是以然
    那麼我們如何将原始的表結構轉換為目标格式輸出呢?從原始資料表結構到目标展示的表結構,我們可以看出,這就是一種典型的把行轉換成列的需求,首先,我們先把原始表結構實作,具體語句如下:

CREATE TABLE skills
(
    name NVARCHAR(6),
    skills NVARCHAR(20),
    score int
)

INSERT INTO skills VALUES('張三','Java',91);
INSERT INTO skills VALUES('張三','HDFS',75);
INSERT INTO skills VALUES('張三','HIVE',85);
INSERT INTO skills VALUES('張三','Spark',85);
INSERT INTO skills VALUES('李四','Java',98);
INSERT INTO skills VALUES('王五','.Net',99);
INSERT INTO skills VALUES('王五','Java',75);
INSERT INTO skills VALUES('王五','HDFS',85);
INSERT INTO skills VALUES('王五','HIVE',85);
INSERT INTO skills VALUES('王五','Spark',85);      

    那麼首先我們回想一下上一面《SqlServer-邏輯查詢-ON與WHERE的天壤之别》中描述的查詢邏輯處理的過程,這對于了解這個示例是非常有幫助的,我在前面講到了SELECT階段通常很多情景下會在SQL語句的最後一步執行,并且SELECT語句用于指定最終表都輸出哪些列,那麼我們在看下我們示例中要求輸出的都有哪些列?輸出結果中包含(姓名、.Net、Java、HDFS、HIVE、Spark),并且我們可以從問題和結果中看出,是把每個人的按照指定格式輸出,腦子裡面有沒有瞬間想起分組?從以上我們可以得出通過SELECT可以控制輸出列,通過分組可以按人員姓名統計分數。我們指導SQL在邏輯查詢處理過程中,每一步後都會生成一張虛拟表,并且GOURP BY在SELECT之前執行,那麼我們就先從分組開始。

第一步:先按名稱執行分組

此步我們隻考慮分組暫不考慮SELECT輸出哪些内容與分組本身的一些特性包括聚合呀什麼的,我們來看下如下語句,看看該語句在邏輯查詢過程中是怎樣的執行過程。

SELECT name FROM skills GROUP BY name      

我們得到的執行結果如下:

SqlServer-資料旋轉-知其然也指其是以然

為了讓該語句順利執行,我在前面制定了SELECT name,輸出name列,查詢結果隻是表象,其實在GROUPBY階段生成的虛拟表結構如下:

SqlServer-資料旋轉-知其然也指其是以然

第二步:通過SELECT指定輸出列

    從該虛拟表結構中我們可以看出,虛拟表中已經對name列加入了分組描述,那麼下一步我們就可以通過指定SELECT指定輸出哪些列來達到我們資料旋轉的目的,因為為在分組中指定列不能直接通過SELECT輸出,而需要結合聚合函數,我們可以将我們要輸出的列包含在聚合函數中,并且在聚合函數中加入我們的CASE語句來達到我們行邊列的效果,例如:

MAX(CASE WHEN skills = ‘Java’ THEN score END) AS Java      

CASE的用法請關注後期SqlServer系列教程文章中詳細了解,這裡我隻重點表述一下CASE表達式中如果沒有寫ELSE,則相當于預設寫了個ELSE NULL,也就是說當判斷到該項資料時,如果條件不滿足會自動填充NULL,那麼我們來看一下完整的SQL如下:

SELECT  name,
               MAX(CASE WHEN skills = '.Net' THEN score END) AS [.Net],
               MAX(CASE WHEN skills = 'Java' THEN score END) AS Java,
               MAX(CASE WHEN skills = 'HDFS' THEN score END) AS HDFS,
               MAX(CASE WHEN skills = 'HIVE' THEN score END) AS HIVE,
               MAX(CASE WHEN skills = 'Spark' THEN score END) AS Spark
         FROM skills 
     GROUP BY name      

我們來具體按照邏輯查詢處理過程分析一下,首先我們進行了分組,該步驟産生的虛拟表結構如下:

SqlServer-資料旋轉-知其然也指其是以然

    接下來我們通過SELECT制定了将所有技能輸出為列,通過CASE語句控制該列具體展示的内容,這裡細心且好學的朋友會有一個疑問,明明用的MAX聚合函數應該顯示的内容都是每個分組中score列最大的值呀?這裡我想說仔細看,我們CASE是現在聚合函數中的,也就是說它在聚合函數之前執行,我們通過下圖來看一下具體的執行過程:

SqlServer-資料旋轉-知其然也指其是以然

我們可以看到原始表的每一行資料都會經曆一邊上圖的過程,原始表執行完最後一行資料,那麼我們得到的中間虛拟結果表,内容如下:

SqlServer-資料旋轉-知其然也指其是以然

 最後我們通過分組與聚合就能得到我們最終想要的結果,通過資料旋轉完成了我們示例最終的展示要求。

SqlServer-資料旋轉-知其然也指其是以然

最後我想補充依據,以上是根據标準SQL進行的資料旋轉,在SqlServer中加入了PIVOT(隻适用于SqlServer2005以上版本),用來實作相同的資料旋轉,但我覺的該用法還是很不直覺,不易于了解,接下來我使用PIVOT進行該需求的實作,大家下來可以咱評論區補充一下對PIVOT的看法。

SELECT  name, [.Net],Java,HDFS,HIVE,Spark
         FROM skills 
        PIVOT(MAX(score) FOR skills IN([.Net],Java,HDFS,HIVE,Spark)) AS T      

處理代碼量降低了一點點之外,我并沒有看到其他任何的優勢,希望大家可以在評論區或者私下聯系我,發表一下您對PIVOT這種用法的看法,創作不易,如果您覺得幫到了您,還請輕按兩下螢幕點個贊。