SQL 行转列和列转行

2024-08-27 15:08
文章标签 sql database 转列 转行

本文主要是介绍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', '语文', 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)ASCreateTime,CASEPayTypeWHEN'支付宝'    THENSUM(Money)ELSE0ENDAS'支付宝',CASEPayTypeWHEN'手机短信'    THENSUM(Money)ELSE0ENDAS'手机短信',CASEPayTypeWHEN'工商银行卡'  THENSUM(Money)ELSE0ENDAS'工商银行卡',CASEPayTypeWHEN'建设银行卡'  THENSUM(Money)ELSE0ENDAS'建设银行卡'FROMInpoursGROUPBYCreateTime,PayType


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

 

SELECTCreateTime,ISNULL(SUM([支付宝])    ,0)  AS[支付宝]    ,ISNULL(SUM([手机短信])  ,0)  AS[手机短信]  ,ISNULL(SUM([工商银行卡]),0)  AS[工商银行卡],  ISNULL(SUM([建设银行卡]),0)  AS[建设银行卡]
FROM
(SELECTCONVERT(VARCHAR(10),CreateTime,120)ASCreateTime,CASEPayTypeWHEN'支付宝'    THENSUM(Money)ELSE0ENDAS'支付宝',CASEPayTypeWHEN'手机短信'  THENSUM(Money)ELSE0ENDAS'手机短信',CASEPayTypeWHEN'工商银行卡'THENSUM(Money)ELSE0ENDAS'工商银行卡',CASEPayTypeWHEN'建设银行卡'THENSUM(Money)ELSE0ENDAS'建设银行卡'FROMInpoursGROUPBYCreateTime,PayType
)T
GROUPBYCreateTime

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

代码CodehighlightingproducedbyActiproCodeHighlighter(freeware)http://www.CodeHighlighter.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(SELECTDISTINCTPayTypeFROMInpours)TSET@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  (SELECTDISTINCTPayTypeFROMInpours)TSET@tmpSql=LEFT(@tmpSql,LEN(@tmpSql)-2)+' FROM ('+CHAR(10);SET@cmdText=@tmpSql+@cmdText+') T GROUP BY CreateTime ';PRINT@cmdTextEXECUTE(@cmdText);


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

SELECT  CreateTime, [支付宝] , [手机短信],[工商银行卡] , [建设银行卡]FROM(SELECT CONVERT(VARCHAR(10), CreateTime, 120) AS CreateTime,PayType, MoneyFROM Inpours) PPIVOT (SUM(Money)FOR PayType IN([支付宝], [手机短信], [工商银行卡], [建设银行卡])) AS TORDER 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 ProgrectDetailSELECT 'A', 100, 200, 50, 50UNION ALLSELECT 'B', 200, 300, 150, 150UNION ALLSELECT 'C', 159, 400, 20, 320UNION ALLSELECT 'D', 250, 30, 15, 15


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

SELECT ProgrectName, 'OverseaSupply' AS Supplier,MAX(OverseaSupply) AS 'SupplyNum'FROM ProgrectDetailGROUP BY ProgrectNameUNION ALLSELECT ProgrectName, 'NativeSupply' AS Supplier,MAX(NativeSupply) AS 'SupplyNum'FROM ProgrectDetailGROUP BY ProgrectNameUNION ALLSELECT ProgrectName, 'SouthSupply' AS Supplier,MAX(SouthSupply) AS 'SupplyNum'FROM ProgrectDetailGROUP BY ProgrectNameUNION ALLSELECT ProgrectName, 'NorthSupply' AS Supplier,MAX(NorthSupply) AS 'SupplyNum'FROM ProgrectDetailGROUP BY ProgrectName


 

用UNPIVOT 实现如下:

SELECT ProgrectName,Supplier,SupplyNumFROM  
(SELECT ProgrectName, OverseaSupply, NativeSupply,SouthSupply, NorthSupplyFROM ProgrectDetail)TUNPIVOT  
(SupplyNum FOR Supplier IN(OverseaSupply, NativeSupply, SouthSupply, NorthSupply )) P


这篇关于SQL 行转列和列转行的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1112016

相关文章

Mysql中InnoDB与MyISAM索引差异详解(最新整理)

