T-SQL:与string连接相反 – 如何将string拆分成多个logging

可能重复:
在SQL中拆分string

我已经看到了几个与 SQL中string连接相关的问题 。 我想知道你将如何处理相反的问题:将分离的昏迷string拆分为多行数据:

可以说我有桌子:

userTypedTags(userID,commaSeparatedTags) 'one entry per user tags(tagID,name) 

并希望将数据插入到表中

 userTag(userID,tagID) 'multiple entries per user 

启发由哪些标签不在数据库中? 题

编辑

感谢您的答案,实际上多一个值得接受,但我只能select一个, 由凯德Roux提出的recursion解决scheme似乎相当干净。 它适用于SQL Server 2005及更高版本。

对于早期版本的SQL Server,可以使用由miies提供的解决scheme。 对于使用文本数据typeswcm答案将是有帮助的。 再次感谢。

这里logging的这个问题有很多种解决scheme,包括这个小gem:

 CREATE FUNCTION dbo.Split (@sep char(1), @s varchar(512)) RETURNS table AS RETURN ( WITH Pieces(pn, start, stop) AS ( SELECT 1, 1, CHARINDEX(@sep, @s) UNION ALL SELECT pn + 1, stop + 1, CHARINDEX(@sep, @s, stop + 1) FROM Pieces WHERE stop > 0 ) SELECT pn, SUBSTRING(@s, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END) AS s FROM Pieces ) 

您也可以使用XML来实现这个效果, 正如这里所看到的那样 ,这可以消除所提供的答案的局限性,这些答案似乎都以某种方式包含recursion。 我在这里所做的特定用途允许多达32个字符的分隔符,但是可以增加它的大小。

 create FUNCTION [dbo].[Split] (@sep VARCHAR(32), @s VARCHAR(MAX)) RETURNS TABLE AS RETURN ( SELECT r.value('.','VARCHAR(MAX)') as Item FROM (SELECT CONVERT(XML, N'<root><r>' + REPLACE(REPLACE(REPLACE(@s,'& ','&amp; '),'<','&lt;'), @sep, '</r><r>') + '</r></root>') as valxml) x CROSS APPLY x.valxml.nodes('//root/r') AS RECORDS(r) ) 

然后你可以使用以下方法调用它:

 SELECT * FROM dbo.Split(' ', 'I hate bunnies') 

哪个返回:

 ----------- |I | |---------| |hate | |---------| |bunnies | ----------- 

我应该注意到,我实际上并不讨厌兔子……它出于某种原因刚刚突然出现在我的头上。


以下是我可以想到在内联表值函数中使用相同方法的最接近的事情。 不要使用它,这是不好的! 这只是为了参考。

 CREATE FUNCTION [dbo].[Split] (@sep VARCHAR(32), @s VARCHAR(MAX)) RETURNS TABLE AS RETURN ( SELECT r.value('.','VARCHAR(MAX)') as Item FROM (SELECT CONVERT(XML, N'<root><r>' + REPLACE(@s, @sep, '</r><r>') + '</r></root>') as valxml) x CROSS APPLY x.valxml.nodes('//root/r') AS RECORDS(r) ) 

我使用这个函数(SQL Server 2005及以上版本)。

 create function [dbo].[Split] ( @string nvarchar(4000), @delimiter nvarchar(10) ) returns @table table ( [Value] nvarchar(4000) ) begin declare @nextString nvarchar(4000) declare @pos int, @nextPos int set @nextString = '' set @string = @string + @delimiter set @pos = charindex(@delimiter, @string) set @nextPos = 1 while (@pos <> 0) begin set @nextString = substring(@string, 1, @pos - 1) insert into @table ( [Value] ) values ( @nextString ) set @string = substring(@string, @pos + len(@delimiter), len(@string)) set @nextPos = @pos set @pos = charindex(@delimiter, @string) end return end 

对于将string拆分为单词的特殊情况,我已经遇到了SQL Server 2008的另一个解决scheme。

 with testTable AS ( SELECT 1 AS Id, N'how now brown cow' AS txt UNION ALL SELECT 2, N'she sells sea shells upon the sea shore' UNION ALL SELECT 3, N'red lorry yellow lorry' UNION ALL SELECT 4, N'the quick brown fox jumped over the lazy dog' ) SELECT display_term, COUNT(*) As Cnt FROM testTable CROSS APPLY sys.dm_fts_parser('"' + txt + '"', 1033, 0,0) GROUP BY display_term HAVING COUNT(*) > 1 ORDER BY Cnt DESC 

返回

 display_term Cnt ------------------------------ ----------- the 3 brown 2 lorry 2 sea 2 

上面的解决scheme略有修改,所以它适用于可变长度的分隔符。

 create FUNCTION dbo.fn_Split2 (@sep nvarchar(10), @s nvarchar(4000)) RETURNS table AS RETURN ( WITH Pieces(pn, start, stop) AS ( SELECT 1, 1, CHARINDEX(@sep, @s) UNION ALL SELECT pn + 1, stop + (datalength(@sep)/2), CHARINDEX(@sep, @s, stop + (datalength(@sep)/2)) FROM Pieces WHERE stop > 0 ) SELECT pn, SUBSTRING(@s, start, CASE WHEN stop > 0 THEN stop-start ELSE 4000 END) AS s FROM Pieces ) 

