如何通过索引从分隔的字符串中获取元素?

时间:2017-07-03 20:23:12

标签: sql sql-server tsql

我有这个功能(信用:searchsqlserver):

CREATE FUNCTION dbo.fnSplit(
    @sInputList VARCHAR(8000)             -- List of delimited items
    , @sDelimiter VARCHAR(8000) = ','     -- delimiter that separates items
 ) RETURNS @List TABLE (item VARCHAR(8000))
     BEGIN
     DECLARE @sItem VARCHAR(8000)
     WHILE CHARINDEX(@sDelimiter,@sInputList,0) <> 0
         BEGIN
         SELECT
         @sItem=RTRIM(LTRIM(SUBSTRING(@sInputList,1,CHARINDEX(@sDelimiter,
            @sInputList,0)-1))),
         @sInputList=RTRIM(LTRIM(SUBSTRING(@sInputList,CHARINDEX(@sDelimiter,
            @sInputList,0)+LEN(@sDelimiter),LEN(@sInputList))))
         IF LEN(@sItem) > 0
            INSERT INTO @List SELECT @sItem
         END

     IF LEN(@sInputList)> 0
     INSERT INTO @List SELECT @sInputList -- Put the last item in
     RETURN
     END
 GO

它将参数作为字符串和分隔符,并逐个返回分隔元素。

select * from fnSplit('1,22,333', ',')    --   returns 1 22 333

我承认我是SQL的新手,我根本无法遵循这个功能背后的整个逻辑。我想要实现的是一个具有第三个参数(索引)的函数,并返回索引提到的位置上的元素。例如:

select * from fnSplit('1 22 333 444 5555 666', ' ' , 2 ) --  333

select * from fnSplit('1 22 333 444 5555 666', ' ' , 0 ) --  1

3 个答案:

答案 0 :(得分:2)

首先,循环拆分效率不高。

使用索引过滤器的示例

Select * 
 From [dbo].[udf-Str-Parse-8K]('1 22 333 444 5555 666', ' '  )
 Where RetSeq=3

<强>返回

RetSeq  RetVal
3       333

没有索引过滤器的示例

Select * 
 From [dbo].[udf-Str-Parse-8K]('1 22 333 444 5555 666', ' '  )

<强>返回

RetSeq  RetVal
1       1
2       22
3       333
4       444
5       5555
6       666

使用了UDF

CREATE FUNCTION [dbo].[udf-Str-Parse-8K] (@String varchar(max),@Delimiter varchar(25))
Returns Table 
As
Return (  
    with   cte1(N)   As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
           cte2(N)   As (Select Top (IsNull(DataLength(@String),0)) Row_Number() over (Order By (Select NULL)) From (Select N=1 From cte1 a,cte1 b,cte1 c,cte1 d) A ),
           cte3(N)   As (Select 1 Union All Select t.N+DataLength(@Delimiter) From cte2 t Where Substring(@String,t.N,DataLength(@Delimiter)) = @Delimiter),
           cte4(N,L) As (Select S.N,IsNull(NullIf(CharIndex(@Delimiter,@String,s.N),0)-S.N,8000) From cte3 S)

    Select RetSeq = Row_Number() over (Order By A.N)
          ,RetVal = LTrim(RTrim(Substring(@String, A.N, A.L)))
    From   cte4 A
);
--Orginal Source http://www.sqlservercentral.com/articles/Tally+Table/72993/
--Select * from [dbo].[udf-Str-Parse-8K]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse-8K]('John||Cappelletti||was||here','||')

答案 1 :(得分:2)

使用多规则表值UDF和循环来解析字符串是非常低效的。  更好的方法:Split strings the right way – or the next best way

无论如何,如果你想调整你的功能,可以为表变量设置IDENTITY列,然后根据第三个参数进行过滤:

CREATE FUNCTION dbo.fnSplit(
    @sInputList VARCHAR(8000)             -- List of delimited items
    , @sDelimiter VARCHAR(8000) = ','     -- delimiter that separates items
    ,@num INT
 ) RETURNS @List TABLE ( item VARCHAR(8000))
     BEGIN
     DECLARE @ListHelper AS TABLE(id INT IDENTITY(1,1), item VARCHAR(8000));
     DECLARE @sItem VARCHAR(8000)
     WHILE CHARINDEX(@sDelimiter,@sInputList,0) <> 0
         BEGIN
         SELECT
         @sItem=RTRIM(LTRIM(SUBSTRING(@sInputList,1,CHARINDEX(@sDelimiter,
            @sInputList,0)-1))),
         @sInputList=RTRIM(LTRIM(SUBSTRING(@sInputList,CHARINDEX(@sDelimiter,
            @sInputList,0)+LEN(@sDelimiter),LEN(@sInputList))))
         IF LEN(@sItem) > 0
            INSERT INTO @ListHelper SELECT @sItem
         END

     IF LEN(@sInputList)> 0
     INSERT INTO @ListHelper SELECT @sInputList -- Put the last item in
     INSERT INTO @List
     SELECT item
     FROM @ListHelper
     WHERE id = @num
     RETURN
     END
 GO

select * from fnSplit('1 22 333 444 5555 666', ' ' , 3 );
--333

LiveDemo

答案 2 :(得分:1)

您可以更改分割字符串函数以包含行号:

    CREATE FUNCTION dbo.fnSplit2(
    @sInputList VARCHAR(8000)             -- List of delimited items
    , @sDelimiter VARCHAR(8000) = ','     -- delimiter that separates items
 ) RETURNS @List TABLE (item VARCHAR(8000))
     BEGIN
     DECLARE @sItem VARCHAR(8000),
             @RowNumber int

     set @RowNumber = 0

     WHILE CHARINDEX(@sDelimiter,@sInputList,0) <> 0
         BEGIN


             SELECT
             @sItem=RTRIM(LTRIM(SUBSTRING(@sInputList,1,CHARINDEX(@sDelimiter,
                @sInputList,0)-1))),
             @sInputList=RTRIM(LTRIM(SUBSTRING(@sInputList,CHARINDEX(@sDelimiter,
                @sInputList,0)+LEN(@sDelimiter),LEN(@sInputList))))
             IF LEN(@sItem) > 0
                INSERT INTO @List SELECT @sItem, @RowNumber

            set @RowNumber = @RowNumber + 1
         END

     set @RowNumber = @RowNumber + 1

     IF LEN(@sInputList)> 0
     INSERT INTO @List SELECT @sInputList, @RowNumber -- Put the last item in
     RETURN
     END