做个东西,发现了些问题,又解决了,很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函数的使用
------------------------------------------------------------------------------------------------
因为之前不会,遂在网上提问,得到了回答,其实里面有很多可以学到的知识