SQL数据库查询实现行转列与列转行结果SQL语句

 

行转列,列转行是我们在开发过程中经常碰到的问题。行转列一般通过CASE WHEN 语句来实现,也可以通过 SQL SERVER 2005 新增的运算符PIVOT来实现。 用传统的方法,比较好理解。层次清晰,而且比较习惯。 但是PIVOT 、 UNPIVOT提供的语法比一系列复杂的 SELECT...CASE 语句中所指定的语法更简单、更具可读性。下面我们通过几个简单的例子来介绍一下列转行、行转列问题。

 

我们首先先通过一个老生常谈的例子,学生成绩表(下面简化了些)来形象了解下行转列

  CREATE   TABLE [ StudentScores ]
(
   
[ UserName ]          NVARCHAR ( 20 ),        -- 学生姓名
    [ Subject ]           NVARCHAR ( 30 ),        -- 科目
    [ Score ]             FLOAT ,               -- 成绩
)

INSERT INTO [ StudentScores ] SELECT ' Nick ' , ' 语文 ' , 80

INSERT INTO [ StudentScores ] SELECT ' Nick ' , ' 数学 ' , 90

INSERT INTO [ StudentScores ] SELECT ' Nick ' , ' 英语 ' , 70

INSERT INTO [ StudentScores ] SELECT ' Nick ' , ' 生物 ' , 85

INSERT INTO [ StudentScores ] SELECT ' Kent ' , ' 语文 ' , 80

INSERT INTO [ StudentScores ] SELECT ' Kent ' , ' 数学 ' , 90

INSERT INTO [ StudentScores ] SELECT ' Kent ' , ' 英语 ' , 70

INSERT INTO [ StudentScores ] SELECT ' Kent ' , ' 生物 ' , 85




如果我想知道每位学生的每科成绩,而且每个学生的全部成绩排成一行,这样方便我查看、统计,导出数据


SELECT
      UserName,
     
MAX ( CASE Subject WHEN ' 语文 ' THEN Score ELSE 0 END ) AS ' 语文 ' ,
     
MAX ( CASE Subject WHEN ' 数学 ' THEN Score ELSE 0 END ) AS ' 数学 ' ,
     
MAX ( CASE Subject WHEN ' 英语 ' THEN Score ELSE 0 END ) AS ' 英语 ' ,
     
MAX ( CASE Subject WHEN ' 生物 ' THEN Score ELSE 0 END ) AS ' 生物 '
FROM dbo. [ StudentScores ]
GROUP BY UserName




查询结果如图所示,这样我们就能很清楚的了解每位学生所有的成绩了


 

 

接下来我们来看看第二个小列子。有一个游戏玩家充值表(仅仅为了说明,举的一个小例子),

 


