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是不要将数据存储在数据库的逗号分隔列表中。 你应该考虑修复表结构。
如果无法更改表结构,则需要将列表中的数据拆分为行以分配正确的名称。 一旦数据被拆分,您可以将数据连接回列表中。
有许多不同的split
function,你可以在网上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