SQL Server 2008中的拆分function

我有Table1像这样的列:

 +--+------+ |ID|Name | +--+------+ |1 |MSSQL | +--+------+ |2 |MySQl | +--+------+ |3 |Oracle| +--+------+ 

在表Table2 ,我有一个列

 +------------+ |Databasename| +------------+ |1,3 | +------------+ |2 | +------------+ |1,2 | +------------+ 

我的输出应该是:

 +------------+ |Databasename| +------------+ |MSSQL,Oracle| +------------+ |MySQL | +------------+ |MSSQL,MYSQL | +------------+ 

我如何得到这个,我需要查询这个..

你正在要求一个分割函数,但你不必分割你的值来得到你想要的结果。

此查询使用for xml技巧在相关子查询中构build逗号分隔名称列表以连接值。 它使用like找出Table1中的每一行Table1使用什么值。

 select ( select ', '+T1.Name from Table1 as T1 where ','+T2.Databasename+',' like '%,'+cast(T1.ID as varchar(10))+',%' for xml path(''), type ).value('substring(text()[1], 3)', 'varchar(max)') as Databasenames from Table2 as T2 

SQL小提琴

首先,最好的解决scheme是不要将数据存储在数据库的逗号分隔列表中。 你应该考虑修复表结构。

如果无法更改表结构,则需要将列表中的数据拆分为行以分配正确的名称。 一旦数据被拆分,您可以将数据连接回列表中。

有许多不同的splitfunction,你可以在网上find,但这里是我通常使用的版本:

 CREATE FUNCTION [dbo].[Split](@String varchar(MAX), @Delimiter char(1)) returns @temptable TABLE (items varchar(MAX)) as begin declare @idx int declare @slice varchar(8000) select @idx = 1 if len(@String)<1 or @String is null return while @idx!= 0 begin set @idx = charindex(@Delimiter,@String) if @idx!=0 set @slice = left(@String,@idx - 1) else set @slice = @String if(len(@slice)>0) insert into @temptable(Items) values(@slice) set @String = right(@String,len(@String) - @idx) if len(@String) = 0 break end return end; 

为了得到你的结果,我将首先应用split函数和一个row_number()因为我没有看到与每一行关联的唯一键。 如果你在每一行有一个唯一的键,那么你将不需要row_number()

 ;with cte as ( select rn, name, id from ( select row_number() over(order by (select 1)) rn, databasename from table2 ) t2 cross apply dbo.split(t2.databasename, ',') i inner join table1 t1 on i.items = t1.id ) select * from cte 

此查询将逗号分隔的列表分解为以下内容:

 | RN | NAME | ID | -------------------- | 1 | MSSQL | 1 | | 1 | Oracle | 3 | | 2 | MySQl | 2 | | 3 | MSSQL | 1 | | 3 | MySQl | 2 | 

一旦你有了正确的name在多行的数据,那么你可以使用STUFF()FOR XML PATH将它连接到列表中。 你完整的查询将类似于这样的:

 ;with cte as ( select rn, name, id from ( select row_number() over(order by (select 1)) rn, databasename from table2 ) t2 cross apply dbo.split(t2.databasename, ',') i inner join table1 t1 on i.items = t1.id ) select STUFF( (SELECT ', ' + c2.name FROM cte c2 where c1.rn = c2.rn order by c2.id FOR XML PATH ('')) , 1, 1, '') Databasename from cte c1 group by c1.rn order by c1.rn; 

看演示与SQL小提琴 。

完整查询的结果是:

 | DATABASENAME | ------------------ | MSSQL, Oracle | | MySQl | | MSSQL, MySQl | 

没有分裂,也没有XMLpath,但达到了正确的结果。

 ;with cte as ( select *, cast(null as varchar(1024)) as str, cast(0 as int) as ID from Table2 union all select DatabaseName, (case when DatabaseName like cast(t.ID as varchar(32)) + ',%' or DatabaseName like '%,' + cast(t.ID as varchar(32)) + ',%' or DatabaseName like '%,' + cast(t.ID as varchar(32)) or DatabaseName = cast(t.ID as varchar(32)) then cast(isnull(str, '') + ',' + t.Name as varchar(1024)) else str end), cte.ID + 1 as ID from cte inner join Table1 t on cte.ID + 1 = t.ID ) select DatabaseName, (case when str like ',%' then substring(str, 2, len(str)) else null end) as str from cte c where ID = (select max(ID) from cte where DatabaseName = c.DatabaseName) 
 --Here it goes: ---------------- -- FieldCount -- ---------------- CREATE FUNCTION [dbo].[FieldCount](@S VARCHAR(8000), @Separator VARCHAR(10)) RETURNS INT AS BEGIN /* @Author: Leonardo Augusto Rezende Santos @Contact: http://www.linkedin.com/pub/leonardo-santos/0/2b1/890 */ DECLARE @Ptr INT, @p INT, @LenS INT, @LenSep INT, @Result INT IF @Separator = ' ' BEGIN SET @S = REPLACE(@S, ' ', '|-|') SET @Separator = '|-|' END WHILE CHARINDEX(@Separator + @Separator, @S) > 0 SET @S = Replace(@S, @Separator + @Separator, @Separator + '_-_' + @Separator) IF @S <> '' SET @Result = 1 ELSE BEGIN SET @Result = 0 RETURN(@Result) END SET @Ptr = 0 SET @LenS = LEN(@S) SET @LenSep = LEN(@Separator) SET @p = CHARINDEX(@Separator, @S) WHILE @p > 0 BEGIN SET @Result = @Result + 1 SET @Ptr = @Ptr + @p + @LenSep SET @p = CHARINDEX(@Separator, SUBSTRING(@S, @Ptr, @LenS - @Ptr + 1)) END RETURN(@Result) END -------------- -- GetField -- -------------- CREATE FUNCTION [dbo].[GetField](@S VARCHAR(8000), @Separator VARCHAR(10), @Field INT) RETURNS VARCHAR(8000) AS BEGIN /* @Author: Leonardo Augusto Rezende Santos @Contact: http://www.linkedin.com/pub/leonardo-santos/0/2b1/890 */ DECLARE @Ptr INT, @p INT, @LenS INT, @LenSep INT, @Fld INT, @Result VARCHAR(8000) IF @Separator = ' ' BEGIN SET @S = REPLACE(@S, ' ', '|-|') SET @Separator = '|-|' END IF @Field > dbo.FieldCount(@S, @Separator) BEGIN SET @Result = '' RETURN(@Result) END SET @Fld = 1 SET @Ptr = 1 SET @LenS = LEN(@S) SET @LenSep = LEN(@Separator) SET @p = CHARINDEX(@Separator, @S) WHILE (@p > 0) and (@Fld < @Field) BEGIN SET @Fld = @Fld + 1 SET @Ptr = @Ptr + @p + @LenSep - 1 SET @p = CHARINDEX(@Separator, SUBSTRING(@S, @Ptr, @LenS - @Ptr + 1)) END IF (@p = 0) and (@Fld = @Field) SET @p = @LenS - @Ptr + 2 SET @Result = SUBSTRING(@S, @Ptr, @p - 1) RETURN(@Result) END /* USAGE*/ select dbo.FieldCount('A1 A2 A3 A4 A5', ' ') --It will return 5 select dbo.GetField('A1 A2 A3 A4 A5', ' ', 3) --It will return 'A3' select dbo.GetField('A1/A2/A3/A4/A5', '/', 3) --It will return 'A3' --Hope it works for you. --Leonardo Augusto