一个简单的SQL 行列转换
Author: eaglet
    在数据库开发中经常会遇到行列转换的问题,比如下面的问题,部门,员工和员工类型三张表,我们要统计类似这样的列表
    部门编号 部门名称  合计  正式员工 临时员工 辞退员工
    1           A           30    20        10          1
    这种问题咋一看摸不着头绪,不过把思路理顺后再看,本质就是一个行列转换的问题。下面我结合这个简单的例子来实现行列转换。

    下面3张表
    
一个简单的SQL 行列转换if exists (select * from sysobjects where id = object_id('EmployeeType'and type = 'u')
一个简单的SQL 行列转换    
drop table EmployeeType
一个简单的SQL 行列转换
GO
一个简单的SQL 行列转换
if exists (select * from sysobjects where id = object_id('Employee'and type = 'u')
一个简单的SQL 行列转换    
drop table Employee
一个简单的SQL 行列转换
GO
一个简单的SQL 行列转换
if exists (select * from sysobjects where id = object_id('Department'and type = 'u')
一个简单的SQL 行列转换    
drop table Department
一个简单的SQL 行列转换
GO
一个简单的SQL 行列转换
一个简单的SQL 行列转换
create table Department 
一个简单的SQL 行列转换(
一个简单的SQL 行列转换  Id         
int primary key
一个简单的SQL 行列转换  Department 
varchar(10)
一个简单的SQL 行列转换)
一个简单的SQL 行列转换
一个简单的SQL 行列转换
create table Employee 
一个简单的SQL 行列转换(
一个简单的SQL 行列转换  EmployeeId     
int primary key
一个简单的SQL 行列转换  DepartmentId   
int Foreign Key (DepartmentId) References Department(Id) , --DepartmentId ,
一个简单的SQL 行列转换
  EmployeeName   varchar(10)
一个简单的SQL 行列转换)
一个简单的SQL 行列转换
一个简单的SQL 行列转换
create table EmployeeType
一个简单的SQL 行列转换(
一个简单的SQL 行列转换  EmployeeId         
int Foreign Key (EmployeeId) References Employee(EmployeeId) , --EmployeeId ,
一个简单的SQL 行列转换
  EmployeeType       varchar(10)
一个简单的SQL 行列转换)

描述部门,员工和员工类型之间的关系。
插入测试数据
一个简单的SQL 行列转换insert Department values (1'A');
一个简单的SQL 行列转换
insert Department values (2'B');
一个简单的SQL 行列转换
一个简单的SQL 行列转换
insert Employee values (11'Bob');
一个简单的SQL 行列转换
insert Employee values (21'John');
一个简单的SQL 行列转换
insert Employee values (31'May');
一个简单的SQL 行列转换
insert Employee values (42'Tom');
一个简单的SQL 行列转换
insert Employee values (52'Mark');
一个简单的SQL 行列转换
insert Employee values (62'Ken');
一个简单的SQL 行列转换
一个简单的SQL 行列转换
insert EmployeeType values (1,  '正式');
一个简单的SQL 行列转换
insert EmployeeType values (2,  '临时');
一个简单的SQL 行列转换
insert EmployeeType values (3,  '正式');
一个简单的SQL 行列转换
insert EmployeeType values (4,  '正式');
一个简单的SQL 行列转换
insert EmployeeType values (5,  '辞退');
一个简单的SQL 行列转换
insert EmployeeType values (6,  '正式');

看一下部门、员工和员工类型的列表
Department EmployeeName EmployeeType
---------- ------------ ------------
A          Bob          正式
A          John         临时
A          May          正式
B          Tom          正式
B          Mark         辞退
B          Ken          正式

现在我们需要输出这样一个列表
部门编号 部门名称  合计  正式员工 临时员工 辞退员工

这个问题我的思路是首先统计每个部门的员工类型总数
这个比较简单,我把它做成一个视图
一个简单的SQL 行列转换if exists (select * from sysobjects where id = object_id('VDepartmentEmployeeType'and type = 'v')
一个简单的SQL 行列转换    
drop view VDepartmentEmployeeType
一个简单的SQL 行列转换
GO
一个简单的SQL 行列转换
create view VDepartmentEmployeeType
一个简单的SQL 行列转换
as
一个简单的SQL 行列转换
select Department.Id, Department.Department, EmployeeType.EmployeeType, count(EmployeeType.EmployeeType) Cnt 
一个简单的SQL 行列转换
from Department, Employee, EmployeeType where
一个简单的SQL 行列转换Department.Id 
= Employee.DepartmentId and Employee.EmployeeId = EmployeeType.EmployeeId
一个简单的SQL 行列转换
group by Department.Id, Department.Department, EmployeeType.EmployeeType
一个简单的SQL 行列转换
GO

现在 select * from VDepartmentEmployeeType

Id          Department EmployeeType Cnt
----------- ---------- ------------ -----------
2           B          辞退           1
1           A          临时           1
1           A          正式           2
2           B          正式           2

有了这个结果,我们再通过行列转换,就可以实现要求的输出了
行列转换采用 case 分支语句来实现,如下:

一个简单的SQL 行列转换select Id as '部门编号', Department as '部门名称'
一个简单的SQL 行列转换
[正式]= Sum(case when EmployeeType = '正式' then Cnt else 0 end),
一个简单的SQL 行列转换
[临时]= Sum(case when EmployeeType = '临时' then Cnt else 0 end),
一个简单的SQL 行列转换
[辞退]= Sum(case when EmployeeType = '辞退' then Cnt else 0 end),
一个简单的SQL 行列转换
[合计]= Sum(case when EmployeeType <> ''  then Cnt else 0 end)
一个简单的SQL 行列转换
from VDepartmentEmployeeType
一个简单的SQL 行列转换
GROUP BY Id, Department

看一下结果
部门编号        部门名称       正式          临时          辞退          合计
----------- ---------- ----------- ----------- ----------- -----------
1           A          2           1           0           3
2           B          2           0           1           3

现在还有一个问题,如果员工类型不可以应编码怎么办?也就是说我们在写程序的时候并不知道有哪些员工类型。这确实是一个
比较棘手的问题,不过不是不能解决,我们可以通过拼接SQL的方式来解决这个问题。看下面代码
一个简单的SQL 行列转换    DECLARE
一个简单的SQL 行列转换    
@s VARCHAR(max)
一个简单的SQL 行列转换    
SELECT @s = isnull(@s + ',','')+  '['+ltrim(EmployeeType)+'] = ' + 
一个简单的SQL 行列转换    
'Sum(case when EmployeeType = ''' + 
一个简单的SQL 行列转换    EmployeeType 
+ ''' then Cnt else 0 end)'
一个简单的SQL 行列转换    
FROM (SELECT DISTINCT EmployeeType FROM VDepartmentEmployeeType ) temp 
一个简单的SQL 行列转换    
EXEC('select Id as 部门编号, Department as 部门名称,' + @s + 
一个简单的SQL 行列转换    
',[合计]= Sum(case when EmployeeType <> ''''  then Cnt else 0 end)' + 
一个简单的SQL 行列转换    
'from VDepartmentEmployeeType GROUP BY Id, Department')
一个简单的SQL 行列转换

执行结果如下:
部门编号        部门名称       辞退          临时          正式          合计
----------- ---------- ----------- ----------- ----------- -----------
1           A          0           1           2           3
2           B          1           0           2           3

这个结果和前面硬编码的结果是一样的,但我们通过程序来获取了所有的员工类型,这样做的好处是如果我们新增了一个员工类型,比如“合同工”,我们不需要修改程序,就可以得到我们想要的输出。


如果你的数据库是SQLSERVER 2005 或以上,也可以采用SQLSERVER2005 通过的新功能 PIVOT

一个简单的SQL 行列转换SELECT Id as '部门编号', Department as '部门名称'[正式],[临时],[辞退]
一个简单的SQL 行列转换
FROM
一个简单的SQL 行列转换(
SELECT Id,Department,EmployeeType,Cnt
一个简单的SQL 行列转换
FROM VDepartmentEmployeeType) p
一个简单的SQL 行列转换PIVOT
一个简单的SQL 行列转换
SUM (Cnt)
一个简单的SQL 行列转换 
FOR EmployeeType IN ([正式],[临时],[辞退])
一个简单的SQL 行列转换)
AS unpvt

结果如下
部门编号        部门名称       正式          临时          辞退
----------- ---------- ----------- ----------- -----------
1           A          2           1           NULL
2           B          2           NULL        1

NULL 可以通过 ISNULL 函数来强制转换为0,这里我就不写出具体的SQL语句了。这个功能感觉还是不错,不过合计好像用这种方法不太好搞。不知道各位同行有没有什么好办法。

相关文章:

  • 2021-11-06
  • 2021-06-24
  • 2021-06-29
  • 2022-02-07
  • 2021-10-22
猜你喜欢
  • 2021-07-19
  • 2021-11-03
  • 2022-12-23
  • 2021-12-04
  • 2022-12-23
  • 2021-10-16
  • 2021-08-18
相关资源
相似解决方案