如何從單行提取成多行(TSQL)?

Outline:

  1. 問題產生背景
  2. 交叉連接
  3. UNION操作
  4. WITH子句
  5. 性能比較
  6. 拋磚引玉(歡迎指教!)

問題產生背景

從一個僅有數額指標的交易表中創建一個查詢,這個查詢用於會計日誌條目。因此如果一個日誌條目應該有存款和借款,但是其他的值應該是相同的,因此我想通過SQL查詢從一行提取2行記錄。聽起來很模糊?我Google之後發現沒有任何結果,所以我就寫了這篇文章。爲了簡化,這裏舉個例子。我有一張表叫做SampleTable,它包含如下記錄:

ID

FirstName

LastName

1

Anna

Gates

2

John

Doe

3

Joe

Bloggs

4

Raj

Kumar

現在你的任務是爲每個記錄創建3行,因此它將顯示如下:

ID

FirstName

LastName

ItemNumber

ItemDescription

1

Anna

Gates

1

Item 1

1

Anna

Gates

2

Item 2

1

Anna

Gates

3

Item 3

2

John

Doe

1

Item 1

2

John

Doe

2

Item 2

2

John

Doe

3

Item 3

3

Joe

Bloggs

1

Item 1

3

Joe

Bloggs

2

Item 2

3

Joe

Bloggs

3

Item 3

4

Raj

Kumar

1

Item 1

4

Raj

Kumar

2

Item 2

4

Raj

Kumar

3

Item 3

現在,你如何實現它呢?有許多種方法,但是我們要找出哪個方法查詢效率最高。有下面是那種方法:

  1. 交叉連接
  2. UNION查詢
  3. WITH查詢

1、交叉連接

爲了激活大家對交叉連接沉睡的記憶,首先介紹下什麼是交叉連接。所謂交叉連接,就是兩個表的笛卡爾積的另一稱謂。交叉連接爲將第一張表的每一行與第二張表的每一行組合產生一新的元組。設兩張表R、S分別有k1、k2條記錄,每條記錄的列數分別爲m、n,則交叉連接的結果元組數爲k1*k2,每個元組的列數爲m+n(前面m列是R的,後面n列是S的)。當然這是在沒有where條件的情況下,如果加了where添加可能會過濾掉一部分不符合條件的記錄。

所以上面的結果可以看成下面兩張表的交叉連接產生的:

從單行提取成多行

因此可以用如下SQL語句:

  1. SELECT SampleTable.ID, SampleTable.FirstName, SampleTable.LastName, Extender.ItemNumber, Extender.ItemDescription  
  2. FROM SampleTable CROSS JOIN 
  3. (SELECT 1 AS ItemNumber, 'Item 1' AS ItemDescription  
  4. UNION ALL 
  5. SELECT 2 AS ItemNumber, 'Item 2' AS ItemDescription  
  6. UNION ALL 
  7. SELECT 3 AS ItemNumber, 'Item 3' AS ItemDescription) AS Extender; 
本來是打算對錶2構建一張臨時表,但考慮到SQL Server與Oracle構建臨時表是有差異的,考慮到這個我就用上面這種方式(UNION ALL,集合查詢)。

2、UNION操作

UNION是集合操作中的一種,SELECT語句的查詢結果是元組的集合,所以多個SELECT語句的結果可進行集合操作。集合操作主要包括並操作UNION、交操作INTERSECT和差操作EXCEPT。注意,參加集合操作的各查詢結果的列數必須相同;對應項的數據類型也必須相同。

下面是實現代碼:

  1. SELECT SampleTable.ID, SampleTable.FirstName, SampleTable.LastName, 1 AS ItemNumber, 'Item 1' AS ItemDescription  
  2. FROM SampleTable  
  3. UNION ALL 
  4. SELECT SampleTable.ID, SampleTable.FirstName, SampleTable.LastName, 2 AS ItemNumber, 'Item 2' AS ItemDescription  
  5. FROM SampleTable  
  6. UNION ALL 
  7. SELECT SampleTable.ID, SampleTable.FirstName, SampleTable.LastName, 3 AS ItemNumber, 'Item 3' AS ItemDescription  
  8. FROM SampleTable; 

3、WITH子句

WITH AS短語,也叫做子查詢部分(subquery factoring),可以讓你做很多事情,定義一個SQL片斷,該SQL片斷會被整個SQL語句所用到。有的時候,是爲了讓SQL語句的可讀性更高些,也有可能是在UNION ALL的不同部分,作爲提供數據的部分。特別對於UNION ALL比較有用。因爲UNION ALL的每個部分可能相同,但是如果每個部分都去執行一遍的話,則成本太高,所以可以使用WITH AS短語,則只要執行一遍即可。如果WITH AS短語所定義的表名被調用兩次以上,則優化器會自動將WITH AS短語所獲取的數據放入一個TEMP表裏,如果只是被調用一次,則不會。而提示materialize則是強制將WITH AS短語裏的數據放入一個全局臨時表裏。很多查詢通過這種方法都可以提高速度。

