一列保存多个ID(将多个用逗号隔开的ID转换成用逗号隔开的名称)

 更新时间:2012年07月27日 11:04:30   作者:  
在做项目时,经常会遇到这样的表结构在主表的中有一列保存的是用逗号隔开ID
背景:在做项目时,经常会遇到这样的表结构在主表的中有一列保存的是用逗号隔开ID。如,当一个员工从属多个部门时、当一个项目从属多个城市时、当一个设备从属多个项目时,很多人都会在员工表中加入一个deptIds VARCHAR(1000)列(本文以员工从属多个部门为例),用以保存部门编号列表(很明显这不符合第一范式,但很多人这样设计了,在这篇文章中我们暂不讨论在这种应用场景下,如此设计的对与错,有兴趣的可以在回复中聊聊),然后我们在查询列表中需要看到这个员工从属哪些部门。
初始化数据:
部门表、员工表数据:
复制代码 代码如下:


IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Department]'))
DROP TABLE [dbo].Department
GO
--部门表
CREATE TABLE Department
(
id int,
name nvarchar(50)
)
INSERT INTO Department(id,name)
SELECT 1,'人事部'
UNION
SELECT 2,'工程部'
UNION
SELECT 3,'管理部'
SELECT * FROM Department

IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Employee]'))
DROP TABLE [dbo].Employee
GO
--员工表
CREATE TABLE Employee
(
id int,
name nvarchar(20),
deptIds varchar(1000)
)
INSERT INTO Employee(id,name,deptIds)
SELECT 1,'蒋大华','1,2,3'
UNION
SELECT 2,'小明','1'
UNION
SELECT 3,'小华',''
SELECT * FROM Employee

希望得到的结果:

解决方法:

第一步,是得到如下的数据。即将员工表集合与相关的部门集合做交叉连接,其中使用了fun_SplitIds函数(作用是将ids分割成id列表),然后员工集合与这个得到的集合做交叉连接
复制代码 代码如下:

SELECT E.*,ISNULL(D.name,'') AS deptName
FROM Employee AS E
OUTER APPLY dbo.fun_SplitIds(E.deptIds) AS DID
LEFT JOIN Department AS D ON DID.ID=D.id;

第二步,已经得到了如上的数据,然后要做的就是根据ID分组,并对deptName列做聚合操作,但可惜的是SQL SERVER还没有提供对字符串做聚合的操作。但想到,我们处理树形结构数据时,用CTE来做关系数据,做成有树形格式的数据,如此我们也可以将这个问题转换成做树形格式的问题,代码如下:
复制代码 代码如下:

;WITH EmployeT AS(
--员工的基本信息(使用OUTER APPLY将多个ID拆分开来,然后与部门表相关联)
--此时已将员工表所存的IDS分别与部门相关联,下面需要将此集合中的deptName聚合成一个记录
SELECT E.*,ISNULL(D.name,'') AS deptName
FROM Employee AS E
OUTER APPLY dbo.fun_SplitIds(E.deptIds) AS DID
LEFT JOIN Department AS D ON DID.ID=D.id
),mike AS(
SELECT id,name,deptIds,deptName
,ROW_NUMBER()OVER(PARTITION BY id ORDER BY id) AS level_num
FROM EmployeT
),mike2 AS(
SELECT id,name,deptIds,CAST(deptName AS NVARCHAR(100)) AS deptName,level_num
FROM mike
WHERE level_num=1
UNION ALL
SELECT m.id,m.name,m.deptIds,CAST(m2.deptName+','+m.deptName AS NVARCHAR(100)) AS deptName,m.level_num
FROM mike AS m
INNER JOIN mike2 AS m2 ON m.ID=m2.id AND m.level_num=m2.level_num+1
),maxMikeByIDT AS(
SELECT id,MAX(level_num) AS level_num
FROM mike2
GROUP BY ID
)

SELECT A.id,A.name,A.deptIds,A.deptName
FROM mike2 AS A
INNER JOIN maxMikeByIDT AS B ON A.id=B.ID AND A.level_num=B.level_num
ORDER BY A.id OPTION (MAXRECURSION 0)

结果如下:

全部SQL:
复制代码 代码如下:

IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Department]'))
DROP TABLE [dbo].Department
GO
--部门表
CREATE TABLE Department
(
id int,
name nvarchar(50)
)
INSERT INTO Department(id,name)
SELECT 1,'人事部'
UNION
SELECT 2,'工程部'
UNION
SELECT 3,'管理部'

SELECT * FROM Department


IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Employee]'))
DROP TABLE [dbo].Employee
GO
--员工表
CREATE TABLE Employee
(
id int,
name nvarchar(20),
deptIds varchar(1000)
)
INSERT INTO Employee(id,name,deptIds)
SELECT 1,'蒋大华','1,2,3'
UNION
SELECT 2,'小明','1'
UNION
SELECT 3,'小华',''

SELECT * FROM Employee

