设为首页 收藏本站
查看: 1834|回复: 0

[经验分享] 重温SQL——行转列,列转行

[复制链接]

尚未签到

发表于 2018-10-20 11:04:12 | 显示全部楼层 |阅读模式
  行转列,列转行是我们在开发过程中经常碰到的问题。行转列一般通过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', '语文', 80INSERT INTO [StudentScores] SELECT 'Nick', '数学', 90INSERT INTO [StudentScores] SELECT 'Nick', '英语', 70INSERT INTO [StudentScores] SELECT 'Nick', '生物', 85INSERT INTO [StudentScores] SELECT 'Kent', '语文', 80INSERT INTO [StudentScores] SELECT 'Kent', '数学', 90INSERT INTO [StudentScores] SELECT 'Kent', '英语', 70INSERT 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, 1INSERT INTO Inpours SELECT '张三', '2010-06-14', '支付宝', 50, 1INSERT INTO Inpours SELECT '张三', '2010-06-14', '手机短信', 100, 1INSERT INTO Inpours SELECT '李四', '2010-06-14', '手机短信', 100, 1INSERT INTO Inpours SELECT '李四', '2010-07-14', '支付宝', 100, 1INSERT INTO Inpours SELECT '王五', '2010-07-14', '工商银行卡', 100, 1INSERT 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) TGROUP BY CreateTime  其实行转列,关键是要理清逻辑,而且对分组(Group by)概念比较清晰。上面两个列子基本上就是行转列的类型了。但是有个问题来了,上面是我为了说明弄的一个简单列子。实际中,可能支付方式特别多,而且逻辑也复杂很多,可能涉及汇率、手续费等等(曾经做个这样一个),如果支付方式特别多,我们的CASE WHEN 会弄出一大堆,确实比较恼火,而且新增一种支付方式,我们还得修改脚本如果把上面的脚本用动态SQL改写一下,我们就能轻松解决这个问题

  下面是通过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




运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-623992-1-1.html 上篇帖子: Spark SQL数据加载和保存实战 下篇帖子: 动态SQL应用小列子
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表