1. 程式人生 > >行轉列:SQL SERVER PIVOT與用法解釋

行轉列:SQL SERVER PIVOT與用法解釋

在資料庫操作中,有些時候我們遇到需要實現“行轉列”的需求,例如一下的表為某店鋪的一週收入情況表:

WEEK_INCOME(WEEK VARCHAR(10),INCOME DECIMAL)

我們先插入一些模擬資料:

複製程式碼
INSERT INTO WEEK_INCOME 
SELECT '星期一',1000
UNION ALL
SELECT '星期二',2000
UNION ALL
SELECT '星期三',3000
UNION ALL
SELECT '星期四',4000
UNION ALL
SELECT '星期五',5000
UNION ALL
SELECT '星期六',6000
UNION ALL
SELECT '星期日',7000
複製程式碼

一般我們最經常使用的查詢是查詢一週中每天或某幾天的收入,例如查詢週一至週日全部的收入:

SELECT WEEK,INCOME FROM WEEK_INCOME

得到如下的查詢結果集:

WEEK           INCOME
星期一           1000
星期二           2000
星期三           3000
星期四           4000
星期五           5000
星期六           6000
星期日           7000

但是在一些情況下(往往是某些報表中),我們希望在一行中顯示週一至週日的收入,這時候查詢結果集應該是這樣的:

星期一   星期二   星期三   星期四   星期五   星期六   星期日
1000     2000     3000     4000     5000     6000     7000

這種情況下,SQL查詢語句可以這樣寫:

複製程式碼
SELECT  
SUM(CASE WEEK WHEN '星期一' THEN INCOME END) AS [星期一],
SUM(CASE WEEK WHEN '星期二' THEN INCOME END) AS [星期二],
SUM(CASE WEEK WHEN '星期三' THEN INCOME END) AS [星期三],
SUM(CASE WEEK WHEN '星期四' THEN INCOME END) AS [星期四],
SUM(CASE WEEK WHEN '星期五' THEN INCOME END) AS [星期五],
SUM(CASE
WEEK WHEN '星期六' THEN INCOME END) AS [星期六], SUM(CASE WEEK WHEN '星期日' THEN INCOME END) AS [星期日] FROM WEEK_INCOME
複製程式碼

但是,在SQL SERVER 2005中提供了更為簡便的方法,這就是"PIVOT"關係運算符。(相反的“列轉行”是UNPIVOT),以下是使用PIVOT實現“行轉列”的SQL語句

複製程式碼
SELECT [星期一],[星期二],[星期三],[星期四],[星期五],[星期六],[星期日]
FROM WEEK_INCOME
PIVOT
(
    SUM(INCOME) for [week] in([星期一],[星期二],[星期三],[星期四],[星期五],[星期六],[星期日])
)TBL
複製程式碼

請參考MSDN中關於PIVOT的用法:

但是MSDN上的描述太過於規範嚴肅,我看了半天還沒弄清楚怎樣使用PIVOT,搞不清楚PIVOT裡面的語法的含義。於是又google了很多資料,以及通過上面提到的WEEK_INCOME表例子作了試驗,最終搞清楚了其用法。在網上有篇博文解釋的很好:T-SQL PIVOT語法剖析與實戰,基本上我要寫的就是參照該博文,再加上自己一點個人理解。

要理解PIVOT語法,就是要清楚微軟為什麼這樣設計PIVOT,但我相信是現實需求催生設計思路,所以歸根到底我們還是要弄清楚什麼是“行轉列”:

正常情況下的查詢結果是這樣:

星期一           1000
星期二           2000
星期三           3000
星期四           4000
星期五           5000
星期六           6000
星期日           7000

行轉列後是這樣:

星期一   星期二   星期三   星期四   星期五   星期六   星期日
1000    2000    3000    4000    5000    6000    7000