Code [http://www.oeedu.com]


     
     
     
     
CREATE TABLE [ Inpours ]
(
[ ID ] INT IDENTITY ( 1 , 1 ),
[ UserName ] NVARCHAR ( 20 ), -- 游戏玩家
[ CreateTime ] DATETIME , -- 充值时间
[ PayType ] NVARCHAR ( 20 ), -- 充值类型
[ Money ] DECIMAL , -- 充值金额
[ IsSuccess ] BIT , -- 是否成功 1表示成功, 0表示失败
CONSTRAINT [ PK_Inpours_ID ] PRIMARY KEY (ID)
)

INSERT INTO Inpours SELECT ' 张三 ' , ' 2010-05-01 ' , ' 支付 ' , 50 , 1

INSERT INTO Inpours SELECT ' 张三 ' , ' 2010-06-14 ' , ' 支付宝 ' , 50 , 1

INSERT INTO Inpours SELECT ' 张三 ' , ' 2010-06-14 ' , ' 手机短信 ' , 100 , 1

INSERT INTO Inpours SELECT ' 李四 ' , ' 2010-06-14 ' , ' 手机短信 ' , 100 , 1

INSERT INTO Inpours SELECT ' 李四 ' , ' 2010-07-14 ' , ' 支付宝 ' , 100 , 1

INSERT INTO Inpours SELECT ' 王五 ' , ' 2010-07-14 ' , ' 工商银行卡 ' , 100 , 1

INSERT INTO Inpours SELECT ' 赵六 ' , ' 2010-07-14 ' , ' 建设银行卡 ' , 100 , 1


 

下面来了一个统计数据的需求,要求按日期、支付方式来统计充值金额信息。这也是一个典型的行转列的例子。我们可以通过下面的脚本来达到目的

 



Code [http://www.oeedu.com]


      
      
      
      
SELECT CONVERT ( VARCHAR ( 10 ), CreateTime, 120 ) AS CreateTime,
CASE PayType WHEN ' 支付宝 ' THEN SUM ( Money ) ELSE 0 END AS ' 支付宝 ' ,
CASE PayType WHEN ' 手机短信 ' THEN SUM ( Money ) ELSE 0 END AS ' 手机短信 ' ,
CASE PayType WHEN ' 工商银行卡 ' THEN SUM ( Money ) ELSE 0 END AS ' 工商银行卡 ' ,
CASE PayType WHEN ' 建设银行卡 ' THEN SUM ( Money ) ELSE 0 END AS ' 建设银行卡 '
FROM Inpours
GROUP BY CreateTime, PayType



如图所示,我们这样只是得到了这样的输出结果,还需进一步处理,才能得到想要的结果


 





SELECT
       CreateTime,
      
ISNULL(SUM([支付宝]), 0) AS [支付宝],
      
ISNULL(SUM([手机短信]), 0) AS [手机短信],
      
ISNULL(SUM([工商银行卡]), 0) AS [工商银行卡],
      
ISNULL(SUM([建设银行卡]), 0) AS [建设银行卡]
FROM
(
   
SELECT CONVERT(VARCHAR(10), CreateTime, 120) AS CreateTime,
          
CASE PayType WHEN '支付宝'     THEN SUM(Money) ELSE 0 END AS '支付宝',
          
CASE PayType WHEN '手机短信'   THEN SUM(Money) ELSE 0 END AS '手机短信',
          
CASE PayType WHEN '工商银行卡' THEN SUM(Money) ELSE 0 END AS '工商银行卡',
          
CASE PayType WHEN '建设银行卡' THEN SUM(Money) ELSE 0 END AS '建设银行卡'
   
FROM Inpours
   
GROUP BY CreateTime, PayType
) T
GROUP BY CreateTime



其实行转列,关键是要理清逻辑,而且对分组(Group by)概念比较清晰。上面两个列子基本上就是行转列的类型了。但是有个问题来了,上面是我为了说明弄的一个简单列子。实际中,可能支付方式特别多,而且逻辑也复杂很多,可能涉及汇率、手续费等等(曾经做个这样一个),如果支付方式特别多,我们的CASE WHEN 会弄出一大堆,确实比较恼火,而且新增一种支付方式,我们还得修改脚本如果把上面的脚本用动态SQL改写一下,我们就能轻松解决这个问题

 


Code [http://www.oeedu.com]


     
     
     
     
DECLARE @cmdText VARCHAR ( 8000 );
DECLARE @tmpSql VARCHAR ( 8000 );

SET @cmdText = ' SELECT CONVERT(VARCHAR(10), CreateTime, 120) AS CreateTime, ' CHAR ( 10 );
SELECT @cmdText = @cmdText ' CASE PayType WHEN ''' PayType ''' THEN SUM(Money) ELSE 0 END AS ''' PayType
''' , ' CHAR ( 10 ) FROM ( SELECT DISTINCT PayType FROM Inpours ) T

SET @cmdText = LEFT ( @cmdText , LEN ( @cmdText ) - 2 ) -- 注意这里,如果没有加CHAR(10) 则用LEFT(@cmdText, LEN(@cmdText) -1)

SET @cmdText = @cmdText ' FROM Inpours GROUP BY CreateTime, PayType ' ;

SET @tmpSql = ' SELECT CreateTime, ' CHAR ( 10 );
SELECT @tmpSql = @tmpSql ' ISNULL(SUM( ' PayType ' ), 0) AS ''' PayType ''' , ' CHAR ( 10 )
FROM ( SELECT DISTINCT PayType FROM Inpours ) T

SET @tmpSql = LEFT ( @tmpSql , LEN ( @tmpSql ) - 2 ) ' FROM ( ' CHAR ( 10 );

SET @cmdText = @tmpSql @cmdText ' ) T GROUP BY CreateTime ' ;
PRINT @cmdText
EXECUTE ( @cmdText );



下面是通过PIVOT来进行行转列的用法,大家可以对比一下,确实要简单、更具可读性(呵呵,习惯的前提下)



Code [http://www.oeedu.com]


      
      
      
      
SELECT
CreateTime,
[ 支付宝 ] , [ 手机短信 ] ,
[ 工商银行卡 ] , [ 建设银行卡 ]
FROM
(
SELECT CONVERT ( VARCHAR ( 10 ), CreateTime, 120 ) AS CreateTime,PayType, Money
FROM Inpours
) P
PIVOT (
SUM ( Money )
FOR PayType IN
(
[ 支付宝 ] , [ 手机短信 ] , [ 工商银行卡 ] , [ 建设银行卡 ] )
)
AS T
ORDER BY CreateTime



 

有时可能会出现这样的错误:

 

消息 325,级别 15,状态 1,第 9 行

 

'PIVOT' 附近有语法错误。您可能需要将当前数据库的兼容级别设置为更高的值,以启用此功能。有关存储过程 sp_dbcmptlevel 的信息,请参见帮助。

 

这个是因为:对升级到 SQL Server 2005 或更高版本的数据库使用 PIVOT 和 UNPIVOT 时,必须将数据库的兼容级别设置为 90 或更高。有关如何设置数据库兼容级别的信息,请参阅 sp_dbcmptlevel (Transact-SQL)。 例如,只需在执行上面脚本前加上 EXEC sp_dbcmptlevel Test, 90; 就OK了, Test 是所在数据库的名称。

 


 

下面我们来看看列转行,主要是通过UNION ALL ,MAX来实现。假如有下面这么一个表

 

代码

CREATE TABLE ProgrectDetail
(
ProgrectName
NVARCHAR ( 20 ), -- 工程名称
OverseaSupply INT , -- 海外供应商供给数量
NativeSupply INT , -- 国内供应商供给数量
SouthSupply INT , -- 南方供应商供给数量
NorthSupply INT -- 北方供应商供给数量
)

INSERT INTO ProgrectDetail
SELECT ' A ' , 100 , 200 , 50 , 50
UNION ALL
SELECT ' B ' , 200 , 300 , 150 , 150
UNION ALL
SELECT ' C ' , 159 , 400 , 20 , 320
UNION ALL
SELECT ' D ' , 250 , 30 , 15 , 15


 

 

 

我们可以通过下面的脚本来实现,查询结果如下图所示

 

代码

SELECT ProgrectName, ' OverseaSupply ' AS Supplier,
MAX (OverseaSupply) AS ' SupplyNum '
FROM ProgrectDetail
GROUP BY ProgrectName
UNION ALL
SELECT ProgrectName, ' NativeSupply ' AS Supplier,
MAX (NativeSupply) AS ' SupplyNum '
FROM ProgrectDetail
GROUP BY ProgrectName
UNION ALL
SELECT ProgrectName, ' SouthSupply ' AS Supplier,
MAX (SouthSupply) AS ' SupplyNum '
FROM ProgrectDetail
GROUP BY ProgrectName
UNION ALL
SELECT ProgrectName, ' NorthSupply ' AS Supplier,
MAX (NorthSupply) AS ' SupplyNum '
FROM ProgrectDetail
GROUP BY ProgrectName


 

 

 

 

UNPIVOT 实现如下:

 

代码

SELECT ProgrectName,Supplier,SupplyNum
FROM
(
SELECT ProgrectName, OverseaSupply, NativeSupply,
SouthSupply, NorthSupply
FROM ProgrectDetail
)T
UNPIVOT
(
SupplyNum
FOR Supplier IN
(OverseaSupply, NativeSupply, SouthSupply, NorthSupply )
) P



你可能感兴趣的:(sql,sql,数据库,server,insert,手机,PIVOT)