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

Gat*_*ler 480 sql t-sql sql-server split

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

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

Nat*_*ord 352

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

SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 2) 
Run Code Online (Sandbox Code Playgroud)

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

SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 3)  --return Hello
Run Code Online (Sandbox Code Playgroud)

显而易见的问题是字符串已经包含句点.我仍然认为使用UDF是最好的方法......任何其他建议?

  • 谢谢扫罗......我应该指出,这个解决方案对于真正的开发来说真是一个糟糕的解决方案.PARSENAME只需要四个部分,因此使用一个包含四个以上部分的字符串会导致它返回NULL.UDF解决方案显然更好. (101认同)
  • 为了使索引以"正确"的方式工作,即从1开始,我用REVERSE劫持了你的劫持:REVERSE(PARSENAME(REPLACE(REVERSE('Hello John Smith'),'','.') ,1)) - 返回Hello (35认同)
  • 这是一个很棒的黑客攻击,也让我觉得这样的东西对于真实语言中那么简单的东西是必要的. (31认同)
  • @BaconBits虽然我在理论上同意,但在实践中这样的工具在规范由你之前的某个人制作的糟糕设计时很有用. (8认同)
  • @FactorMystic [First Normal Form](http://en.wikipedia.org/wiki/First_normal_form)要求您不要在单个字段中放置多个值.它实际上是RDBMS的第一条规则.不提供`SPLIT()`函数,因为它鼓励数据库设计不良,并且数据库永远不会被优化以使用以这种格式存储的数据.RDBMS没有义务帮助开发人员完成设计*不能处理的愚蠢事情.正确的答案将*始终*"正常化您的数据库就像我们40年前告诉你的那样." SQL和RDBMS都不应该归咎于糟糕的设计. (3认同)

Jon*_*tor 186

您可以在SQL用户定义函数中找到解析分隔字符串的解决方案(来自代码项目).

你可以使用这个简单的逻辑:

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
Run Code Online (Sandbox Code Playgroud)

  • @GateKiller这个解决方案不支持Unicode,它使用硬编码数字(18,3),这使得它不具备可行的"可重用"功能. (12认同)
  • 这可以工作,但分配了大量内存并浪费CPU. (4认同)
  • 从SQL Server 2016开始,现在有一个内置函数[`STRING_SPLIT`](https://docs.microsoft.com/zh-cn/sql/t-sql/functions/string-split-transact-sql)它将拆分一个字符串并返回一个单列的表结果,您可以在SELECT语句或其他地方使用它。 (2认同)

vzc*_*czc 108

首先,创建一个函数(使用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
Run Code Online (Sandbox Code Playgroud)

然后,将它用作任何表(或修改它以适合您现有的存储过程),就像这样.

select s 
from dbo.SplitString('Hello John Smith', ' ')
where zeroBasedOccurance=1
Run Code Online (Sandbox Code Playgroud)

更新

对于长度超过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
Run Code Online (Sandbox Code Playgroud)

用法保持不变.

  • 它很优雅但仅适用于100个元素,因为递归深度的限制. (14认同)
  • @Pking,不,默认为"100"(防止无限循环).使用[MAXRECURSION提示](http://msdn.microsoft.com/en-us/library/ms175972%28v=sql.90%29.aspx)来定义递归级别的数量(`0`到`32767`,` 0`是"无限制" - 可能粉碎服务器).BTW,比'PARSENAME`更好的答案,因为它是通用的:-).+1 (3认同)

Aar*_*and 60

这里的大多数解决方案都使用循环或递归CTE.基于集合的方法将是优越的,我保证:

CREATE FUNCTION [dbo].[SplitString]
    (
        @List NVARCHAR(MAX),
        @Delim VARCHAR(255)
    )
    RETURNS TABLE
    AS
        RETURN ( SELECT [Value] FROM 
          ( 
            SELECT 
              [Value] = LTRIM(RTRIM(SUBSTRING(@List, [Number],
              CHARINDEX(@Delim, @List + @Delim, [Number]) - [Number])))
            FROM (SELECT Number = ROW_NUMBER() OVER (ORDER BY name)
              FROM sys.all_objects) AS x
              WHERE Number <= LEN(@List)
              AND SUBSTRING(@Delim + @List, [Number], LEN(@Delim)) = @Delim
          ) AS y
        );
Run Code Online (Sandbox Code Playgroud)

有关分割函数的更多信息,为什么(并证明)while循环和递归CTE不能缩放,以及更好的替代方法,如果分割来自应用程序层的字符串:

但是,在SQL Server 2016或更高版本上,您应该查看STRING_SPLIT()STRING_AGG():

  • 我逐字尝试了这个函数:`select*from DBO.SplitString('Hello John smith','');`并且输出的结果是:**Value**Hello ello llo lo o John ohn hn n smith mith ith th (5认同)
  • @AaronBertrand GateKiller发布的原始问题涉及空格分隔符. (2认同)

Nat*_*erl 37

您可以利用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
Run Code Online (Sandbox Code Playgroud)

创建具有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
Run Code Online (Sandbox Code Playgroud)

创建功能

    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
Run Code Online (Sandbox Code Playgroud)

用法(在我的笔记本电脑上输出40s的40m行)

    select * 
    from #yak 
    cross apply dbo.ufn_ParseArray(array, ',', 1)
Run Code Online (Sandbox Code Playgroud)

清理

    drop table dbo.Numbers;
    drop function  [dbo].[ufn_ParseArray]
Run Code Online (Sandbox Code Playgroud)

这里的性能并不令人惊讶,但调用超过一百万行表的函数并不是最好的主意.如果执行一个字符串拆分多行,我会避免该函数.

  • 关于它如何工作的一些解释将是伟大的 (3认同)
  • 最好的解决方案IMO,其他人有一些限制..这很快,可以用很多元素解析长字符串. (2认同)

Shn*_*ugo 28

这个问题不是关于字符串拆分方法,而是关于如何获取第n个元素.

这里所有的答案都使用递归,做某种类型的字符串分裂CTE,多发性CHARINDEX,REVERSE并且PATINDEX,发明的功能,请致电CLR方法,数表,CROSS APPLYS ^ ......大多数的答案涉及多行代码.

但是 - 如果你真的只想要获得第n个元素的方法 - 这可以作为真正的单行,没有UDF,甚至不是子选择......并且作为额外的好处:类型安全

获取由空格分隔的第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)')
Run Code Online (Sandbox Code Playgroud)

当然,您可以使用变量作为分隔符和位置(用于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)')
Run Code Online (Sandbox Code Playgroud)

如果您的字符串可能包含禁用字符(尤其是其中一个字符&><),您仍然可以这样做.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)');
Run Code Online (Sandbox Code Playgroud)

SQL-Server 2016+的更新

很遗憾,开发人员忘记返回部分索引STRING_SPLIT.但是,使用SQL-Server 2016+,有JSON_VALUE.

文件明确规定:

当OPENJSON解析JSON数组时,该函数将JSON文本中元素的索引作为键返回.

一个字符串只OPENJSON需要括号:JSON_VALUE.
也就是说像一个字符串OPENJSON需要是1,2,3.
这些是非常简单的字符串操作.试试吧:

DECLARE @str VARCHAR(100)='Hello John Smith';
DECLARE @position INT = 2;

--We can build the json-path '$[1]' using CONCAT
SELECT JSON_VALUE('["' + REPLACE(@str,' ','","') + '"]',CONCAT('$[',@position-1,']'));
Run Code Online (Sandbox Code Playgroud)


bre*_*dan 21

这是一个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
Run Code Online (Sandbox Code Playgroud)

你会这样称呼它:


Select * From SplitString('Hello John Smith',' ')
Run Code Online (Sandbox Code Playgroud)

编辑:更新解决方案以处理len> 1的分隔符,如下所示:


select * From SplitString('Hello**John**Smith','**')
Run Code Online (Sandbox Code Playgroud)

  • 注意len(),因为如果它的参数有尾随空格,它将不会返回正确的数字.例如len(' - ')= 2. (2认同)

小智 15

在这里,我发布了一种简单的解决方案

CREATE FUNCTION [dbo].[split](
          @delimited NVARCHAR(MAX),
          @delimiter NVARCHAR(100)
        ) RETURNS @t TABLE (id INT IDENTITY(1,1), val NVARCHAR(MAX))
        AS
        BEGIN
          DECLARE @xml XML
          SET @xml = N'<t>' + REPLACE(@delimited,@delimiter,'</t><t>') + '</t>'

          INSERT INTO @t(val)
          SELECT  r.value('.','varchar(MAX)') as item
          FROM  @xml.nodes('/t') as records(r)
          RETURN
        END
Run Code Online (Sandbox Code Playgroud)


执行这样的功能

  select * from dbo.split('Hello John Smith',' ')
Run Code Online (Sandbox Code Playgroud)


Dam*_*ake 10

在我看来,你们让它变得太复杂了.只需创建一个CLR UDF并完成它.

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Collections.Generic;

public partial class UserDefinedFunctions {
  [SqlFunction]
  public static SqlString SearchString(string Search) {
    List<string> SearchWords = new List<string>();
    foreach (string s in Search.Split(new char[] { ' ' })) {
      if (!s.ToLower().Equals("or") && !s.ToLower().Equals("and")) {
        SearchWords.Add(s);
      }
    }

    return new SqlString(string.Join(" OR ", SearchWords.ToArray()));
  }
};
Run Code Online (Sandbox Code Playgroud)

  • 我想这太复杂了,因为我需要使用Visual Studio,然后在服务器上启用CLR,然后创建并编译项目,最后将程序集添加到数据库中,以便使用它.但仍然是一个有趣的答案. (19认同)
  • @ guillegr123,它不一定要复杂.您可以下载并安装(免费!),SQL#,这是一个SQLCLR函数和过程库.您可以从http://www.SQLsharp.com获取它.是的,我是作者,但String_Split包含在免费版中. (3认同)

小智 10

使用stringvalues()声明怎么样?

DECLARE @str varchar(max)
SET @str = 'Hello John Smith'

DECLARE @separator varchar(max)
SET @separator = ' '

DECLARE @Splited TABLE(id int IDENTITY(1,1), item varchar(max))

SET @str = REPLACE(@str, @separator, '''),(''')
SET @str = 'SELECT * FROM (VALUES(''' + @str + ''')) AS V(A)' 

INSERT INTO @Splited
EXEC(@str)

SELECT * FROM @Splited
Run Code Online (Sandbox Code Playgroud)

达到了结果集.

id  item
1   Hello
2   John
3   Smith
Run Code Online (Sandbox Code Playgroud)


ang*_*gel 9

我使用frederic的答案,但这在SQL Server 2005中不起作用

我修改它,我正在使用selectunion all,它的工作原理

DECLARE @str varchar(max)
SET @str = 'Hello John Smith how are you'

DECLARE @separator varchar(max)
SET @separator = ' '

DECLARE @Splited table(id int IDENTITY(1,1), item varchar(max))

SET @str = REPLACE(@str, @separator, ''' UNION ALL SELECT ''')
SET @str = ' SELECT  ''' + @str + '''  ' 

INSERT INTO @Splited
EXEC(@str)

SELECT * FROM @Splited
Run Code Online (Sandbox Code Playgroud)

结果集是:

id  item
1   Hello
2   John
3   Smith
4   how
5   are
6   you
Run Code Online (Sandbox Code Playgroud)


jos*_*uan 8

这种模式工作正常,你可以概括

Convert(xml,'<n>'+Replace(FIELD,'.','</n><n>')+'</n>').value('(/n[INDEX])','TYPE')
                          ^^^^^                                   ^^^^^     ^^^^
Run Code Online (Sandbox Code Playgroud)

注意FIELD,INDEXTYPE.

让一些带有标识符的表格

sys.message.1234.warning.A45
sys.message.1235.error.O98
....
Run Code Online (Sandbox Code Playgroud)

然后,你可以写

SELECT Source         = q.value('(/n[1])', 'varchar(10)'),
       RecordType     = q.value('(/n[2])', 'varchar(20)'),
       RecordNumber   = q.value('(/n[3])', 'int'),
       Status         = q.value('(/n[4])', 'varchar(5)')
FROM   (
         SELECT   q = Convert(xml,'<n>'+Replace(fieldName,'.','</n><n>')+'</n>')
         FROM     some_TABLE
       ) Q
Run Code Online (Sandbox Code Playgroud)

分裂和铸造所有零件.


Gor*_*ski 7

如果数据库的兼容级别为130或更高,则可以使用STRING_SPLIT函数和OFFSET FETCH子句按索引获取特定项.

要获取索引N(从零开始)的项目,可以使用以下代码

SELECT value
FROM STRING_SPLIT('Hello John Smith',' ')
ORDER BY (SELECT NULL)
OFFSET N ROWS
FETCH NEXT 1 ROWS ONLY
Run Code Online (Sandbox Code Playgroud)

要检查数据库兼容级别,请执行以下代码:

SELECT compatibility_level  
FROM sys.databases WHERE name = 'YourDBName';
Run Code Online (Sandbox Code Playgroud)

  • 这里的问题是STRING_SPLIT不保证返回结果的顺序。因此,您的项目1可能不是我的项目1。 (2认同)

kta*_*kta 6

我正在网上寻找解决方案,以下对我有用. 参考.

你调用这个函数:

SELECT * FROM dbo.split('ram shyam hari gopal',' ')
Run Code Online (Sandbox Code Playgroud)
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

CREATE FUNCTION [dbo].[Split](@String VARCHAR(8000), @Delimiter CHAR(1))       
RETURNS @temptable TABLE (items VARCHAR(8000))       
AS       
BEGIN       
    DECLARE @idx INT       
    DECLARE @slice VARCHAR(8000)        
    SELECT @idx = 1       
    IF len(@String)<1 OR @String IS NULL  RETURN       
    WHILE @idx!= 0       
    BEGIN       
        SET @idx = charindex(@Delimiter,@String)       
        IF @idx!=0       
            SET @slice = LEFT(@String,@idx - 1)       
        ELSE       
            SET @slice = @String       
        IF(len(@slice)>0)  
            INSERT INTO @temptable(Items) VALUES(@slice)       
        SET @String = RIGHT(@String,len(@String) - @idx)       
        IF len(@String) = 0 break       
    END   
    RETURN       
END
Run Code Online (Sandbox Code Playgroud)


Ram*_*asi 6

还有另一个通过分隔函数得到字符串的第n部分:

create function GetStringPartByDelimeter (
    @value as nvarchar(max),
    @delimeter as nvarchar(max),
    @position as int
) returns NVARCHAR(MAX) 
AS BEGIN
    declare @startPos as int
    declare @endPos as int
    set @endPos = -1
    while (@position > 0 and @endPos != 0) begin
        set @startPos = @endPos + 1
        set @endPos = charindex(@delimeter, @value, @startPos)

        if(@position = 1) begin
            if(@endPos = 0)
                set @endPos = len(@value) + 1

            return substring(@value, @startPos, @endPos - @startPos)
        end

        set @position = @position - 1
    end

    return null
end
Run Code Online (Sandbox Code Playgroud)

和用法:

select dbo.GetStringPartByDelimeter ('a;b;c;d;e', ';', 3)
Run Code Online (Sandbox Code Playgroud)

返回:

c
Run Code Online (Sandbox Code Playgroud)


Sei*_*bar 5

试试这个:

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
Run Code Online (Sandbox Code Playgroud)

像这样测试:

select * from SplitWordList('Hello John Smith')
Run Code Online (Sandbox Code Playgroud)


Ale*_*nko 5

以下示例使用递归CTE

更新于 2013年9月18日

CREATE FUNCTION dbo.SplitStrings_CTE(@List nvarchar(max), @Delimiter nvarchar(1))
RETURNS @returns TABLE (val nvarchar(max), [level] int, PRIMARY KEY CLUSTERED([level]))
AS
BEGIN
;WITH cte AS
 (
  SELECT SUBSTRING(@List, 0, CHARINDEX(@Delimiter,  @List + @Delimiter)) AS val,
         CAST(STUFF(@List + @Delimiter, 1, CHARINDEX(@Delimiter, @List + @Delimiter), '') AS nvarchar(max)) AS stval, 
         1 AS [level]
  UNION ALL
  SELECT SUBSTRING(stval, 0, CHARINDEX(@Delimiter, stval)),
         CAST(STUFF(stval, 1, CHARINDEX(@Delimiter, stval), '') AS nvarchar(max)),
         [level] + 1
  FROM cte
  WHERE stval != ''
  )
  INSERT @returns
  SELECT REPLACE(val, ' ','' ) AS val, [level]
  FROM cte
  WHERE val > ''
  RETURN
END
Run Code Online (Sandbox Code Playgroud)

SQLFiddle上演示