zoukankan      html  css  js  c++  java
  • 数据库表的转置

    问题

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

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

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

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

    准备数据

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

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

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

    然后,插入若干数据。

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

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

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

    SQL Server2005 中的方式

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

    SELECT ProductID, [5] AS 五月, [6] AS 六月, [7] AS 七月
    FROM 
    Orders 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 SumSubTotal
    FROM Orders
    GROUP 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 Orders
    group 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	NULL
    1	NULL	90	NULL
    1	NULL	NULL	95
    2	60	NULL	NULL
    2	NULL	61	NULL
    2	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 语法

    http://www.cnblogs.com/qiantuwuliang/archive/2009/06/03/1495770.html

    本文主要参考一下文章:

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

    http://blog.csdn.net/zhanghongju/archive/2006/06/02/769445.aspx

    MSDN: 使用 PIVOT 和 UNPIVOT

    http://technet.microsoft.com/zh-cn/library/ms177410.aspx

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

    http://www.cnblogs.com/huangbaixun/archive/2008/07/26/1252002.html

  • 相关阅读:
    PHP 安装配置
    网络,进程 相关指令
    大数据之路:阿里巴巴大数据实践PDF下载
    计算机系统概论PDF下载
    计算机组成与体系结构性能设计PDF下载
    简明python教程PDF下载
    软件工程-实践者的研究方法PDF下载
    软件测试PDF下载
    设计模式解析PDF下载
    设计原本pdf下载
  • 原文地址:https://www.cnblogs.com/haogj/p/2010349.html
Copyright © 2011-2022 走看看