NB:我已经使用了datalength(),因为如果有尾随空格,len()报告不正确。

这是一个与2005年之前的SQL Server版本兼容的Split函数。

 CREATE FUNCTION dbo.Split(@data nvarchar(4000), @delimiter nvarchar(100)) RETURNS @result table (Id int identity(1,1), Data nvarchar(4000)) AS BEGIN DECLARE @pos INT DECLARE @start INT DECLARE @len INT DECLARE @end INT SET @len = LEN('.' + @delimiter + '.') - 2 SET @end = LEN(@data) + 1 SET @start = 1 SET @pos = 0 WHILE (@pos < @end) BEGIN SET @pos = CHARINDEX(@delimiter, @data, @start) IF (@pos = 0) SET @pos = @end INSERT @result (data) SELECT SUBSTRING(@data, @start, @pos - @start) SET @start = @pos + @len END RETURN END 

使用CLR,这是一个更简单的替代scheme,可以在所有情况下工作,但比接受的答案快40%

 using System; using System.Collections; using System.Data.SqlTypes; using System.Text.RegularExpressions; using Microsoft.SqlServer.Server; public class UDF { [SqlFunction(FillRowMethodName="FillRow")] public static IEnumerable RegexSplit(SqlString s, SqlString delimiter) { return Regex.Split(s.Value, delimiter.Value); } public static void FillRow(object row, out SqlString str) { str = new SqlString((string) row); } } 

当然,它仍然比PostgreSQL的regexp_split_to_table慢8倍。

 SELECT substring(commaSeparatedTags,0,charindex(',',commaSeparatedTags)) 

会给你第一个标签。 你可以进行类似的操作,每次将substring和charindex组合一层,就可以得到第二个等等。 这是一个直接的解决scheme,但它只适用于非常less的标签,因为查询增长得非常快,变得不可读。 然后转到function,如其他更复杂的回答这篇文章所述。

我写了一段时间回来。 它假设分隔符是一个逗号,并且各个值不超过127个字符。 它可以很容易地修改。

它不受限于4000个字符的好处。

祝你好运!

 ALTER Function [dbo].[SplitStr] ( @txt text ) Returns @tmp Table ( value varchar(127) ) as BEGIN declare @str varchar(8000) , @Beg int , @last int , @size int set @size=datalength(@txt) set @Beg=1 set @str=substring(@txt,@Beg,8000) IF len(@str)<8000 set @Beg=@size ELSE BEGIN set @last=charindex(',', reverse(@str)) set @str=substring(@txt,@Beg,8000-@last) set @Beg=@Beg+8000-@last+1 END declare @workingString varchar(25) , @stringindex int while @Beg<=@size Begin WHILE LEN(@str) > 0 BEGIN SELECT @StringIndex = CHARINDEX(',', @str) SELECT @workingString = CASE WHEN @StringIndex > 0 THEN SUBSTRING(@str, 1, @StringIndex-1) ELSE @str END INSERT INTO @tmp(value) VALUES (cast(rtrim(ltrim(@workingString)) as varchar(127))) SELECT @str = CASE WHEN CHARINDEX(',', @str) > 0 THEN SUBSTRING(@str, @StringIndex+1, LEN(@str)) ELSE '' END END set @str=substring(@txt,@Beg,8000) if @Beg=@size set @Beg=@Beg+1 else IF len(@str)<8000 set @Beg=@size ELSE BEGIN set @last=charindex(',', reverse(@str)) set @str=substring(@txt,@Beg,8000-@last) set @Beg=@Beg+8000-@last+1 END END return END 

我上调了“内森惠勒”的答案,因为我发现“凯德Roux”的答案没有超过一定的string大小的工作。

几个点

– 我发现添加DISTINCT关键字改善了我的performance。

-Nathan的答案只适用于你的标识符是5个字符或更less,当然你可以调整…如果你分裂的项目是INT标识符,我是你我们可以像我一样的下面:

 CREATE FUNCTION [dbo].Split ( @sep VARCHAR(32), @s VARCHAR(MAX) ) RETURNS @result TABLE ( Id INT NULL ) AS BEGIN DECLARE @xml XML SET @XML = N'<root><r>' + REPLACE(@s, @sep, '</r><r>') + '</r></root>' INSERT INTO @result(Id) SELECT DISTINCT r.value('.','int') as Item FROM @xml.nodes('//root//r') AS RECORDS(r) RETURN END 

我通常用下面的代码来做这件事:

 create function [dbo].[Split](@string varchar(max), @separator varchar(10)) returns @splited table ( stringPart varchar(max) ) with execute as caller as begin declare @stringPart varchar(max); set @stringPart = ''; while charindex(@separator, @string) > 0 begin set @stringPart = substring(@string, 0, charindex(@separator, @string)); insert into @splited (stringPart) values (@stringPart); set @string = substring(@string, charindex(@separator, @string) + len(@separator), len(@string) + 1); end return; end go 

你可以用这个查询来testing它:

 declare @example varchar(max); set @example = 'one;string;to;rule;them;all;;'; select * from [dbo].[Split](@example, ';');