如何分割字符串以便可以访问项目x?

时间:2020-03-05 18:37:48  来源:igfitidea点击:

使用SQL Server,如何分割字符串以便可以访问项x?

取一个字符串" Hello John Smith"。如何按空格分割字符串并访问索引1的项目,该项目应返回" John"?

解决方案

回答

我们可能会发现" SQL用户定义函数"中的解析定界字符串的解决方案很有用(来自The Code Project)。

我们可以使用以下简单逻辑:

Declare @products varchar(200) = '1|20|3|343|44|6|8765'
Declare @individual varchar(20) = null

WHILE LEN(@products) > 0
BEGIN
    IF PATINDEX('%|%', @products) > 0
    BEGIN
        SET @individual = SUBSTRING(@products,
                                    0,
                                    PATINDEX('%|%', @products))
        SELECT @individual

        SET @products = SUBSTRING(@products,
                                  LEN(@individual + '|') + 1,
                                  LEN(@products))
    END
    ELSE
    BEGIN
        SET @individual = @products
        SET @products = NULL
        SELECT @individual
    END
END

回答

好吧,我的并不是那么简单,但这是我用来将逗号分隔的输入变量拆分为单个值,然后将其放入表变量中的代码。我确定我们可以对此进行一些修改,以基于空格进行拆分,然后针对该表变量执行基本的SELECT查询以获取结果。

-- Create temporary table to parse the list of accounting cycles.
DECLARE @tblAccountingCycles table
(
    AccountingCycle varchar(10)
)

DECLARE @vchAccountingCycle varchar(10)
DECLARE @intPosition int

SET @vchAccountingCycleIDs = LTRIM(RTRIM(@vchAccountingCycleIDs)) + ','
SET @intPosition = CHARINDEX(',', @vchAccountingCycleIDs, 1)

IF REPLACE(@vchAccountingCycleIDs, ',', '') <> ''
BEGIN
    WHILE @intPosition > 0
    BEGIN
        SET @vchAccountingCycle = LTRIM(RTRIM(LEFT(@vchAccountingCycleIDs, @intPosition - 1)))
        IF @vchAccountingCycle <> ''
        BEGIN
            INSERT INTO @tblAccountingCycles (AccountingCycle) VALUES (@vchAccountingCycle)
        END
        SET @vchAccountingCycleIDs = RIGHT(@vchAccountingCycleIDs, LEN(@vchAccountingCycleIDs) - @intPosition)
        SET @intPosition = CHARINDEX(',', @vchAccountingCycleIDs, 1)
    END
END

这个概念几乎是相同的。另一种选择是利用SQL Server 2005本身内的.NET兼容性。我们基本上可以在.NET中编写一个简单的方法,该方法将拆分字符串,然后将其公开为存储过程/函数。

回答

这是将要执行的UDF。它将返回一个定界值的表​​,还没有尝试所有的方案,但是示例运行良好。

CREATE FUNCTION SplitString 
(
    -- Add the parameters for the function here
    @myString varchar(500),
    @deliminator varchar(10)
)
RETURNS 
@ReturnTable TABLE 
(
    -- Add the column definitions for the TABLE variable here
    [id] [int] IDENTITY(1,1) NOT NULL,
    [part] [varchar](50) NULL
)
AS
BEGIN
        Declare @iSpaces int
        Declare @part varchar(50)

        --initialize spaces
        Select @iSpaces = charindex(@deliminator,@myString,0)
        While @iSpaces > 0

        Begin
            Select @part = substring(@myString,0,charindex(@deliminator,@myString,0))

            Insert Into @ReturnTable(part)
            Select @part

    Select @myString = substring(@mystring,charindex(@deliminator,@myString,0)+ len(@deliminator),len(@myString) - charindex(' ',@myString,0))

            Select @iSpaces = charindex(@deliminator,@myString,0)
        end

        If len(@myString) > 0
            Insert Into @ReturnTable
            Select @myString

    RETURN 
END
GO

我们可以这样称呼它:

Select * From SplitString('Hello John Smith',' ')

编辑:更新了解决方案,以处理len> 1的delimters,如:

select * From SplitString('Hello**John**Smith','**')

回答

试试这个:

CREATE function [SplitWordList]
(
 @list varchar(8000)
)
returns @t table 
(
 Word varchar(50) not null,
 Position int identity(1,1) not null
)
as begin
  declare 
    @pos int,
    @lpos int,
    @item varchar(100),
    @ignore varchar(100),
    @dl int,
    @a1 int,
    @a2 int,
    @z1 int,
    @z2 int,
    @n1 int,
    @n2 int,
    @c varchar(1),
    @a smallint
  select 
    @a1 = ascii('a'),
    @a2 = ascii('A'),
    @z1 = ascii('z'),
    @z2 = ascii('Z'),
    @n1 = ascii('0'),
    @n2 = ascii('9')
  set @ignore = '''"'
  set @pos = 1
  set @dl = datalength(@list)
  set @lpos = 1
  set @item = ''
  while (@pos <= @dl) begin
    set @c = substring(@list, @pos, 1)
    if (@ignore not like '%' + @c + '%') begin
      set @a = ascii(@c)
      if ((@a >= @a1) and (@a <= @z1))  
        or ((@a >= @a2) and (@a <= @z2))
        or ((@a >= @n1) and (@a <= @n2))
      begin
        set @item = @item + @c
      end else if (@item > '') begin
        insert into @t values (@item)
        set @item = ''
      end
    end 
    set @pos = @pos + 1
  end
  if (@item > '') begin
    insert into @t values (@item)
  end
  return
end

像这样测试它:

select * from SplitWordList('Hello John Smith')

回答

我不相信SQL Server具有内置的拆分功能,因此除了UDF之外,我知道的唯一其他答案是劫持PARSENAME函数:

SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 2)

PARSENAME接受一个字符串并将其分割为句点字符。它以数字作为第二个参数,该数字指定要返回的字符串段(从后到前)。

SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 3)  --return Hello

明显的问题是字符串已经包含句点。我仍然认为使用UDF是最好的方法...还有其他建议吗?

回答

首先,创建一个函数(使用CTE,公用表表达式消除了对临时表的需要)

create function dbo.SplitString 
    (
        @str nvarchar(4000), 
        @separator char(1)
    )
    returns table
    AS
    return (
        with tokens(p, a, b) AS (
            select 
                1, 
                1, 
                charindex(@separator, @str)
            union all
            select
                p + 1, 
                b + 1, 
                charindex(@separator, @str, b + 1)
            from tokens
            where b > 0
        )
        select
            p-1 zeroBasedOccurance,
            substring(
                @str, 
                a, 
                case when b > 0 then b-a ELSE 4000 end) 
            AS s
        from tokens
      )
    GO

然后,将其用作任何表(或者对其进行修改以适合我们现有的存储过程),如下所示。

select s 
from dbo.SplitString('Hello John Smith', ' ')
where zeroBasedOccurance=1

更新

如果输入字符串超过4000个字符,则先前版本将失败。此版本解决了以下限制:

create function dbo.SplitString 
(
    @str nvarchar(max), 
    @separator char(1)
)
returns table
AS
return (
with tokens(p, a, b) AS (
    select 
        cast(1 as bigint), 
        cast(1 as bigint), 
        charindex(@separator, @str)
    union all
    select
        p + 1, 
        b + 1, 
        charindex(@separator, @str, b + 1)
    from tokens
    where b > 0
)
select
    p-1 ItemIndex,
    substring(
        @str, 
        a, 
        case when b > 0 then b-a ELSE LEN(@str) end) 
    AS s
from tokens
);

GO

用法保持不变。

回答

我们可以利用Number表来进行字符串解析。

创建一个物理数字表:

create table dbo.Numbers (N int primary key);
    insert into dbo.Numbers
        select top 1000 row_number() over(order by number) from master..spt_values
    go

创建具有1000000行的测试表

create table #yak (i int identity(1,1) primary key, array varchar(50))

    insert into #yak(array)
        select 'a,b,c' from dbo.Numbers n cross join dbo.Numbers nn
    go

创建功能

create function [dbo].[ufn_ParseArray]
        (   @Input      nvarchar(4000), 
            @Delimiter  char(1) = ',',
            @BaseIdent  int
        )
    returns table as
    return  
        (   select  row_number() over (order by n asc) + (@BaseIdent - 1) [i],
                    substring(@Input, n, charindex(@Delimiter, @Input + @Delimiter, n) - n) s
            from    dbo.Numbers
            where   n <= convert(int, len(@Input)) and
                    substring(@Delimiter + @Input, n, 1) = @Delimiter
        )
    go

用法(在我的笔记本电脑上以40秒的速度输出300万行)

select * 
    from #yak 
    cross apply dbo.ufn_ParseArray(array, ',', 1)

清理

drop table dbo.Numbers;
    drop function  [dbo].[ufn_ParseArray]

这里的性能并不令人惊讶,但是调用百万行表中的函数并不是最好的主意。如果将字符串拆分成许多行,我将避免使用该函数。

回答

没有代码,但请阅读有关此内容的权威文章。其他答案中的所有解决方案都是本文列出的解决方案:SQL Server 2005和更高版本中的数组和列表

我个人最常使用Numbers表解决方案,因为它适合我的工作...