其實with子句提供定義一個臨時視圖的方法,這個定義只對with子句出現的那條查詢有效。換就話說,就是把查詢結果放入一個臨時表,然後通過查詢語句從臨時表查詢結果。

如果你對with子句還是不熟悉理解的話,看下面的例子。下面的查詢是:查詢具有最大餘額的賬戶的語句,如果具有同樣最大查詢的賬戶有很多,他們都會被選擇。即假設如果最大餘額是10000的賬戶有3個,則這三個賬戶都會顯示出來。

  1. with max-balance(value) as   
  2.         select max(balance)   
  3.         from account   
  4. select account-number   
  5. from account,max-balance   
  6. where account.balance=max-balance.value  

我想看到這,with子句大家都理解了。下面是上面問題的實現代碼:

  1. WITH ExtendedTable(ID, FirstName, LastName) AS 
  2. (SELECT SampleTable.ID, SampleTable.FirstName, SampleTable.LastName  
  3. FROM SampleTable)  
  4. SELECT *, 1 AS ItemNumber, 'Item 1' AS ItemDescription from ExtendedTable  
  5. UNION ALL 
  6. SELECT *, 2 AS ItemNumber, 'Item 2' AS ItemDescription from ExtendedTable  
  7. UNION ALL 
  8. SELECT *, 3 AS ItemNumber, 'Item 3' AS ItemDescription from ExtendedTable 

性能比較

上面三種方法都可以得到相同的結果,那到底它們的性能到底如何呢?下面我們把這三種方法的代碼放到同一個查詢中執行,如下:
-----------------------------------------
--方法一、交叉連接
-----------------------------------------

  1. SELECT SampleTable.ID, SampleTable.FirstName, SampleTable.LastName, Extender.ItemNumber, Extender.ItemDescription  
  2. FROM SampleTable CROSS JOIN 
  3. (SELECT 1 AS ItemNumber, 'Item 1' AS ItemDescription  
  4. UNION ALL 
  5. SELECT 2 AS ItemNumber, 'Item 2' AS ItemDescription  
  6. UNION ALL 
  7. SELECT 3 AS ItemNumber, 'Item 3' AS ItemDescription) AS Extender; 

-----------------------------------------
--方法二、UNION操作
-----------------------------------------

  1. SELECT SampleTable.ID, SampleTable.FirstName, SampleTable.LastName, 1 AS ItemNumber, 'Item 1' AS ItemDescription  
  2. FROM SampleTable  
  3. UNION ALL 
  4. SELECT SampleTable.ID, SampleTable.FirstName, SampleTable.LastName, 2 AS ItemNumber, 'Item 2' AS ItemDescription  
  5. FROM SampleTable  
  6. UNION ALL 
  7. SELECT SampleTable.ID, SampleTable.FirstName, SampleTable.LastName, 3 AS ItemNumber, 'Item 3' AS ItemDescription  
  8. FROM SampleTable; 

-----------------------------------------
--方法三、WITH子句
-----------------------------------------

  1. WITH ExtendedTable(ID, FirstName, LastName) AS 
  2. (SELECT SampleTable.ID, SampleTable.FirstName, SampleTable.LastName  
  3. FROM SampleTable)  
  4. SELECT *, 1 AS ItemNumber, 'Item 1' AS ItemDescription from ExtendedTable  
  5. UNION ALL 
  6. SELECT *, 2 AS ItemNumber, 'Item 2' AS ItemDescription from ExtendedTable  
  7. UNION ALL 
  8. SELECT *, 3 AS ItemNumber, 'Item 3' AS ItemDescription from ExtendedTable 

執行查詢時選中包括實際的執行計劃(在SQL Server的查詢菜單下面),得到執行計劃如下圖所示(由於圖太大分三張貼上)。

方法一:

從單行提取成多行

方法二:

從單行提取成多行

方法三:

從單行提取成多行

從圖中我們可以清楚地看到方法一得開銷僅佔15%,而方法二、三相同都佔43%。由此可見交叉連接的性能最好,而union操作與with子句性能相對較低。

拋磚引玉

ps.不知道您有沒有看到這裏,可能很多看官還沒看到這節就把網頁給關了(⊙﹏⊙b汗)。如果您看到這裏了,你能說出造成這個性能差異的原因嗎?歡迎大家回帖,包括拍磚。

我指出其中一點:交叉連接和with子句一樣都是用構建一張臨時表與SampleTable做連接,但是他們的性能差異源於交叉連接時執行select語句時做了聚簇索引。(just maybe,I'am not sure!)

另外推薦一篇講with ties的文章:偶遇with ties

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章