[转载]SQL行转列,列转行

简介: 原文地址:http://www.cnblogs.com/kerrycode/archive/2010/07/28/1786547.html 行转列,列转行是我们在开发过程中经常碰到的问题。行转列一般通过CASE WHEN 语句来实现,也可以通过 SQL SERVER 2005 新增的运算符PIVOT来实现。

原文地址:http://www.cnblogs.com/kerrycode/archive/2010/07/28/1786547.html

 行转列,列转行是我们在开发过程中经常碰到的问题。行转列一般通过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

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

 

 

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

 代码

复制代码
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
复制代码

 

下面来了一个统计数据的需求,要求按日期、支付方式来统计充值金额信息。这也是一个典型的行转列的例子。我们可以通过下面的脚本来达到目的
复制代码
代码
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改写一下,我们就能轻松解决这个问题 

代码
复制代码
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来进行行转列的用法,大家可以对比一下,确实要简单、更具可读性(呵呵,习惯的前提下)

复制代码
代码
复制代码
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
复制代码

 

目录
相关文章
|
3月前
|
SQL 流计算
Flink SQL提供了行转列的功能,可以通过使用`UNPIVOT`操作来实现
【1月更文挑战第1天】Flink SQL提供了行转列的功能,可以通过使用`UNPIVOT`操作来实现
112 0
|
8月前
|
SQL 前端开发 关系型数据库
pg库实现sql行转列
这个主题还是比较常见的,行转列主要适用于对数据作聚合统计,如统计某类目的商品在某个时间区间的销售情况。列转行问题同样也很常见。
221 0
pg库实现sql行转列
|
SQL Oracle 关系型数据库
【SQL开发实战技巧】系列(二十四):数仓报表场景☞通过执行计划详解”行转列”,”列转行”是如何实现的
本篇文章讲解的主要内容是:***目前Oracle支持的行列互换有两种方式:case when、pivot\unpivot,我将通过几个案例来给大家详解如何通过这两种方式实现“行转列”,“列转行”的需求,并通过执行计划看case when、pivot\unpivot二者的底层逻辑关系以及效率上的影响。***
【SQL开发实战技巧】系列(二十四):数仓报表场景☞通过执行计划详解”行转列”,”列转行”是如何实现的
|
SQL
SQL中的行转列和列转行
SQL是IT行业很多岗位都要求具备的一项能力,对于数据岗位而言更是如此,甚至说扎实的SQL基础也往往是入职这些岗位的必备技能。而在SQL面试中,一道出镜频率很高的题目就是行转列和列转行的问题,可以说这也是一道经典的SQL题目,本文就这一问题做以介绍分享。
827 0
SQL中的行转列和列转行
|
SQL
行转列【死磕sql】
行转列【死磕sql】
137 0
行转列【死磕sql】
|
SQL 分布式计算 Hadoop
Flink SQL 如何实现列转行?
在 SQL 任务里面经常会遇到一列转多行的需求,今天就来总结一下在 Flink SQL 里面如何实现列转行的,先来看下面的一个具体案例. 需求 原始数据格式如下: name data JasonLee [{"content_type":"flink","url":"111"},{"content_type":"spark","url":"222"},{"content_type":"hadoop","url":"333"}] data 格式化
|
SQL
重温SQL行转列,性能又双叒提升了
重温SQL行转列,性能又双叒提升了
297 0
重温SQL行转列,性能又双叒提升了
|
SQL
SQL中行转列、列转行
SQL中行转列、列转行
213 0
SQL中行转列、列转行
【每日SQL打卡】​​​​​​​​​​​​​​​DAY 19丨行转列【难度中等】​
【每日SQL打卡】​​​​​​​​​​​​​​​DAY 19丨行转列【难度中等】​
|
SQL Oracle Java
建议收藏丨sql行转列的一千种写法!!
建议收藏丨sql行转列的一千种写法!!
建议收藏丨sql行转列的一千种写法!!