1. 程式人生 > >SQL Server 2005 中行列轉換(Pivot 和 UNPivot的使用)

SQL Server 2005 中行列轉換(Pivot 和 UNPivot的使用)

針對sql2005  系統提供兩個新的關鍵字 PIVOT 和UNPIVOT可用來作此類操作.
語法規則
<pivot_clause> ::=
        ( aggregate_function ( value_column )
        FOR pivot_column
        IN ( <column_list> )
    )
<unpivot_clause> ::=
        ( value_column FOR pivot_column IN ( <column_list> ) )
table_source PIVOT <pivot_clause>


指定基於 table_source 對 pivot_column 進行透視。table_source 是表或表表達式。輸出是包含 table_source 中 pivot_column 和 value_column 列之外的所有列的表。table_source 中 pivot_column 和 value_column 列之外的列被稱為透視運算子的組合列。
PIVOT 對輸入表執行組合列的分組操作,併為每個組返回一行。此外,input_table 的 pivot_column 中顯示的 column_list 中指定的每個值,輸出中都對應一列。


有關詳細資訊,請參閱“備註”部分和使用 PIVOT 和 UNPIVOT。
aggregate_function

系統或使用者定義的聚合函式。聚合函式應該對空值固定不變。對空值固定不變的聚合函式在求聚合值時不考慮組中的空值。
不允許使用 COUNT(*) 系統聚合函式。


value_column
PIVOT 運算子的值列。與 UNPIVOT 一起使用時,value_column 不能是輸入 table_source 中的現有列的名稱。

FOR pivot_column
PIVOT 運算子的透視列。pivot_column 必須屬於可隱式或顯式轉換為 nvarchar() 的型別。此列不能為 image 或 rowversion。
使用 UNPIVOT 時,pivot_column 是從 table_source 中提取的輸出列的名稱。table_source 中不能有該名稱的現有列。


IN ( column_list )
在 PIVOT 子句中,列出 pivot_column 中將成為輸出表的列名的值。該列表不能指定被透視的輸入 table_source 中已存在的任何列名。
在 UNPIVOT 子句中,列出 table_source 中將被提取到單個 pivot_column 中的列。

建立測試環境
CREATE TABLE pvt (VendorID int, Emp1 int, Emp2 int,
Emp3 int, Emp4 int, Emp5 int)
GO
INSERT INTO pvt VALUES (1,4,3,5,4,4)
INSERT INTO pvt VALUES (2,4,1,5,5,5)
INSERT INTO pvt VALUES (3,4,3,5,4,4)
INSERT INTO pvt VALUES (4,4,2,5,5,4)
INSERT INTO pvt VALUES (5,5,1,5,5,5)
GO
--行列合併使用 UNPIVOT
SELECT VendorID, Employee, Orders into #test
FROM
   (SELECT VendorID, Emp1, Emp2, Emp3, Emp4, Emp5
   FROM pvt) p
UNPIVOT
   (Orders FOR Employee IN        --//Orders是用來作第1列的,Employee 是合併後的列名
      (Emp1, Emp2, Emp3, Emp4, Emp5)
)AS unpvt
GO
select * from #test
--行列轉換 使用PIVOT
select VendorID,Emp1,emp2,emp3,emp4 from #test
pivot
(count(orders) For Employee in   --//可以使用相應的聚合函式
([Emp1],[emp2],[emp3],[emp4])
) as p

請注意,UNPIVOT 並不完全是 PIVOT 的逆操作。PIVOT 會執行一次聚合,從而將多個可能的行合併為輸出中的單個行。而 UNPIVOT 不會重現原始表值表示式的結果,因為行已經被合併了。另外,UNPIVOT 的輸入中的 NULL 不會顯示在輸出中,然而在執行 PIVOT 操作之前輸入中可能會含有原始的 NULL 值。

/*
標題:普通行列轉換(version 2.0)


問題:假設有張學生成績表(tb)如下:
姓名 課程 分數
張三 語文 74
張三 數學 83
張三 物理 93
李四 語文 74
李四 數學 84
李四 物理 94
想變成(得到如下結果):
姓名 語文 數學 物理
---- ---- ---- ----
李四 74   84   94
張三 74   83   93
-------------------
*/createtable tb(姓名 varchar(10) , 課程varchar(10) , 分數int)
insertinto tb values('張三' , '語文' ,74)
insertinto tb values('張三' , '數學' ,83)
insertinto tb values('張三' , '物理' ,93)
insertinto tb values('李四' , '語文' ,74)
insertinto tb values('李四' , '數學' ,84)
insertinto tb values('李四' , '物理' ,94)
go--SQL SERVER 2000 靜態SQL,指課程只有語文、數學、物理這三門課程。(以下同)select 姓名 as 姓名 ,
 
max(case 課程when'語文'then 分數else0end) 語文,
 
max(case 課程when'數學'then 分數else0end) 數學,
 
max(case 課程when'物理'then 分數else0end) 物理
from tb
groupby 姓名