--创建一个表值函数,用来拆分用逗号分割的数字串,返回只有一列数字的表
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[fun_SplitIds]'))
DROP FUNCTION [dbo].fun_SplitIds
GO
CREATE FUNCTION dbo.fun_SplitIds(
@Ids nvarchar(1000)
)
RETURNS @t_id TABLE (id VARCHAR(36))
AS
BEGIN
DECLARE @i INT,@j INT,@l INT,@v VARCHAR(36);
SET @i = 0;
SET @j = 0;
SET @l = len(@Ids);
while(@j < @l)
begin
SET @j = charindex(',',@Ids,@i+1);
IF(@j = 0) set @j = @l+1;
SET @v = cast(SUBSTRING(@Ids,@i+1,@j-@i-1) as VARCHAR(36));
INSERT INTO @t_id VALUES(@v)
SET @i = @j;
END
RETURN;
END
GO


;WITH EmployeT AS(
--员工的基本信息(使用OUTER APPLY将多个ID拆分开来,然后与部门表相关联)
--此时已将员工表所存的IDS分别与部门相关联,下面需要将此集合中的deptName聚合成一个记录
SELECT E.*,ISNULL(D.name,'') AS deptName
FROM Employee AS E
OUTER APPLY dbo.fun_SplitIds(E.deptIds) AS DID
LEFT JOIN Department AS D ON DID.ID=D.id
),mike AS(
SELECT id,name,deptIds,deptName
,ROW_NUMBER()OVER(PARTITION BY id ORDER BY id) AS level_num
FROM EmployeT
),mike2 AS(
SELECT id,name,deptIds,CAST(deptName AS NVARCHAR(100)) AS deptName,level_num
FROM mike
WHERE level_num=1
UNION ALL
SELECT m.id,m.name,m.deptIds,CAST(m2.deptName+','+m.deptName AS NVARCHAR(100)) AS deptName,m.level_num
FROM mike AS m
INNER JOIN mike2 AS m2 ON m.ID=m2.id AND m.level_num=m2.level_num+1
),maxMikeByIDT AS(
SELECT id,MAX(level_num) AS level_num
FROM mike2
GROUP BY ID
)

SELECT A.id,A.name,A.deptIds,A.deptName
FROM mike2 AS A
INNER JOIN maxMikeByIDT AS B ON A.id=B.ID AND A.level_num=B.level_num
ORDER BY A.id OPTION (MAXRECURSION 0)

相关文章

  • SQL server 三种常用的触发器

    SQL server 三种常用的触发器

    文章介绍了SQL Server中三种常用的触发器:插入触发器、删除触发器和更新触发器,每种触发器在特定操作(插入、删除、更新)发生时触发,触发器可以访问两个虚拟表:Inserted(新数据)和Deleted(旧数据),用于获取和操作数据,感兴趣的朋友一起看看吧
    2025-03-03
  • SQL如何实现横表与纵表相互转换

    SQL如何实现横表与纵表相互转换

    针对SQL横向表转纵向的问题,本文从实际应用出发,详细讲解了语法和操作步骤,并结合实例进行了演示和说明。文章还探讨了该方法的优缺点,提出了一些值得注意的事项,旨在帮助读者更深入地理解这一重要的数据处理技巧
    2023-04-04
  • 用SQL语句实现替换字符串

    用SQL语句实现替换字符串

    这样可以方便我们直接在数据库中替换字符
    2008-07-07
  • SQL Server定期收缩日志文件的详细步骤

    SQL Server定期收缩日志文件的详细步骤

    事务日志记录着在相关数据库上的操作,同时还存储数据库恢复(recovery)的相关信息,收缩日志的原因有很多种,有些是考虑空间不足,有些则是应用程序限制导致的,本文将给大家介绍SQL Server定期收缩日志文件的详细步骤,需要的朋友可以参考下
    2024-06-06
  • SQLserver安装时要求CDKEY的解决办法

    SQLserver安装时要求CDKEY的解决办法

    今天安装服务器的时候出现的cdkey无法通过验证,后来改了注册表重启后,再次安装正常通过。
    2011-03-03
  • SQL server高级应用 收藏版

    SQL server高级应用 收藏版

    SQL server高级应用 收藏版,使用sqlserver的朋友可以参考下
    2012-08-08
  • SQL Server数据库表的创建与管理操作大全

    SQL Server数据库表的创建与管理操作大全

    这篇文章主要给大家介绍了关于SQL Server数据库表的创建与管理操作的相关资料, SQL Server是一个关系型数据库管理系统,它可以帮助用户创建、管理和查询数据,文中介绍的非常详细,需要的朋友可以参考下
    2024-01-01
  • Sqlserver 存储过程中结合事务的代码

    Sqlserver 存储过程中结合事务的代码

    Sqlserver 存储过程中使用事务
    2010-06-06
  • insert select与select into 的用法使用说明

    insert select与select into 的用法使用说明

    将一个table1的数据的部分字段复制到table2中,或者将整个table1复制到table2中,这时候我们就要使用SELECT INTO 和 INSERT INTO SELECT 表复制语句了
    2011-12-12
  • 必须会的SQL语句(三) 数据插入

    必须会的SQL语句(三) 数据插入

    这篇文章主要介绍了sqlserver中数据插入的sql语句,需要的朋友可以参考下
    2015-01-01

最新评论