2013-10-18 71 views
5

我需要创建一个函数,它将返回分隔字符串的第n个元素。使用T-SQL,返回字符串中的第n个分隔元素

对于数据迁移项目,我使用SQL脚本将存储在SQL Server数据库中的JSON审计记录转换为结构化报告。目标是在没有任何代码的情况下提供脚本使用的sql脚本和sql函数。

(这是一个短期的修补程序将同时使用一个新的审核功能被添加的ASP.NET/MVC应用程序)

有分隔字符串可用表例子不乏其人。 我选择的公共表表达式例如http://www.sqlperformance.com/2012/07/t-sql-queries/split-strings

例子:我想从返回67“1,222,2,67,888,1111”

回答

5

这是我最初的解决方案... 它是基于工作通过Aaron Bertrand http://www.sqlperformance.com/2012/07/t-sql-queries/split-strings

我只是改变了返回类型,使其成为一个标量函数。

例子: 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][email protected]) 
     FROM a 
     WHERE [end] < @ll 
    ) 
    INSERT @Items SELECT [value] 
    FROM a 
    WHERE LEN([value]) > 0 
    OPTION (MAXRECURSION 0); 

    SELECT @result=Item 
    FROM @Items 
    WHERE [email protected] 

    RETURN @result; 
END 
GO 
+3

这是相当的开销先用沉重的递归CTE来分割你的字符串,只是挑out *第n个元素*。这可以做得更容易... – Shnugo

1

在精神失常的罕见一刻,我只是想,分裂是,如果要容易得多我们使用XML解析出来给我们:

(使用@Gary金德尔的答案变量)

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)) 

此列出该字符串的所有元素,由指定的茶分裂racter。

我们可以使用XPath测试过滤掉空值,以及进一步的XPath测试来限制这对我们感兴趣的元素在全加里的功能变为:

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 
+0

伟大的解决方案。我想知道什么是更昂贵的表变量与身份或XML。 Jon,我将不得不创建你的函数,并在一个大的结果集上与CTE解决方案并行运行,并查看哪些使用了更多的资源。 –

+0

有兴趣知道 - CTE做了很多字符串操作。对于你的例子(数字只在元素中),我没有打扰解析出非法并用xml实体替换(例如''=> @apos;')。对于更复杂的字符串,您可能需要(但是它通常不是分割操作的情况) –

+0

只需重新阅读您的评论。如果你想存储的东西,你仍然可以很容易地将输出保存到表变量。比较是在两个解析机制之间进行的。 –

1

,你可以把这个选择UFN。如果你需要,你可以自定义它来指定分隔符。在这种情况下,你的ufn将有两个输入。数字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 @[email protected] 
      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 @[email protected]+1 
    END 
-1

我没有足够的评论声望,所以我添加了一个答案。请适当调整。

我跟加里·金德尔的答案的情况下的一个问题那里是两个分隔符

如果你 SELECT * FROM dbo.GetSplitString_CTE(“ABC ^高清^^ GHI”,“^”之间没有任何东西, 3) 你 GHI的 而不是一个空字符串

如果您注释掉 WHERE LEN([值])> 0 线,你会得到期望的结果

0

我无法对Gary的溶胶评论因为我的信誉低下

我知道Gary引用了另一个链接。

我一直在努力了解为什么我们需要这个变量

@ld INT = LEN(@Delimiter) 

我也弄不明白,为什么CHARINDEX具有分隔符的长度的位置开始,@ld

我与许多测试单个字符分隔符的例子,他们的工作。大多数时候,分隔符是单个字符。但是,由于显影剂包含在LD作为分隔符的长度,代码必须对具有一个以上的字符

在这种情况下定界符工作,下列情况下将会失败

11 ,,, 22 ,, ,33 ,,, 44 ,,, 55 ,,,

我从这个链接的代码克隆。 http://codebetter.com/raymondlewallen/2005/10/26/quick-t-sql-to-parse-a-delimited-string/

我已经尝试了各种方案,包括有多个字符分隔符

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 
+1

看起来你在这里问一个问题。你是?如果没有,请删除你问的部分。 –

11

这是最简单的答案rerieve 67(类型安全!):

SELECT CAST('<x>' + REPLACE('1,222,2,67,888,1111',',','</x><x>') + '</x>' AS XML).value('/x[4]','int') 

这个问题是不是关于字符串拆分的方法,而是关于如何获得第n个元素。最简单的,完全inlineable方式将IMO是这样的:

这是一个真正的一行得到第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)') 

当然,你可以使用变量定界符和位置(使用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)') 

如果字符串可能包括禁止的字符,你仍然可以这样做。首先在您的字符串上首先使用FOR XML PATH,以隐式地使用拟合转义序列替换所有禁止的字符。

这是一个非常特殊的情况,如果 - 另外 - 您的分隔符是分号。在这种情况下,我第一次更换分隔符为“#DLMT#”,并替换这最后的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)'); 
+1

这是一些严重的事情!惊人 –

相关问题