zoukankan      html  css  js  c++  java
  • 数据库 一步步处理 行列转换

    做个东西,发现了些问题,又解决了,很Happy!

    数据库有2个表:

    [省份表],2列(provinceId--省的id,provinceName--省名字),

    [公司表],好几列(companyId--公司Id, provinceId--公司所在省id, recruitType--这个招聘会类型(只有1,2,3这三个值,每个值一个含义),......其他问题无关列)

    ------------------------------------------------------------------------------------------------

    方法一:(自己写的)

    ------------------------------------------------------------------------------------------------

    我想获得的结果,即每个省按那三个招聘会类型统计各种类型公司的总数,如下:

    ------------------------------------------------------------------------------------------------

    先获得了下面表,省id,招聘会类型,公司总数,这是在[公司表]里面按(省id,招聘会类型)group by和count()得到的,注意这里有个问题,就是[公司表]里面不是什么省份的都有,所以provinceId是不连续的,所以要在后面处理.

    ------------------------------------------------------------------------------------------------

    然后联合[省份表],得到下面的表,这个很容易,只要注意一下 left join,就可以显示所有的省份了

    ------------------------------------------------------------------------------------------------

    现在问题来了,我想显示的目标图是第一个图,就是这么把那些相同省的行合并为同一行,并且有三个列分别显示那三种招聘会类型的公司总数?

    我想了半天,因为最终是要在Web上显示的,我想要不就在数据库搞好,把结果直接返回,Web端就不要处理什么了,要不就这样返回吧,在Web端合并相同的行,

    但最后发现还是数据库端好处理,因为T-SQL很强大,并且Web端处理这些表格很麻烦,我不知道它那些DataTable啊什么的类似怎么样构造的,遂选择在数据库端处理.

    ------------------------------------------------------------------------------------------------

    1,建立最后想要结果的表,把目标列写好,然后要不insert,要不update

    2,用游标一个一个处理上面的那个表,provinceId一样的就update那一行几个值,否则insert新行

    3,最后处理一些数据,得到想要结果:

    代码量大啊!!!主要用游标一行一行处理的......

    USE [Statistics]
    GO
    /****** Object:  StoredProcedure [dbo].[procAnalyseProvince]    Script Date: 2013/12/11 16:22:25 ******/
    SET ANSI_NULLS ON
    GO
    SET QUOTED_IDENTIFIER ON
    GO
    -- =============================================
    -- Author:        <Author,,Name>
    -- Create date: <Create Date,,>
    -- Description:    按招聘单位地域统计的数据源
    -- =============================================
    ALTER PROCEDURE [dbo].[procAnalyseProvince]
    @timeFrom char(10),
    @timeTo char(20)
    AS
    BEGIN
    
    /*下面是第一部分,获得临时表: [provinceId,recruitType,companySum] ,注意这里的provinceId可能只有一部分,即来招聘的那部分*/
    begin
        if OBJECT_ID(N'#tableTemp',N'U') is not null
        drop table #tableTemp
    end
    
    select [Companys].provinceId , [Companys].recruitType ,COUNT(*) as companySum
    into #tableTemp from [Companys]   --到临时表-----------------------------表的列:[provinceId,recruitType,companySum]
    where [Companys].recruitTime between @timeFrom and @timeTo
    group by [Companys].provinceId,[Companys].recruitType  --根据省份和招聘会类型分组,不管哪个是哪个团体
    order by [Companys].provinceId
    
    /*下面也是一部分,获得临时表: [provinceId,provinceName,recruitType,companySum] ,注意这里的provinceId包含数据库中全部省*/
    begin
        if OBJECT_ID(N'#newTable',N'U') is not null
        drop table #newTable
    end
    
    select [Provinces].provinceId,[Provinces].provinceName,#tableTemp.recruitType,#tableTemp.companySum
    into #newTable  --到临时表----------------------------------表的列:[provinceId,provinceName,recruitType,companySum]
    from [Provinces] left join #tableTemp on [Provinces].provinceId=#tableTemp.provinceId
    order by [Provinces].provinceId ASC, #tableTemp.recruitType ASC
    
    /*下面作处理,先创建最后结果表:*/
    begin
        if OBJECT_ID(N'#lastTable',N'U') is not null
        drop table #lastTable
    end
    
    create table #lastTable   --创建表最终要返回应用程序的表!!!!注意这里的强大之处!!!
    (
    provinceId int primary key,
    provinceName nvarchar(20) not null,
    type1Sum int default 0,  --默认值为0,省去null处理的麻烦
    type1Ratio varchar(10),
    type2Sum int default 0,
    type2Ratio varchar(10),
    type3Sum int default 0,
    type3Ratio varchar(10),
    allTypeSum int default 0,
    allTypeRatio varchar(10)
    )
    
    /*下面用游标处理表,第一次处理,只是在最终表填了[provinceId,provinceName,type1Sum,type2Sum,type3Sum]*/
    begin  --这里定义一些变量
    declare @preProvinceId int --上一个省的id
    set @preProvinceId=0  --初始为0
    declare @provinceId int,@provinceName nvarchar(20),@recruitType int,@companySum int  --定义一个游标所对应的一行值
    end
    
    declare curToNewTable scroll cursor for select * from #newTable
    open curToNewTable
    fetch first from curToNewTable into @provinceId,@provinceName,@recruitType,@companySum  --取第一条
    
    while @@FETCH_STATUS=0  --取成功了
    begin
      if(@provinceId!=@preProvinceId)  --跟上一次的不一样,所以要插入新行
        begin
          set @preProvinceId=@provinceId  --先设置这个
          if(@recruitType is null) --注意,这里因为之前的原因,@recruitType可能为null,这样这一行只录入省
            insert into #lastTable (provinceId,provinceName) values (@provinceId,@provinceName)
          else if(@recruitType=1)
            insert into #lastTable (provinceId,provinceName,type1Sum) values(@provinceId,@provinceName,@companySum)
          else if(@recruitType=2)
            insert into #lastTable (provinceId,provinceName,type2Sum) values(@provinceId,@provinceName,@companySum)
          else  --3,@recruitType只有null,1,2,3,这四种值
            insert into #lastTable (provinceId,provinceName,type3Sum) values(@provinceId,@provinceName,@companySum)
        end
      else  --一样,就update那一行
        begin
          if(@recruitType=1)  --这里就不要管@recruitType是null了,那不处理
            update #lastTable set #lastTable.type1Sum=@companySum where #lastTable.provinceId=@provinceId
          else if(@recruitType=2)
            update #lastTable set #lastTable.type2Sum=@companySum where #lastTable.provinceId=@provinceId
          else if(@recruitType=3)
            update #lastTable set #lastTable.type3Sum=@companySum where #lastTable.provinceId=@provinceId
        end
      fetch next from curToNewTable into @provinceId,@provinceName,@recruitType,@companySum  --取下一条
    end
    close curToNewTable  --关闭游标
    deallocate curToNewTable  --释放游标资源
    
    /*下面用游标对最后结果表作第二次处理,即把表的全部项填满除了总计的比例项*/
    declare @allSingleTotal int,@allMediumTotal int,@allBigTotal int,@allTypeTotal int --整体的,这几个值是放到最后一行"总计"的
    set @allSingleTotal=(select sum(type1Sum) from #lastTable)
    set @allMediumTotal=(select sum(type2Sum) from #lastTable)
    set @allBigTotal=(select sum(type3Sum) from #lastTable)
    set @allTypeTotal=@allSingleTotal+@allMediumTotal+@allBigTotal  --其实这个值也为这一列的所有值的和
    
    declare @lineType1Sum int,@lineType2Sum int,@lineType3Sum int,@lineTypeAllSum int--一行的
    
    declare curToFillRatio scroll cursor for select type1Sum,type2Sum,type3Sum from #lastTable  --游标
    open curToFillRatio
    
    fetch first from curToFillRatio into @lineType1Sum,@lineType2Sum,@lineType3Sum
    
    while(@@FETCH_STATUS=0)
    begin
      set @lineTypeAllSum=@lineType1Sum+@lineType2Sum+@lineType3Sum  --三者之和,这个值也是待填上的
      
      /*下面获得一行的4个比例的值,4位数,3位小数的形式*/
      declare @decml1 decimal(4,3),@decml2 decimal(4,3),@decml3 decimal(4,3),@decml4 decimal(4,3)   --小数,共4位,小数点后有3位(就1.000 0.223 0.022 0.003这几种情况)
      
      if(@allSingleTotal=0)  set @decml1=0  --每行的专场比例
      else  set @decml1=@lineType1Sum*1.0/@allSingleTotal  --注意*1.0
      
      if(@allMediumTotal=0)  set @decml2=0  --每行的中型比例
      else  set @decml2=@lineType2Sum*1.0/@allMediumTotal  --注意*1.0
        
      if(@allBigTotal=0)  set @decml3=0  --大型
      else  set @decml3=@lineType3Sum*1.0/@allBigTotal
    
      if(@allTypeTotal=0)  set @decml4=0  --总计
      else  set @decml4=@lineTypeAllSum*1.0/@allTypeTotal
    
      /*下面对小数进行处理,得到百分比形式的除了百分号的值*/
      declare @temp1 decimal(4,1),@temp2 decimal(4,1),@temp3 decimal(4,1),@temp4 decimal(4,1)--  --转化成共四位,一位小数的百分比前面的数值,即 100.0  40.3  5.4  0.4
      set @temp1=CAST(100*@decml1 as decimal(4,1))
      set @temp2=CAST(100*@decml2 as decimal(4,1))
      set @temp3=CAST(100*@decml3 as decimal(4,1))
      set @temp4=CAST(100*@decml4 as decimal(4,1))
    
      /*好了,可以更新了*/
      update #lastTable set type1Ratio=CAST(@temp1 as varchar(10))+'%',
                            type2Ratio=CAST(@temp2 as varchar(10))+'%',
                            type3Ratio=CAST(@temp3 as varchar(10))+'%',
                            allTypeSum=@lineTypeAllSum,
                            allTypeRatio=CAST(@temp4 as varchar(10))+'%'
                        where current of curToFillRatio
      fetch next from curToFillRatio into @lineType1Sum,@lineType2Sum,@lineType3Sum
    end
    close curToFillRatio
    deallocate curToFillRatio
    
    /*插入总计的行*/
    insert into #lastTable values(40, '总计',@allSingleTotal,'100.0%',
                                              @allMediumTotal,'100.0%',
                                              @allBigTotal,'100.0%',
                                              @allTypeTotal,'100.0%')
    
    /*最后只获取一个结果表*/
    select * from
    (
      select top 100 * from #lastTable where provinceId!=40 order by allTypeSum desc  --注意这里必须要top这样的语句
    )T
    union all
    select * from #lastTable where provinceId=40
    
    END
    我的一步步处理代码

    ------------------------------------------------------------------------------------------------

    方法二:(得到的答案)

    ------------------------------------------------------------------------------------------------

    这里的主要问题就是把有那个有多行省Id是一样的合并到一行中去,添加了列

    神伤中......居然可以这么简单......

    select provinceId,provinceName
    ,sum(case recruitType when 1 then companySum end) as myType1Sum
    ,sum(case recruitType when 2 then companySum end) as myType2Sum
    ,sum(case recruitType when 3 then companySum end) as myType3Sum
     from #newTable
     group by provinceId,provinceName

    分2步来理解这个,首先看这个:

    select provinceId,provinceName
    ,case recruitType when 1 then companySum end as typ1Sum
    ,case recruitType when 2 then companySum end as typ2Sum
    ,case recruitType when 3 then companySum end as typ3Sum
    from #newTable

    结果是:

    我想可以这样理解:select语句其实也是从头到尾一行行处理的,上面的select语句处理到每一行,选出省id,省名字,case测试此行的recruitType,若是1,then取此行[companySum]列的值当做[typ1Sum]列的值,若不是1的话则[typ1Sum]列的值就成了默认的null.

    其他2个case语句同理.于是就得到了另外的三个列,并且只是在对应的列填了值,行还是一样的.

    然后就根据这个中间表group by,再sum(),注意sum()里面的是要sum的列名,sum(case recruitType when 1 then companySum end) 的意思应该是按照(case recruitType when 1 then companySum end)列进行sum,因为sql语句case recruitType when 3 then companySum end as typ3Sum若没有as语句则显示为没指定列名的列

    ------------------------------------------------------------------------------------------------

    附(group by说明)

    1,值相等的为一组

    2,聚合函数作用于每一组

    3,显示的列只能为聚合函数产生或分组依据的列

    附(case表达式)

    语法格式:

    CASE 测试表达式

       When 简单表达式1 then 结果表达式1

       When 简单表达式2 then 结果表达式2 ……..

       When 简单表达式n then 结果表达式n

       [else 结果表达式n+1]

    END

    ------------------------------------------------------------------------------------------------

    方法三:(得到的答案)

    ------------------------------------------------------------------------------------------------

    sql server 提供了一个行列转换的东西 Pivot

    pivot的介绍(引用):http://hi.baidu.com/guoxiaoming/item/62db410f4bc8143af3eafccd

    select * from #newTable
    
    select *   -- 第三步,呈现结果
    from #newTable  --第一步 原始查询,这里已有表
    pivot  -- 第二步 定义行转列
    (
    max(companySum)
    for recruitType 
    in ([1],[2],[3])
    )
    as t
    order by provinceId

    结果如下,本来的表为第一个,第二个为转换的结果表

    pivot函数不怎么理解,不做过多说明

    ------------------------------------------------------------------------------------------------

    总结

    ------------------------------------------------------------------------------------------------

    1.数据库需要得到的结果比较麻烦时,一步步来,顶多多用几个临时表

    2.数据库很强大!T-SQL结合了SQL的强大的集合操纵能力和一般编程语言如C的流程控制能力,数据还是尽量获得较完整形式再传给应用程序吧,应用程序端处理不是特别方便

    3.sql server 游标的使用

    4.类似decimal(4,1)的decimal类型数据的使用;CAST(100*@decml2 as decimal(4,1))中的cast()类型转换函数的使用

    5.select case recruitType when 2 then companySum end as typ2Sum 这种比较奇怪的select语句,增加了列

    6.行列转化的pivot函数的使用

    ------------------------------------------------------------------------------------------------

    因为之前不会,遂在网上提问,得到了回答,其实里面有很多可以学到的知识

    我的问题http://q.cnblogs.com/q/57720/

  • 相关阅读:
    Visual Studio 中的 .NET Framework 类库
    泛型
    泛型
    事件
    基于事件的异步模式
    使用委托进行异步编程
    使用 IAsyncResult 调用异步方法
    异步编程设计模式
    演练:使用VS2010 C# 创作简单的多线程组件
    [转][MEF插件式开发] 一个简单的例子
  • 原文地址:https://www.cnblogs.com/jiayith/p/3465007.html
Copyright © 2011-2022 走看看