SQL行列轉換方法(詳細例子)

普通行列轉換(version 1.0)僅針對sql server 2000提供靜態和動態寫法,version 2.0增加sql server 2005的有關寫法。
問題:假設有張學生成績表(tb)如下:
姓名 課程 分數
張三 語文 74
張三 數學 83
張三 物理 93
李四 語文 74
李四 數學 84
李四 物理 94
想變成(得到如下結果):
姓名 語文 數學 物理
---- ---- ---- ----
李四 74   84   94
張三 74   83   93
-------------------
*/
create table tb(姓名 varchar(10) , 課程 varchar(10) , 分數 int)
insert into tb values('張三' , '語文' , 74)
insert into tb values('張三' , '數學' , 83)
insert into tb values('張三' , '物理' , 93)
insert into tb values('李四' , '語文' , 74)
insert into tb values('李四' , '數學' , 84)
insert into tb values('李四' , '物理' , 94)
go
--SQL SERVER 2000 靜態SQL,指課程只有語文、數學、物理這三門課程。(以下同)
select 姓名 as 姓名 ,
max(case 課程 when '語文' then 分數 else 0 end) 語文,
max(case 課程 when '數學' then 分數 else 0 end) 數學,
max(case 課程 when '物理' then 分數 else 0 end) 物理
from tb
group by 姓名
--SQL SERVER 2000 動態SQL,指課程不止語文、數學、物理這三門課程。(以下同)
declare @sql varchar(8000)
set @sql = 'select 姓名 '
select @sql = @sql + ' , max(case 課程 when ''' + 課程 + ''' then 分數 else 0 end) [' + 課程 + ']'
from (select distinct 課程 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 @sql varchar(8000)
select @sql = isnull(@sql + '],[' , '') + 課程 from tb group by 課程
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 分數 else 0 end) 語文,
max(case 課程 when '數學' then 分數 else 0 end) 數學,
max(case 課程 when '物理' then 分數 else 0 end) 物理,
cast(avg(分數*1.0) as decimal(18,2)) 平均分,
sum(分數) 總分
from tb
group by 姓名
--SQL SERVER 2000 動態SQL。
declare @sql varchar(8000)
set @sql = 'select 姓名 '
select @sql = @sql + ' , max(case 課程 when ''' + 課程 + ''' then 分數 else 0 end) [' + 課程 + ']'
from (select distinct 課程 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) as decimal(18,2)) 平均分 , sum(分數) 總分 from tb group by 姓名) n
where m.姓名 = n.姓名
--SQL SERVER 2005 動態SQL。
declare @sql varchar(8000)
select @sql = isnull(@sql + ',' , '') + 課程 from tb group by 課程
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.姓名')
drop table tb  
------------------
------------------
/*
問題:如果上述兩表互相換一下:即表結構和數據爲:
姓名 語文 數學 物理
張三 74  83  93
李四 74  84  94
想變成(得到如下結果):
姓名 課程 分數
---- ---- ----
李四 語文 74
李四 數學 84
李四 物理 94
張三 語文 74
張三 數學 83
張三 物理 93
--------------
*/
create table tb(姓名 varchar(10) , 語文 int , 數學 int , 物理 int)
insert into tb values('張三',74,83,93)
insert into tb values('李四',74,84,94)
go
--SQL SERVER 2000 靜態SQL。
select * from
(
select 姓名 , 課程 = '語文' , 分數 = 語文 from tb
union all
select 姓名 , 課程 = '數學' , 分數 = 數學 from tb
union all
select 姓名 , 課程 = '物理' , 分數 = 物理 from tb
) t
order by 姓名 , case 課程 when '語文' then 1 when '數學' then 2 when '物理' then 3 end
--SQL SERVER 2000 動態SQL。
--調用系統表動態生態。
declare @sql varchar(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,不包含列名爲姓名的其它列
order by colid asc
exec(@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
union all
select 姓名 as 姓名 , 課程 = '數學' , 分數 = 數學 from tb
union all
select 姓名 as 姓名 , 課程 = '物理' , 分數 = 物理 from tb
union all
select 姓名 as 姓名 , 課程 = '平均分' , 分數 = cast((語文 + 數學 + 物理)*1.0/3 as decimal(18,2)) from tb
union all
select 姓名 as 姓名 , 課程 = '總分' , 分數 = 語文 + 數學 + 物理 from tb
) t
order by 姓名 , case 課程 when '語文' then 1 when '數學' then 2 when '物理' then 3 when '平均分' then 4 when '總分' then 5 end
drop table tb
--------------------------------------------------
以下是 dawugui 回貼
--------------------------------------------------
create table A(id char(3), num1 int, num2 int, num3 int, num4 int)
insert A select '001', 80, 90, 50, 60
insert A select '002', 84, 70, 60, 82
go
--SQL2005實現方法:
select * from A
unpivot
(num for col in([num1],[num2],[num3],[num4]))T2)tmp
--SQL2000實現:
---調用系統表動態生態
declare @s nvarchar(4000)
select @s=isnull(@s+' union all ','')+' select ID,[num]='+quotename(Name,'''')+',Qty='+quotename(Name)+' from A'
from syscolumns
where Name!=N'ID' and ID=object_id('A')--表名A,不包含列名爲ID的其它列
order by colid asc
exec(@s+' order by ID asc,[num] asc')
--生成的靜態語句
select ID,[num]='num1',Qty=[num1] from A union all
select ID,[num]='num2',Qty=[num2] from A union all
select ID,[num]='num3',Qty=[num3] from A union all
select ID,[num]='num4',Qty=[num4] from A
order by ID asc,[num] asc
/*
ID num Qty
---- ---- -----------
001 num1 80
001 num2 90
001 num3 50
001 num4 60
002 num1 84
002 num2 70
002 num3 60
002 num4 82
------------------------------
*/
--動態方法:
declare @s nvarchar(4000)
select @s=isnull(@s+' union all ','')+' select ID,[num]='+quotename(Name)+' from A'
from syscolumns
where Name!=N'ID' and ID=object_id('A')
order by colid asc
exec(@s+' order by ID asc')
--生成的語句如下:
select ID,[num]=[num1] from A union all
select ID,[num]=[num2] from A union all
select ID,[num]=[num3] from A union all
select ID,[num]=[num4] from A
order by ID asc,[num] asc
/*
ID num
---- -----------
001 80
001 90
001 50
001 60
002 82
002 60
002 70
002 84
*/
---drop table A
/*
將表數據旋轉90度(2007-11-19於海南三亞)
將下表數據:
A                    b           c           d           e         
-------------------- ----------- ----------- ----------- -----------
x                    1           2           3           4
y                    5           6           7           8
z                    9           10          11          12
轉化成如下結果:
a                    x          y          z        
-------------------- ---------- ---------- ----------
b                    1          5          9
c                    2          6          10
d                    3          7          11
e                    4          8          12
*/
--生成測試數據
create table test1(A varchar(20),b int,c int,d int,e int)
insert into test1 select 'x',1,2 ,3 ,4
insert into test1 select 'y',5,6 ,7 ,8
insert into test1 select 'z',9,10,11,12
go
--生成中間數據表
declare @s varchar(8000)
set @s = 'create table test2(a varchar(20)'
select @s = @s + ',' + A + ' varchar(10)' from test1
set @s = @s + ')'
exec(@s)
print @s
--藉助中間表實現行列轉換
declare @name varchar(20)
declare t_cursor cursor for
select name from syscolumns
where id=object_id('test1') and colid > 1 order by colid
open t_cursor
fetch next from t_cursor into @name
while @@fetch_status = 0
begin
    exec('select ' + @name + ' as t into test3 from test1')
    set @s='insert into test2 select ''' + @name + ''''
    select @s = @s + ',''' + rtrim(t) + '''' from test3
    exec(@s)
    exec('drop table test3')
    fetch next from t_cursor into @name
end
close t_cursor
deallocate t_cursor
--查看行列互換處理結果
select * from test1
select * from test2
--刪除表
drop table test1
drop table test2
----------------------------------------------------------------------------
/*固定的寫法:*/
select t1.* , t2.y , t3.z from
(select a = 'b' , x = b from test1 where a = 'x') t1,
(select a = 'b' , y = b from test1 where a = 'y') t2,
(select a = 'b' , z = b from test1 where a = 'z') t3
where t1.a = t2.a and t1.a = t2.a
union all
select t1.* , t2.y , t3.z from
(select a = 'c' , x = c from test1 where a = 'x') t1,
(select a = 'c' , y = c from test1 where a = 'y') t2,
(select a = 'c' , z = c from test1 where a = 'z') t3
where t1.a = t2.a and t1.a = t2.a
union all
select t1.* , t2.y , t3.z from
(select a = 'd' , x = d from test1 where a = 'x') t1,
(select a = 'd' , y = d from test1 where a = 'y') t2,
(select a = 'd' , z = d from test1 where a = 'z') t3
where t1.a = t2.a and t1.a = t2.a
union all
select t1.* , t2.y , t3.z from
(select a = 'e' , x = e from test1 where a = 'x') t1,
(select a = 'e' , y = e from test1 where a = 'y') t2,
(select a = 'e' , z = e from test1 where a = 'z') t3
where t1.a = t2.a and t1.a = t2.a
----------------------------------------------------------------------------
/*
表tb,數據如下:
項目種類 業績 提成
洗吹類  200   10
外賣      100   5
合計      300   15
轉換成:
項目種類 洗吹類 外賣 合計
業績      200     100   300
提成      10      5     15
*/
create table tb
(
項目種類 varchar(10),
業績     int,
提成     int
)
insert into tb(項目種類,業績,提成) values('洗吹類',200,10)
insert into tb(項目種類,業績,提成) values('外賣' ,100,5)
insert into tb(項目種類,業績,提成) values('合計' ,300,15)
go
select 項目種類,sum(洗吹類) as 洗吹類 , sum(外賣) as 外賣 , sum(合計) as 合計 from
(
select 項目種類 = '業績',
         洗吹類   = case when 項目種類 = '洗吹類' then 業績 else 0 end,
         外賣     = case when 項目種類 = '外賣'   then 業績 else 0 end,
         合計     = case when 項目種類 = '合計'   then 業績 else 0 end
from tb
union all
select 項目種類 = '提成' ,
         洗吹類   = case when 項目種類 = '洗吹類' then 提成 else 0 end,
         外賣     = case when 項目種類 = '外賣'   then 提成 else 0 end,
         合計     = case when 項目種類 = '合計'   then 提成 else 0 end
from tb
) m
group by 項目種類
order by 項目種類 desc
drop table tb
/*
項目種類 洗吹類      外賣        合計        
-------- ----------- ----------- -----------
業績     200         100         300
提成     10          5           15
(所影響的行數爲 2 行)
*/
--------------------------------------------------------------------------
/*
數據庫中tb表格如下

月份    工資   福利 獎金
1月     100    200   300
2月     110    210   310
3月     120    220   320
4月     130    230   330
我想得到的結果是
項目   1月    2月 3月 4月
工資   100    110 120 130
福利   200    210 220 230
獎金   300    310 320 330
就是說完全把表格的行列顛倒,有點像那種旋轉矩陣,請問如何用sql 語句實現?
*/
if exists (select * from dbo.sysobjects
where id = object_id(N'[dbo].[p_zj]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[p_zj]
GO
/*--行列互換的通用存儲過程(原著:鄒建):將指定的表,按指定的字段進行行列互換*/
create proc p_zj
       @tbname sysname, --要處理的表名
       @fdname sysname, --做爲轉換的列名
       @new_fdname sysname='' --爲轉換後的列指定列名
as
declare @s1 varchar(8000) , @s2 varchar(8000),
        @s3 varchar(8000) , @s4 varchar(8000),
        @s5 varchar(8000) , @i varchar(10)
select @s1 = '' , @s2 = '' , @s3 = '' , @s4 = '' , @s5 = '' , @i = '0'
select @s1 = @s1 + ',@' + @i + ' varchar(8000)',
       @s2 = @s2 + ',@' + @i + '=''' + case isnull(@new_fdname , '') when '' then ''
       else @new_fdname + '=' end + '''''' + name + '''''''',
       @s3 = @s3 + 'select @' + @i + '=@' + @i + '+'',['' + [' + @fdname +
       ']+'']=''+cast([' + name + '] as varchar) from [' + @tbname + ']',
       @s4 = @s4 + ',@' + @i + '=''select ''+@' + @i,
       @s5 = @s5 + '+'' union all ''+@' + @i,
       @i=cast(@i as int)+1
