使用T-SQL,从string返回第n个分隔的元素
我有一个需要创build一个函数将返回一个分隔string的第n个元素。
对于数据迁移项目,我使用SQL脚本将存储在SQL Server数据库中的JSON审计logging转换为结构化报告。 目标是提供一个没有任何代码的脚本使用的sql脚本和sql函数。
(这是在ASP.NET / MVC应用程序中添加新的审计function时使用的短期修复程序)
对于可用的表格示例,不存在分隔string的短缺。 我已经select了一个公共expression式示例http://www.sqlperformance.com/2012/07/t-sql-queries/split-strings
例如:我想从'1,222,2,67,888,1111'
这是最简单的答案,以缓解67( types安全!! ):
SELECT CAST('<x>' + REPLACE('1,222,2,67,888,1111',',','</x><x>') + '</x>' AS XML).value('/x[4]','int')
这个问题不是关于string拆分方法 ,而是关于如何获得第n个元素 。 最简单的,完全可圈定的方式是这个国际海事组织:
这是一个真正的单行来得到第2部分由一个空间分隔:
DECLARE @input NVARCHAR(100)=N'part1 part2 part3'; SELECT CAST(N'<x>' + REPLACE(@input,N' ',N'</x><x>') + N'</x>' AS XML).value('/x[2]','nvarchar(max)')
当然你可以使用variables来定界和定位(使用sql:column
直接从查询的值中检索位置):
DECLARE @dlmt NVARCHAR(10)=N' '; DECLARE @pos INT = 2; SELECT CAST(N'<x>' + REPLACE(@input,@dlmt,N'</x><x>') + N'</x>' AS XML).value('/x[sql:variable("@pos")][1]','nvarchar(max)')
如果你的string可能包含禁止的字符 ,你仍然可以这样做。 首先在string上使用FOR XML PATH
来隐式地用拟合转义序列replace所有禁止的字符。
这是一个非常特殊的情况,如果 – 另外 – 你的分隔符是分号 。 在这种情况下,首先将分隔符replace为“#DLMT#”,并最终将其replace为XML标签:
SET @input=N'Some <, > and &;Other äöü@€;One more'; SET @dlmt=N';'; SELECT CAST(N'<x>' + REPLACE((SELECT REPLACE(@input,@dlmt,'#DLMT#') AS [*] FOR XML PATH('')),N'#DLMT#',N'</x><x>') + N'</x>' AS XML).value('/x[sql:variable("@pos")][1]','nvarchar(max)');
这是我最初的解决scheme…这是基于Aaron Bertrand的工作http://www.sqlperformance.com/2012/07/t-sql-queries/split-strings
我只是改变了返回types,使其成为一个标量函数。
示例:SELECT dbo.GetSplitString_CTE('1,222,2,67,888,1111',',',4)
CREATE FUNCTION dbo.GetSplitString_CTE ( @List VARCHAR(MAX), @Delimiter VARCHAR(255), @ElementNumber int ) RETURNS VARCHAR(4000) AS BEGIN DECLARE @result varchar(4000) DECLARE @Items TABLE ( position int IDENTITY PRIMARY KEY, Item VARCHAR(4000) ) DECLARE @ll INT = LEN(@List) + 1, @ld INT = LEN(@Delimiter); WITH a AS ( SELECT [start] = 1, [end] = COALESCE(NULLIF(CHARINDEX(@Delimiter, @List, @ld), 0), @ll), [value] = SUBSTRING(@List, 1, COALESCE(NULLIF(CHARINDEX(@Delimiter, @List, @ld), 0), @ll) - 1) UNION ALL SELECT [start] = CONVERT(INT, [end]) + @ld, [end] = COALESCE(NULLIF(CHARINDEX(@Delimiter, @List, [end] + @ld), 0), @ll), [value] = SUBSTRING(@List, [end] + @ld, COALESCE(NULLIF(CHARINDEX(@Delimiter, @List, [end] + @ld), 0), @ll)-[end]-@ld) FROM a WHERE [end] < @ll ) INSERT @Items SELECT [value] FROM a WHERE LEN([value]) > 0 OPTION (MAXRECURSION 0); SELECT @result=Item FROM @Items WHERE position=@ElementNumber RETURN @result; END GO
在一个罕见的疯狂时刻,我只是认为,如果我们使用XML来为我们parsing它,那么拆分会更容易:
(使用@Gary Kindel的答案中的variables)
declare @xml xml set @xml = '<split><el>' + replace(@list,@Delimiter,'</el><el>') + '</el></split>' select el = split.el.value('.','varchar(max)') from @xml.nodes('/split/el') split(el))
这列出了由指定字符分割的string的所有元素。
我们可以使用xpathtesting来过滤掉空值,再进一步的xpathtesting来将其限制在我们感兴趣的元素中。完整的Gary的函数变成:
alter FUNCTION dbo.GetSplitString_CTE ( @List VARCHAR(MAX), @Delimiter VARCHAR(255), @ElementNumber int ) RETURNS VARCHAR(max) AS BEGIN declare @xml xml set @xml = '<split><el>' + replace(@list,@Delimiter,'</el><el>') + '</el></split>' declare @ret varchar(max) set @ret = (select el = split.el.value('.','varchar(max)') from @xml.nodes('/split/el[string-length(.)>0][position() = sql:variable("@elementnumber")]') split(el)) return @ret END
你可以把这个select到UFN。 如果你需要,你也可以自定义指定分隔符。 在这种情况下,你的ufn将有两个input。 数字N和分隔符使用。
DECLARE @tlist varchar(max)='10,20,30,40,50,60,70,80,90,100' DECLARE @i INT=1, @nth INT=3 While len(@tlist) <> 0 BEGIN IF @i=@nth BEGIN select Case when charindex(',',@tlist) <> 0 Then LEFT(@tlist,charindex(',',@tlist)-1) Else @tlist END END Select @tlist = Case when charindex(',',@tlist) <> 0 Then substring(@tlist,charindex(',',@tlist)+1,len(@tlist)) Else '' END SELECT @i=@i+1 END
我不能评论加里的解决scheme,因为我的名声很低
我知道加里正在引用另一个链接。
我一直在努力去理解为什么我们需要这个variables
@ld INT = LEN(@Delimiter)
我也不明白为什么charindex必须从分隔符长度的位置开始@ld
我用单个字符分隔符testing了很多例子,并且它们工作。 大多数时候,分隔符是单个字符。 但是,由于开发人员将ld作为分隔符的长度,因此代码必须适用于具有多个字符的分隔符
在这种情况下,以下情况将失败
11 ,,, 22 ,,, 33 ,,, 44 ,,, 55 ,,,
我从这个链接的代码克隆。 http://codebetter.com/raymondlewallen/2005/10/26/quick-t-sql-to-parse-a-delimited-string/
我已经testing了各种场景,包括具有多个字符的分隔符
alter FUNCTION [dbo].[split1] ( @string1 VARCHAR(8000) -- List of delimited items , @Delimiter VARCHAR(40) = ',' -- delimiter that separates items , @ElementNumber int ) RETURNS varchar(8000) AS BEGIN declare @position int declare @piece varchar(8000)='' declare @returnVal varchar(8000)='' declare @Pattern varchar(50) = '%' + @Delimiter + '%' declare @counter int =0 declare @ld int = len(@Delimiter) declare @ls1 int = len (@string1) declare @foundit int = 0 if patindex(@Pattern , @string1) = 0 return '' if right(rtrim(@string1),1) <> @Delimiter set @string1 = @string1 + @Delimiter set @position = patindex(@Pattern , @string1) + @ld -1 while @position > 0 begin set @counter = @counter +1 set @ls1 = len (@string1) if (@ls1 >= @ld) set @piece = left(@string1, @position - @ld) else break if (@counter = @ElementNumber) begin set @foundit = 1 break end if len(@string1) > 0 begin set @string1 = stuff(@string1, 1, @position, '') set @position = patindex(@Pattern , @string1) + @ld -1 end else set @position = -1 end if @foundit =1 set @returnVal = @piece else set @returnVal = '' return @returnVal
我没有足够的评价,所以我加了一个答案。 请适当调整。
对于加里·金德尔(Gary Kindel)的回答,在两个分隔符之间没有任何内容的情况下,我遇到了一个问题
如果你从dbo.GetSplitString_CTE('abc ^ def ^^ ghi','^',3)中select*,你会得到ghi而不是空string
如果你注释掉WHERE LEN([value])> 0行,你会得到想要的结果