博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
数据库表的转置
阅读量:5122 次
发布时间:2019-06-13

本文共 4557 字,大约阅读时间需要 15 分钟。

问题

在工作中会遇到这样的问题。

一个学生有多科成绩,每科成绩记录在表中为一条记录,那么查询出来之后,要把一个学生所有科目的成绩打印成一行,即把表中的多条记录合成一条记录。

在销售管理中,每个月的销售数字在表中表示为一条记录,在统计的时候,需要按照产品,在一行中输出所有月份的销售统计。

当然对于一些比较死板的人来说可以说那我就做多个字段来记录就是了,但是这样的扩展性就很差了。如果科目变了呢,你不得改表结构吗?

准备数据

下面我们以销售为例,首先创建如下的表。

create table Orders(    ProductID  int,    OrderMonth int,    SubTotal    money)

 

表中的每一行表示一个产品每月的销售情况。

然后,插入若干数据。

insert into Orders ( ProductID, OrderMonth, SubTotal )select 1, 5,  100.00  union allselect 1, 6,  100.00  union allselect 2, 5,  200.00  union allselect 2, 6,  200.00  union allselect 2, 7,  300.00  union allselect 3, 5,  400.00  union allselect 3, 5,  400.00

 此时,表中的数据如下所示。

现在,我们需要统计每种产品在每个月的销售情况。

SQL Server2005 中的方式

使用在 SQL Server2005 中提供的 Pivot 进行转置。

SELECT ProductID, [5] AS 五月, [6] AS 六月, [7] AS 七月FROMOrders PIVOT(SUM (Orders.SubTotal)FOR Orders.OrderMonth IN( [5], [6], [7] )) AS pvt ORDER BY ProductID;

 

Orders PIVOT 表示对表 Orders 进行转置操作,这个表称为输入表。

Orders.OrderMonth 称为透视列(pivot_column),FOR Orders.OrderMonth IN ( [5], [6], [7] ) 表示针对表中 OrderMonth 为 5,6,7  的月进行分组之后转置。

SUM (Orders.SubTotal) 表示针对每组的 SubTotal 进行分组求和。SubTotal 列称为值列。

SELECT ProductID, [5] AS 五月, [6] AS 六月, [7] AS 七月 语句中的 ProductId 与 OrderMonth 组合在一起完成分组,后面的 [5] AS 五月, [6] AS 六月, [7] AS 七月 分别对应 FOR Orders.OrderMonth IN ( [5], [6], [7] ) 中的  [5], [6], [7] 。

Pivot 实际上按照以下的步骤完成操作:

1. 分组求和,先按照  ProductID,OrderMonth 进行分组求和

SELECT ProductID, OrderMonth, SUM (Orders.SubTotal) AS SumSubTotalFROM OrdersGROUP BY ProductID,OrderMonth;

 

得到的结果如下:

2. PIVOT根据FOR OrderMonth IN指定的值5、6、7,首先在结果集中建立名为5、6、7的列,然后从上面的结果中取出 OrderMonth 列中取出相符合的值,分别放置到5、6、7的列中。此时得到的结果集的别名为pvt(见语句中AS pvt的指定)。结果集的内容如下所示。

3.  最后根据SELECT ProductID, [5] AS 五月, [6] AS 六月, [7] AS 七月 FROM的指定,从别名pvt结果集中检索数据,并分别将名为5、6、7的列在最终结果集中重新命名为五月、六月、七月。这里需要注意的是FROM的含 义,其表示从经PIVOT关系运算符得到的pvt结果集中检索数据,而不是从Sales.Orders中检索数据。

Pivot 的语法如下:

SELECT 
<非透视的列>
, [第一个透视的列] AS
<列名称>
, [第二个透视的列] AS
<列名称>
, ... [最后一个透视的列] AS
<列名称>
,FROM (
<生成数据的 select 查询>
) AS
<源查询的别名>
PIVOT (
<聚合函数>
(
<要聚合的列>
)FOR [
<包含要成为列标题的值的列>
] IN ( [第一个透视的列], [第二个透视的列], ... [最后一个透视的列])) AS
<透视表的别名>
<可选的 order by 子句>
;

 

需要注意的是:如果聚合函数与 PIVOT 一起使用,则计算聚合时将不考虑出现在值列中的任何空值。

SQL Server 2000 中的方式

在 SQL Server 2005 之前,通常需要通过 case 子句来处理。

select ProductID,     sum( case when OrderMonth = 5 then SubTotal end ) as 五月,     sum( case when OrderMonth = 6 then SubTotal end ) as 六月,     sum( case when OrderMonth = 7 then SubTotal end ) as 七月from Ordersgroup by ProductID

 

补充说明:

今天在使用 pivot 的时候,出现一个奇怪的事情,转置居然失败了!

表的结构如下:

create table  tbl_marks(    markId  int  identity(1,1) ,  -- 成绩的关键字    sid int   ,                    -- 学生的标识    cid int ,                      -- 课程的标识     mark    int,             -- 成绩)

 表中的数据为:

insert into tbl_marks ( sid, cid, mark ) values ( 1, 1, 100 );insert into tbl_marks ( sid, cid, mark ) values ( 1, 2, 90 );insert into tbl_marks ( sid, cid, mark ) values ( 1, 3, 95 );insert into tbl_marks ( sid, cid, mark ) values ( 2, 1, 60 );insert into tbl_marks ( sid, cid, mark ) values ( 2, 2, 61 );insert into tbl_marks ( sid, cid, mark ) values ( 2, 3, 99 );

 执行的转置语句如下:

select  sid, [1] as [database], [2] as [CSharp], [3] as [Xml]from   tbl_marks pivot(    sum( mark )    for cid in ( [1], [2], [3] )) as pvt

 

注意,最后的 as pvt 提供的别名必须要有,虽然没有实际的用途,但是 SQLServer 2005 要求必须提供。

结果如下:

1   100 NULL    NULL1   NULL    90  NULL1   NULL    NULL    952   60  NULL    NULL2   NULL    61  NULL2   NULL    NULL    99

 

应该只有两行的结果,居然是六行,显然没有进行分组!

经过分析,发现在 pivot 中,分组的依据是隐含的,pivot 将对表中除了组函数计算列和转置的列之外所有的字段进行分组,在上边的情况下,组函数对 mark 进行求值,对课程列 cid 进行转置,而表中实际有 4 列,剩下了 markit 和 sid 两列,而 markit 是一个自增长的标识列,所有的行都不相同,这样,实际上的分组并不是在 sid 一个字段上进行的,导致了错误的转置结果。

处理的方法是将表中的列限制在 3 列,排除掉 markit 这个标识列,通过子查询可以轻松处理这个问题。

select  sid, [1] as [database], [2] as [CSharp], [3] as [Xml]from  ( select sid, cid, mark from  tbl_marks) t pivot(    sum( mark )    for cid in ( [1], [2], [3] )) as pvt

 

注意,在 SQLServer 中子查询需要提供一个别名,虽然还是没有什么用途。  

如果使用 case 的话,上边的转置还可以这样写。

select [sid],     max( case  when  cid = 1    then   mark end ) as [database],    min( case  when  cid = 2 then mark end ) as cSharp ,    -- 仅仅对课程编号是 2 的课程成绩进行求和,其实课程编号为 2 的成绩仅仅出现了一次                                                -- 所以,实际上返回的就是课程 2 的成绩        avg( case  when cid  = 3 then mark end ) as xml,        sum( mark ) as [总分]from tbl_marks group by [sid]

 

在这个例子中,组函数不仅可以使用 sum, 其实使用 max, min, avg 都可以,你知道为什么吗?  

  

 

参考文献

详细的 case 使用说明可以参考 钱途无梁 的 sql 中 case when 语法

本文主要参考一下文章:

张洪举的文章:在SQL Server 2005中实现表的行列转换

MSDN: 使用 PIVOT 和 UNPIVOT

Sman Sky :表中数据转置(Pivot)在Sql Server 2000 和Sql Server 2005 的实现

 

 

转载于:https://www.cnblogs.com/xiaopohou/archive/2011/12/01/2269945.html

你可能感兴趣的文章
慵懒中长大的人,只会挨生活留下的耳光
查看>>
"远程桌面连接--“发生身份验证错误。要求的函数不受支持
查看>>
【BZOJ1565】 植物大战僵尸
查看>>
VALSE2019总结(4)-主题报告
查看>>
浅谈 unix, linux, ios, android 区别和联系
查看>>
51nod 1428 活动安排问题 (贪心+优先队列)
查看>>
中国烧鹅系列:利用烧鹅自动执行SD卡上的自定义程序(含视频)
查看>>
Solaris11修改主机名
查看>>
latex for wordpress(一)
查看>>
如何在maven工程中加载oracle驱动
查看>>
Flask 系列之 SQLAlchemy
查看>>
aboutMe
查看>>
【Debug】IAR在线调试时报错,Warning: Stack pointer is setup to incorrect alignmentStack,芯片使用STM32F103ZET6...
查看>>
一句话说清分布式锁,进程锁,线程锁
查看>>
python常用函数
查看>>
FastDFS使用
查看>>
服务器解析请求的基本原理
查看>>
[HDU3683 Gomoku]
查看>>
【工具相关】iOS-Reveal的使用
查看>>
数据库3
查看>>