也就是說,行轉列後,原來的某個列的值變做了列名,在這裡就是原來WEEK列的值“星期一”,"星期二"..."星期日"邊做了列名,而我們需要做的另一個工作就是計算這些列的值(這裡的“計算”其實就是PIVOT裡面的聚合函式(sum,avg等))

現在結合註釋來分析一下PIVOT語法(在這之前最好看看我上面提到博文:T-SQL PIVOT語法剖析與實戰,裡面說到的PIVOT語法的三個步驟挺重要):

複製程式碼
SELECT [星期一],[星期二],[星期三],[星期四],[星期五],[星期六],[星期日]--這裡是PIVOT第三步(選擇行轉列後的結果集的列)這裡可以用“*”表示選擇所有列,也可以只選擇某些列(也就是某些天)
FROM WEEK_INCOME --這裡是PIVOT第二步驟(準備原始的查詢結果,因為PIVOT是對一個原始的查詢結果集進行轉換操作,所以先查詢一個結果集出來)這裡可以是一個select子查詢,但為子查詢時候要指定別名,否則語法錯誤
PIVOT
(
    SUM(INCOME) for [week] in([星期一],[星期二],[星期三],[星期四],[星期五],[星期六],[星期日])--這裡是PIVOT第一步驟,也是核心的地方,進行行轉列操作。聚合函式SUM表示你需要怎樣處理轉換後的列的值,是總和(sum),還是平均(avg)還是min,max等等。例如如果week_income表中有兩條資料並且其week都是“星期一”,其中一條的income是1000,另一條income是500,那麼在這裡使用sum,行轉列後“星期一”這個列的值當然是1500了。後面的for [week] in([星期一],[星期二]...)中 for [week]就是說將week列的值分別轉換成一個個列,也就是“以值變列”。但是需要轉換成列的值有可能有很多,我們只想取其中幾個值轉換成列,那麼怎樣取呢?就是在in裡面了,比如我此刻只想看工作日的收入,在in裡面就只寫“星期一”至“星期五”(注意,in裡面是原來week列的值,"以值變列")。總的來說,SUM(INCOME) for [week] in([星期一],[星期二],[星期三],[星期四],[星期五],[星期六],[星期日])這句的意思如果直譯出來,就是說:將列[week]值為"星期一","星期二","星期三","星期四","星期五","星期六","星期日"分別轉換成列,這些列的值取income的總和。
)TBL--別名一定要寫
複製程式碼

以上是我對PIVOT的理解,我盡所能表達出來。不過話說回來,個人的理解的方式也不同,就如我開始看了很多篇博文,都沒有搞清楚PIVOT用法。結果還是硬的通過例子和別人的博文再加上思考才弄懂了,所以如果各位看了本篇之後仍不能理解,那很正常,配合例子再加上自己思考,慢慢的定能理解。

