来源:CSDN

作者:潇湘隐者

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

大家好,我是小五

今天和大家分享的内容是关于SQL中行转列和列转行的一些操作行转列,列转行是我们在开发过程中经常碰到的问题。行转列一般通过CASE WHEN 语句来实现,也可以通过 SQL SERVER 的运算符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

推荐阅读

分库分表 与NewSQL数据库

MySQL事务的实现原理

为啥不能用uuid做MySQL的主键!?

as cast float server sql_面试常考!SQL行转列和列转行相关推荐

  1. JAVA面试常考系列十

    转载自 JAVA面试常考系列十 题目一 Servlet是什么? Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,是用Java编写的服务器端程序 ...

  2. JAVA面试常考系列十一

    转载自 JAVA面试常考系列十一 题目一 什么是JSP? JSP(Java Server Page)是一个文本文档,是一种将静态内容和动态生成内容混合在一起的技术. JSP包含两种类型的文本:静态数据 ...

  3. JAVA面试常考系列一

    转载自 JAVA面试常考系列一 题目一 什么是Java虚拟机?为什么Java被称为平台无关的编程语言? java虚拟机是一个可以执行字节码文件(.class)的虚拟机进程. 为什么java与平台无关呢 ...

  4. java面试常考_JAVA面试常考系列十

    JAVA面试常考系列十 题目一 Servlet是什么? Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,是用Java编写的服务器端程序,主要的 ...

  5. 计算机网路之面试常考

    计算机网络之面试常考   整理一下计算机网络部分的面试常考点,参考书籍:<计算机网络>第五版 谢希仁的那本,希望对大家有所帮助 OSI,TCP/IP,五层协议的体系结构,以及各层协议 OS ...

  6. ❤️40条软件测试面试常考题目总结(附答案解析)【建议收藏】❤️

    40条软件测试面试常考题目总结,话不多说上干货~ (附答案解析) 1 开发犯低级错误怎么办? 开发首先要规范好编码,出低级错时不要指责,内心指出错误.让他们自己进行测试,反思找出错误. 2 你进行过哪 ...

  7. PHP面试常考内容之Memcache和Redis(2)

    你好,是我琉忆. 继周一(2019.2-18)发布的"PHP面试常考内容之Memcache和Redis(1)"后,这是第二篇,感谢你的支持和阅读. 本周(2019.2-18至2-2 ...

  8. PHP面试常考内容之面向对象(3)

    PHP面试专栏正式起更,每周一.三.五更新,提供最好最优质的PHP面试内容. 继上一篇"PHP面试常考内容之面向对象(2)"发表后,今天更新面向对象的最后一篇(3).需要(1),( ...

  9. JAVA面试常考系列九

    转载自 JAVA面试常考系列九 题目一 RMI架构层的结构是如何组成的? RMI体系结构由三层组成,分别是: 存根和骨架层(Stub and Skeleton Layer) 远程引用层(Remote ...

最新文章

  1. Swing 实现聊天系统 私发与群发
  2. jquery--call()amp;apply()函数
  3. <LINUX内核完全剖析:基于0.12内核> 笔记一
  4. 什么插件格式化文档_推荐15款IntelliJ IDEA 神级插件
  5. 什么叫云服务器_什么叫云计算,云计算是什么,最通俗的解释是这样的
  6. linux终奌站 信息 格式 更改 /etc/bashrc
  7. iOS 滑动性能优化
  8. Gstreamer官方教程汇总2---GStreamer concepts
  9. Android 10 使用PreferenceScreen的方法
  10. 查看opencv版本信息
  11. 自动驾驶岗位常见面试笔试题
  12. postMan请求下载接口添加参数
  13. 2022年fw保研经验(东南大学网安、湖南大学计科学硕、中科院沈阳自动化所,最终东南网安)
  14. 多边形面积计算公式, 根据GPS经纬度计算面积
  15. Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之部署到Linux
  16. python中如何解决类互相调用问题_两个.py之间类的相互调用问题
  17. 如何判断您是否拥有32位或64位版本的Google Chrome浏览器
  18. RxSwift--RxSwift简介
  19. ASP.Net请求处理机制初步探索之旅 - Part 2 核心(转)
  20. Golang:实现断点续传(http单线程下载文件)

热门文章

  1. jx8net一定在所有的方方面面都更坚强更勇敢了吧
  2. 【转载】雨更大了的飞鸽传书
  3. 其实企业的C++人最清楚企业的问题
  4. 局域网聊天的本质是函数
  5. 程序员从幼稚到成熟会经历哪些变化?你都知道吗?
  6. 苦苦发愁学习Python?七天掌握Python就在此时
  7. 广州程序员辞职创业卖菠萝油,放弃30万年薪
  8. 前端xss攻击的原理
  9. 织梦 详情页 php,织梦DEDECMS列表页与详情页调用图集多张图片的方法
  10. 图像处理------图像细化