--SQL SERVER 2000 動態SQL,指課程不止語文、數學、物理這三門課程。(以下同)declare@sqlvarchar(8000)
set@sql='select 姓名'select@sql=@sql+' , max(case 課程 when'''+ 課程+''' then 分數 else 0 end) ['+ 課程 +']'from (selectdistinct 課程from tb) as a
set@sql=@sql+' from tb group by 姓名'exec(@sql)

--SQL SERVER 2005 靜態SQL。select*from (select*from tb) a pivot (max(分數)for 課程in (語文,數學,物理)) b

--SQL SERVER 2005 動態SQL。declare@sqlvarchar(8000)
select@sql=isnull(@sql+'],[' ,'')+ 課程from tbgroupby 課程
set@sql='['+@sql+']'exec ('select * from (select * from tb) a pivot (max(分數) for 課程 in ('+@sql+')) b')

---------------------------------/*
問題:在上述結果的基礎上加平均分,總分,得到如下結果:
姓名 語文 數學 物理 平均分 總分
---- ---- ---- ---- ------ ----
李四 74   84   94   84.00  252
張三 74   83   93   83.33  250
*/--SQL SERVER 2000 靜態SQL。select 姓名 姓名,
 
max(case 課程when'語文'then 分數else0end) 語文,
 
max(case 課程when'數學'then 分數else0end) 數學,
 
max(case 課程when'物理'then 分數else0end) 物理,
 
cast(avg(分數*1.0)asdecimal(18,2)) 平均分,
 
sum(分數) 總分
from tb
groupby 姓名

--SQL SERVER 2000 動態SQL。declare@sqlvarchar(8000)
set@sql='select 姓名'select@sql=@sql+' , max(case 課程 when'''+ 課程+''' then 分數 else 0 end) ['+ 課程 +']'from (selectdistinct 課程from tb) as a
set@sql=@sql+' , cast(avg(分數*1.0) as decimal(18,2)) 平均分 , sum(分數) 總分 from tb group by 姓名'exec(@sql)

--SQL SERVER 2005 靜態SQL。select m.* , n.平均分 , n.總分from
(
select*from (select*from tb) a pivot (max(分數)for 課程in (語文,數學,物理)) b) m,
(
select 姓名 ,cast(avg(分數*1.0)asdecimal(18,2)) 平均分 , sum(分數) 總分from tbgroupby 姓名) n
where m.姓名 = n.姓名

--SQL SERVER 2005 動態SQL。declare@sqlvarchar(8000)
select@sql=isnull(@sql+',' ,'')+ 課程from tbgroupby 課程
exec ('select m.* , n.平均分 , n.總分 from
(select * from (select * from tb) a pivot (max(分數) for 課程 in (
'+@sql+')) b) m ,
(select 姓名 , cast(avg(分數*1.0) as decimal(18,2)) 平均分 , sum(分數) 總分 from tb group by 姓名) n
where m.姓名 = n.姓名
')

droptable tb   

------------------
--
----------------/*
問題:如果上述兩表互相換一下:即表結構和資料為:
姓名 語文 數學 物理
張三 74  83  93
李四 74  84  94


想變成(得到如下結果):


姓名 課程 分數
---- ---- ----
李四 語文 74
李四 數學 84
李四 物理 94
張三 語文 74
張三 數學 83
張三 物理 93
--------------
*/createtable tb(姓名 varchar(10) , 語文int , 數學int , 物理 int)
insertinto tb values('張三',74,83,93)
insertinto tb values('李四',74,84,94)
go--SQL SERVER 2000 靜態SQL。select*from
(
select 姓名 , 課程='語文' , 分數= 語文from tb
unionallselect 姓名 , 課程='數學' , 分數= 數學from tb
unionallselect 姓名 , 課程='物理' , 分數= 物理from tb
) t
orderby 姓名 , case 課程when'語文'then1when'數學'then2when'物理'then3end--SQL SERVER 2000 動態SQL。
--
呼叫系統表動態生態。declare@sqlvarchar(8000)
select@sql=isnull(@sql+' union all' ,'' )+' select 姓名 , [課程] ='+quotename(Name ,'''')+' , [分數] ='+quotename(Name)+' from tb'from syscolumns
where name!= N'姓名'and ID =object_id('tb')--表名tb,不包含列名為姓名的其它列orderby colid ascexec(@sql+' order by 姓名')

--SQL SERVER 2005 動態SQL。select 姓名 , 課程 , 分數from tb unpivot (分數for 課程in([語文] ,[數學] ,[物理])) t

--SQL SERVER 2005 動態SQL,同SQL SERVER 2000 動態SQL。--------------------
/*

問題:在上述的結果上加個平均分,總分,得到如下結果:


姓名 課程   分數
---- ------ ------
李四 語文   74.00
李四 數學   84.00
李四 物理   94.00
李四 平均分 84.00
李四 總分   252.00
張三 語文   74.00
張三 數學   83.00
張三 物理   93.00
張三 平均分 83.33
張三 總分   250.00
------------------
*/select*from
(
select 姓名 as 姓名 , 課程 ='語文' , 分數= 語文 from tb
unionallselect 姓名 as 姓名 , 課程 ='數學' , 分數= 數學 from tb
unionallselect 姓名 as 姓名 , 課程 ='物理' , 分數= 物理 from tb
unionallselect 姓名 as 姓名 , 課程 ='平均分' , 分數=cast((語文+ 數學+ 物理)*1.0/3asdecimal(18,2))from tb
unionallselect 姓名 as 姓名 , 課程 ='總分' , 分數= 語文 + 數學 + 物理from tb
) t
orderby 姓名 , case 課程when'語文'then1when'數學'then2when'物理'then3when'平均分'then4when'總分'then5enddroptable tb