《Mysql中InnoDB与MyISAM索引差异详解(最新整理)》InnoDB和MyISAM在索引实现和特性上有差异,包括聚集索引、非聚集索引、事务支持、并发控制、覆盖索引、主键约束、外键支持和物理存... 目录1. 索引类型与数据存储方式InnoDBMyISAM2. 事务与并发控制InnoDBMyISAM

MySQL 日期时间格式化函数 DATE_FORMAT() 的使用示例详解

《MySQL日期时间格式化函数DATE_FORMAT()的使用示例详解》`DATE_FORMAT()`是MySQL中用于格式化日期时间的函数,本文详细介绍了其语法、格式化字符串的含义以及常见日期... 目录一、DATE_FORMAT()语法二、格式化字符串详解三、常见日期时间格式组合四、业务场景五、总结一、

mysql线上查询之前要性能调优的技巧及示例

《mysql线上查询之前要性能调优的技巧及示例》文章介绍了查询优化的几种方法,包括使用索引、避免不必要的列和行、有效的JOIN策略、子查询和派生表的优化、查询提示和优化器提示等,这些方法可以帮助提高数... 目录避免不必要的列和行使用有效的JOIN策略使用子查询和派生表时要小心使用查询提示和优化器提示其他常

grom设置全局日志实现执行并打印sql语句

《grom设置全局日志实现执行并打印sql语句》本文主要介绍了grom设置全局日志实现执行并打印sql语句,包括设置日志级别、实现自定义Logger接口以及如何使用GORM的默认logger,通过这些... 目录gorm中的自定义日志gorm中日志的其他操作日志级别Debug自定义 Loggergorm中的

MySQL InnoDB引擎ibdata文件损坏/删除后使用frm和ibd文件恢复数据

《MySQLInnoDB引擎ibdata文件损坏/删除后使用frm和ibd文件恢复数据》mysql的ibdata文件被误删、被恶意修改,没有从库和备份数据的情况下的数据恢复,不能保证数据库所有表数据... 参考:mysql Innodb表空间卸载、迁移、装载的使用方法注意!此方法只适用于innodb_fi

mysql通过frm和ibd文件恢复表_mysql5.7根据.frm和.ibd文件恢复表结构和数据

《mysql通过frm和ibd文件恢复表_mysql5.7根据.frm和.ibd文件恢复表结构和数据》文章主要介绍了如何从.frm和.ibd文件恢复MySQLInnoDB表结构和数据,需要的朋友可以参... 目录一、恢复表结构二、恢复表数据补充方法一、恢复表结构(从 .frm 文件)方法 1:使用 mysq

mysql8.0无备份通过idb文件恢复数据的方法、idb文件修复和tablespace id不一致处理

《mysql8.0无备份通过idb文件恢复数据的方法、idb文件修复和tablespaceid不一致处理》文章描述了公司服务器断电后数据库故障的过程,作者通过查看错误日志、重新初始化数据目录、恢复备... 周末突然接到一位一年多没联系的妹妹打来电话,“刘哥,快来救救我”,我脑海瞬间冒出妙瓦底,电信火苲马扁.

MySQL进阶之路索引失效的11种情况详析

《MySQL进阶之路索引失效的11种情况详析》:本文主要介绍MySQL查询优化中的11种常见情况,包括索引的使用和优化策略,通过这些策略,开发者可以显著提升查询性能,需要的朋友可以参考下... 目录前言图示1. 使用不等式操作符(!=, <, >)2. 使用 OR 连接多个条件3. 对索引字段进行计算操作4

MySQL表锁、页面锁和行锁的作用及其优缺点对比分析

《MySQL表锁、页面锁和行锁的作用及其优缺点对比分析》MySQL中的表锁、页面锁和行锁各有特点,适用于不同的场景,表锁锁定整个表,适用于批量操作和MyISAM存储引擎,页面锁锁定数据页,适用于旧版本... 目录1. 表锁(Table Lock)2. 页面锁(Page Lock)3. 行锁(Row Lock

MySQL zip安装包配置教程

《MySQLzip安装包配置教程》这篇文章详细介绍了如何使用zip安装包在Windows11上安装MySQL8.0,包括下载、解压、配置环境变量、初始化数据库、安装服务以及更改密码等步骤,感兴趣的朋... 目录mysql zip安装包配置教程1、下载zip安装包:2、安装2.1 解压zip包到安装目录2.2