SQL行转列与列转行

目录

先准备点数据:

CREATE TABLE Sell
(
  [Year] INT,
  [Quarter] NVARCHAR(10),
  Quantity INT
)
GO

INSERT INTO Sell
SELECT 2006, 'Q1', 20 UNION ALL
SELECT 2006, 'Q2', 15 UNION ALL
SELECT 2006, 'Q2', 4 UNION ALL
SELECT 2006, 'Q3', 12 UNION ALL
SELECT 2006, 'Q4', 18 UNION ALL
SELECT 2007, 'Q1', 10 UNION ALL
SELECT 2007, 'Q2', 10 UNION ALL
SELECT 2008, 'Q1', 8 UNION ALL
SELECT 2008, 'Q2', 7 UNION ALL
SELECT 2008, 'Q3', 5 UNION ALL
SELECT 2008, 'Q3', 10 UNION ALL
SELECT 2008, 'Q4', 9
GO

1. 何为行列互换

简单地说,就是实现如下形式的相互转换

行转列是将某行中的值作为了标题,而列转行就是将标题变为了列中的值。

2. 行转列

1) 静态
静态适合于要转换为行的列值是确定的一些值,或值不是很多的情况

-- MSSQL 2005+ Pivot 静态(行转列)
SELECT *
FROM   sell PIVOT( SUM(quantity) FOR [quarter] IN ( Q1, Q2, Q3, Q4 ) ) p

-- MSSQL 2000+ 静态(行转列)
SELECT
	[Year],
	SUM(CASE [Quarter] WHEN N'Q1' THEN Quantity ELSE 0 END) Q1,
	SUM(CASE [Quarter] WHEN N'Q2' THEN Quantity ELSE 0 END) Q2,
	SUM(CASE [Quarter] WHEN N'Q3' THEN Quantity ELSE 0 END) Q3,
	SUM(CASE [Quarter] WHEN N'Q4' THEN Quantity ELSE 0 END) Q4
FROM Sell
GROUP BY [Year]

2) 动态
当要转换为行的列值不确定或非常多的时候,可以用动态 SQL

-- MSSQL 2005+ Pivot 动态(行转列)
DECLARE @sql VARCHAR(1000), @distinct_val VARCHAR(500) ;
SET @sql = 'SELECT * FROM sell PIVOT(SUM(quantity) FOR [quarter] IN (' ;
SET @distinct_val = (SELECT STUFF(( SELECT DISTINCT ',' + [quarter] FROM sell FOR XML PATH('')), 1, 1, '')) ;
SET @sql = @sql + @distinct_val + '))' + ' p'
EXEC(@sql)

-- MSSQL 2000+ 动态(行转列)
DECLARE @sql VARCHAR(2000)

SET @sql = 'SELECT [Year]'
SELECT @sql = @sql + ',SUM(CASE [Quarter] WHEN ''' + [Quarter] + ''' THEN [Quantity] ELSE 0 END) ' + [Quarter]
FROM sell
GROUP BY [Quarter]

SET @sql = @sql + ' FROM Sell GROUP BY [Year]'

EXEC(@sql)

3. 列转行

列转行用动态比较麻烦,而且不好把控新的标题,其实实现的思路和上面行转列一样,就是找规律构造SQL。这里仅给出静态(假设上面语句的结果已保存在表 Sell_2 中):

-- 用 unpovit
select [Year],[Quarter],[Quantity]
from sell_2 unpivot(Quantity for [Quarter] in(Q1,Q2,Q3,Q4)) b

-- 不用 unpivot(静态)
select [Year],'Q1' [Quarter], Q1 [Quantity] from Sell_2 union all
select [Year],'Q2' [Quarter], Q2 [Quantity] from Sell_2 union all
select [Year],'Q3' [Quarter], Q3 [Quantity] from Sell_2 union all
select [Year],'Q4' [Quarter], Q4 [Quantity] from Sell_2
order by 1,2

4. 小结

1) Pivot 大致的执行过程:先按未出现在Pivot表达时中的列进行分组,再按in中的枚举项分别聚合。其实和select case when 类似。

2) 当列值不确认时,可以构造动态SQL实现;

3) 列转行Pivot和select .. Case…的性能差不多,但行转列,用unPivot 相比union [all]性能优势非常明显,毕竟后者要多次访问表,而且unPivot还更简洁。

4) Pivot 和 unPiovt 并非完全可逆,因为Pivot的时候,行已经被聚合过了。