以下指令碼可以實現動態多欄“透視表”:
declare @week_tmp nchar(20), @select_str nchar(100)
declare pivot_cursor cursor for select distinct week from week_income
open pivot_cursor
fetch next from pivot_cursor into @week_tmp
while @@FETCH_STATUS=0
begin
set @select_str=isnull(rtrim(@select_str)+',','')+'case week when '''+isnull(rtrim(@week_tmp),'')+''' then income end'+isnull(rtrim(' as '[email protected]_tmp),'')
fetch next from pivot_cursor into @week_tmp
end
close pivot_cursor
deallocate pivot_cursor
set @select_str='select '+rtrim(@select_str)+' from week_income'
exec(@select_str)
當然,我們也可以設法讓Pivot指令碼動態(第2方案)。只需改兩行,其他不變。
......
set @select_str=isnull(rtrim(@select_str)+',','')+isnull(rtrim(@week_tmp),'') --只取出橫向透視表多欄名
......
set @select_str='select '+rtrim(@select_str)+' from week_income Pivot (sum(income) for week in ('+rtrim(@select_str)+')) pvt' --實現動態Pivot指令碼
......
上述指令碼在MSSQL2005驗證通過,ORACLE可能需要適當修改(但不支援Pivot,只能第1種方案),如:+號、isnull函式等。

p.s

看了網上的例子,以為只能將純數字的進行行列轉換,但是經過測試,如果表內資料不是數字,也可以進行行轉列

另外 此方法只適用於 2005以上資料庫,如果是2000的庫檔案掛接到SQL 2005以上版本的庫上執行,會出現如下報錯

.....SET COMPATIBILITY_LEVEL....You may need to set the compatibility level of the current database to a higher value to enable this feature.....

其原因就是 2000的庫雖然掛接到2005以上的Server上,但是庫結構的相容級別並沒有變。

所以 需要 設定資料庫相容級別到 2005以上才行

設定方法:

ALTER DATABASE database_name 
SET COMPATIBILITY_LEVEL = { 90 | 100 | 110 }
引數: database_name

要修改的資料庫的名稱。

COMPATIBILITY_LEVEL { 90 | 100 | 110 }

要使資料庫與之相容的 SQL Server 版本。 該值必須為下列值之一:

90 = SQL Server 2005

100 = SQL Server 2008 和 SQL Server 2008 R2

110 = SQL Server 2012

註釋:

對於所有 SQL Server 2012 安裝,預設的相容級別都為 110。 除非 model 資料庫有更低的相容級別,否則 SQL Server 2012 中建立的資料庫會設定為該級別。 在將資料庫從 SQL Server 的任何早期版本升級到 SQL Server 2012 時,如果資料庫的相容級別不在 90 以下,則該資料庫將保留其現有的相容級別。 升級相容級別低於 90 的資料庫會將資料庫的相容級別設定為 90。 這既適用於系統資料庫也適用於使用者資料庫。 使用 ALTER DATABASE 可更改資料庫的相容級別。 若要檢視資料庫的當前相容級別,請查詢 sys.databases 目錄檢視中的 compatibility_level 列。

利用相容級別獲得向後相容

相容級別隻影響指定資料庫的行為,而不影響整個伺服器的行為。 相容級別只實現與 SQL Server 的早期版本保持部分向後相容。 通過將相容級別用作臨時性的遷移輔助工具,可解決相關相容級別設定控制的行為之間存在的版本差異問題。 如果現有 SQL Server 應用程式受到 SQL Server 2012 中行為差異的影響,請對該應用程式進行轉換,使之能正常執行。 然後使用 ALTER DATABASE 將相容級別更改為 100。 資料庫的新相容性設定將在該資料庫下次成為當前資料庫(無論是在登入時作為預設資料庫還是在 USE 語句中指定)時生效。

最佳做法

如果在使用者連線到資料庫時更改相容級別,可能會使活動查詢產生不正確的結果集。 例如,如果在編寫查詢計劃時相容級別發生更改,則編寫後的計劃可能同時基於舊的和新的相容級別,從而造成計劃不正確,並可能導致結果不準確。 此外,如果將計劃放在計劃快取中供後續的查詢重用,則問題可能更加複雜。 為了避免查詢結果不準確,建議您使用以下過程來更改資料庫的相容級別:

  1. 通過使用 ALTER DATABASE SET SINGLE_USER,將資料庫設定為單使用者訪問模式。

  2. 更改資料庫的相容級別。

  3. 通過使用 ALTER DATABASE SET MULTI_USER,將資料庫設為多使用者訪問模式。

相容級別和儲存過程

執行某一儲存過程時,該儲存過程將使用定義它的資料庫的當前相容級別。 在更改某一資料庫的相容性設定時,該資料庫的所有儲存過程都將隨之自動重新編寫。

實際操作例子:

建立表:
CREATE TABLE [dbo].[LabelPropertyValue](

[id] [int] IDENTITY(1,1) NOT NULL,
[LabelProid] [int] NULL,
[FaxDocListID] [int] NULL,
[Propertyvalue] [nvarchar](255) NULL,
 CONSTRAINT [PK__LabelPro__3213E83F44160A59] PRIMARY KEY CLUSTERED 
(
[id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

測試資料:

id           LabelProid           FaxDocListID           Propertyvalue
1415           2           1200           2013-05-18
1416           3           1200           MU222
1417           7           1200           到達
1418           4           1200           虹橋機場
1419           5           1200           T2
1420           6           1200                      
1421           9           1200           徐XX
1422           10           1200           市部長
1423           11           1200           2013-05-18
1424           12           1200           2045
1425           14           1200           5059
1426           15           1200           服務完成
1427           17           1200           VVIP
1428           2           1198           2013-05-18
1429           3           1198           MU5555
1430           7           1198           出發
1431           4           1198           浦東機場
1432           5           1198           T1
1433           6           1198                      
1434           9           1198           鄭XXX
1435           10           1198           市記
1436           11           1198           2013-05-18
1437           12           1198           0900
1438           14           1198           5064
1439           15           1198           服務取消
1440           17           1198           VVIP
1550           15           1201           服務完成
NULL           NULL           NULL           NULL

如果是 

select [2] as '日期',* from 
(select Propertyvalue,LabelProid from LabelPropertyValue
where   (FaxDocListID = 1200) OR
                      (FaxDocListID = 1201) OR
                      (FaxDocListID = 1198)
) a
PIVOT
(
max(Propertyvalue) for LabelProid in ([2],[3],[7],[4],[5],[6],[9],[10],[11],[12],[14],[15],[17])
)b

結果為 


如果是

select [2] as '日期',* from 
(select FaxDocListID,Propertyvalue,LabelProid from LabelPropertyValue
where   (FaxDocListID = 1200) OR
                      (FaxDocListID = 1201) OR
                      (FaxDocListID = 1198)
) a
PIVOT
(
max(Propertyvalue) for LabelProid in ([2],[3],[7],[4],[5],[6],[9],[10],[11],[12],[14],[15],[17])
)b
order by FaxDocListID desc

結果為 


相關推薦

SQL SERVER PIVOT用法解釋

在資料庫操作中,有些時候我們遇到需要實現“行轉列”的需求,例如一下的表為某店鋪的一週收入情況表: WEEK_INCOME(WEEK VARCHAR(10),INCOME DECIMAL) 我們先插入一些模擬資料: INSERT INTO WEEK_INCOME

SQLdecode函式

  前言   開發中我們經常會用到行轉列,這裡記錄一下我在專案中實現行轉列的思路。需求:報表模組,統計某機房機架的不同狀態(1 空閒  2 預佔  3 佔用)的數量(真實需求更為複雜,這裡只是討論技術,簡化一下)     decode函式   以下介紹摘自百度百科:  

oracle 11g 的問題 decode實現pivot實現

oracle 11g 行轉列的問題舉一個簡單的例子,假設有表名為demo其中只有兩列一列為型別names,一列為數量nums。表中資料如下:目標統計出表中apple及orange各自的總數,在一列中顯示出來。常規寫法:select names,sum(nums) from d

表的 DECODE(Oracle) 和 CASE WHEN 的異同點

異同點 都可以對錶行轉列; DECODE功能上和簡單Case函式比較類似,不能像Case搜尋函式一樣,進行更復雜的判斷 在Case函式中,可以使用BETWEEN, LIKE, IS NULL, IN, EXISTS等等(也可以使用NOT IN和NOT EXISTS,但是這個時候要注意NULL的

sql(PIVOT)轉行(UNPIVOT)

列轉行 gif 而且 碼農 實現 score username info rect 在做數據統計的時候,行轉列,列轉行是經常碰到的問題。case when方式太麻煩了,而且可擴展性不強,可以使用 PIVOT,UNPIVOT比較快速實現行轉列,列轉行,而且可擴展性強

SQL(PIVOT)轉行(UNPIVOT)簡明方法

數量 ref 統計 logs pan when 可擴展 南方 float 原文地址:https://www.cnblogs.com/linJie1930906722/p/6036714.html 在做數據統計的時候,行轉列,列轉行是經常碰到的問題。case when方式太

sql內置函數pivot強大的功能

子查詢 註意 ges 中一 rom cnblogs blog 聚合函數 星期六 http://blog.csdn.net/xb12369/article/details/8149608 http://www.cnblogs.com/lwhkdash/archive/20

SQL Server 轉行

結果 name pre toolbar des null 表名 再次 arch 一、多行轉成一列(並以","隔開) 表名:A 表數據: 想要的查詢結果: 查詢語句: SELECT name , value = ( STUFF(( SELECT

PIVOT函數

sco 課程 計算列 格式 ... str logs sub .com PIVOT函數,行轉列 PIVOT函數的格式如下: PIVOT(<聚合函數>([聚合列值]) FOR [行轉列前的列名] IN([行轉列後的列名1],[行轉列後的列名2],[行轉列後的列名3

sql pivot) 和unpivot(轉行)的用法

sql clas 數據 sele core unp null col style 1、PIVOT用法(行轉列) select * from Table_Score as a pivot (sum(score) for a.name in ([語文],[數學],[外語],[

查詢每個學生每門課程的成績,sql server實現

本人經常寫sql server指令碼,有時需要行轉列,這裡做個筆記。 練習指令碼 -- 學生表 CREATE TABLE student ( stuid VARCHAR(16) NOT NULL, stunm VARCHAR(20) NOT NULL, PRI

SQL資料庫查詢實現轉行結果SQL語句

 CREATETABLE[StudentScores](    [UserName]NVARCHAR(20),        --學生姓名[Subject]NVARCHAR(30),        --科目[Score]FLOAT,               --成績)INSERTINTO[StudentS

SQL Server 動態(引數化表名、分組欄位、欄位值)

一.本文所涉及的內容(Contents) 二.背景(Contexts)   其實行轉列並不是一個什麼新鮮的話題了,甚至已經被大家說到爛了,網上的很多例子多多少少都有些問題,所以我希望能讓大家快速的看到執行的效果,所以在動態列的基礎上再把表、分組欄位、行轉列欄位、值這四個行轉列固定需要的值變成真正意義的

Oracle pivot轉行unpivot 的Sql語句總結

多行轉字串 這個比較簡單,用||或concat函式可以實現 print? 1.  select concat(id,username) str from app_user   2.     3.  select id||username str from app_use

SQL 轉行

先準備點資料: CREATE TABLE Sell ( [Year] INT, [Quarter] NVARCHAR(10), Quantity INT ) GO INSERT INTO

sql內建函式pivot強大的功能

語法: PIVOT用於將列值旋轉為列名(即行轉列),在SQL Server 2000可以用聚合函式配合CASE語句實現 PIVOT的一般語法是:PIVOT(聚合函式(列) FOR 列 in (…) )AS P 完整語法: table_source PIVOT(

SQL pivot),unpivot(轉行)

【pivot】行轉列:多行變一列 假設學生成績表Score1 Name Subject Score 小張 語文 88 小花 數學 89 小張

sql應用轉行

一、行轉列例項: 場景: 今天運營人員讓我提取每個使用者在某種交易型別下每年的交易總金額。 表結構: CREATE TABLE `ORDERS` (   `ID` int(11) NOT NULL AUTO_INCREMENT,   `USER_ID` varchar(10

SQL Server 轉行。多成一

一、多行轉成一列(並以","隔開)表名:A表資料:想要的查詢結果:查詢語句:SELECT name , value = ( STUFF(( SELECT ',' + value FROM A

SQLPIVOT關鍵字的用法

昨天寫報表遇到行轉列,原來都是使用動態sql來實現,這次嘗試使用了下pivot來實現,pivot是sql server2005後加入的關鍵字,它使用起來比較方便,比起動態sql會簡化很多。 基本語法:select 列1,[A],[B],[C]... from table p