from syscolumns
where object_id(@tbname)=id and name<>@fdname
select @s1=substring(@s1,2,8000),
       @s2=substring(@s2,2,8000),
       @s4=substring(@s4,2,8000),
       @s5=substring(@s5,16,8000)
exec('declare ' + @s1 + 'select ' + @s2 + @s3 + 'select ' + @s4 + '
exec(' + @s5 + ')')
go
--用上面的存儲過程測試:
create table Test(月份 varchar(4), 工資 int, 福利 int, 獎金 int)
insert Test
select '1月',100,200,300 union all
select '2月',110,210,310 union all
select '3月',120,220,320 union all
select '4月',130,230,330
go
exec p_zj 'Test', '月份' , '項目'
drop table Test
drop proc p_zj
/*
項目   1月         2月         3月         4月        
---- ----------- ----------- ----------- -----------
福利   200         210         220         230
工資   100         110         120         130
獎金   300         310         320         330
(所影響的行數爲 3 行)
*/
/*
靜態寫法(SQL2005)
*/
--測試環境
create table Test(月份 varchar(4), 工資 int, 福利 int, 獎金 int)
insert Test
select '1月',100,200,300 union all
select '2月',110,210,310 union all
select '3月',120,220,320 union all
select '4月',130,230,330
go
--測試語句
SELECT * FROM
(
SELECT 考覈月份,月份,金額 FROM
     (SELECT 月份, 工資, 福利, 獎金 FROM Test) p
UNPIVOT
     (金額 FOR 考覈月份 IN (工資, 福利, 獎金))AS unpvt
) T
PIVOT
(MAX(金額) FOR 月份 in ([1月],[2月],[3月],[4月]))AS pt
--測試結果
/*
考覈月份 1月     2月      3月     4月
------- ----- -----   ------ -------
福利200210220230
工資100110120130
獎金300310320330
*/
--刪除環境
Drop table Test
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章