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


493

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

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




4
最高位置的答案是-至少对我来说-很老式的和相当过时。程序位置,循环,递归,CLR,函数,多行代码...阅读“活动”答案以找到更多最新方法可能很有趣。
Shnugo

我添加了一个使用最新方法的新答案:stackoverflow.com/a/49669994/632604
Gorgi Rankovski

Answers:


191

您可能会发现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

1
为什么 SET @p_SourceText = RTRIM( LTRIM( @p_SourceText)) SET @w_Length = DATALENGTH( RTRIM( LTRIM( @p_SourceText)))和不 SET @p_SourceText = RTRIM( LTRIM( @p_SourceText)) SET @w_Length = DATALENGTH( @p_SourceText)
贝丝2010年

12
@GateKiller此解决方案不支持Unicode,并且使用了硬编码的数字(18,3),这使其无法成为可行的“可重用”功能。
Filip De Vos

4
这可以工作,但会分配大量内存并浪费CPU。
jjxtra 2015年

2
从SQL Server 2016开始,现在有一个内置函数STRING_SPLIT,该函数将拆分字符串并返回一个单列表结果,您可以在SELECT语句或其他地方使用该结果。
qJake

太糟糕了,我工作的那些家伙不在2016年。但是,我会牢记这一点,以防他们从鞋中脱颖而出。临时解决方案。我将其实现为函数,并添加了定界符作为参数。
布兰登·格里芬

355

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

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

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

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

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


102
谢谢Saul ...我应该指出,此解决方案对于实际开发而言确实是一个糟糕的解决方案。PARSENAME只需要四个部分,因此使用包含四个以上部分的字符串会使它返回NULL。UDF解决方案显然更好。
内森·贝德福德

33
这是一个很棒的技巧,也让我哭泣,对于在真实语言中如此简单的东西来说,像这样的东西是必需的。
Factor Mystic 2010年

36
为了使索引以“正确”的方式工作,即从1开始,我使用REVERSE劫持了您的劫持行为:REVERSE(PARSENAME(REPLACE(REVERSE('Hello John Smith'),'','。') ,1))-返回Hello
NothingsImpossible 2012年

3
@FactorMystic 第一范式要求您不要将多个值放在单个字段中。这实际上是RDBMS的第一条规则。一个SPLIT()没有提供的功能,因为它鼓励穷人的数据库设计,数据库将永远不会被优化使用存储在该格式的数据。RDBMS没有义务帮助开发人员执行其设计无法处理的愚蠢操作。正确的答案始终是“像40年前一样,对数据库进行标准化”。SQL和RDBMS都不归咎于糟糕的设计。
培根片

8
尽管我在理论上同意@BaconBits,但在实践中,当规范由您之前的人制作的较差的设计时,此类工具非常有用。
蒂姆·阿贝尔

110

首先,创建一个函数(使用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

用法保持不变。


14
它很优雅,但由于递归深度的限制,只能用于100个元素。
2012年

4
@Pking,不,默认值为100(防止无限循环)。使用MAXRECURSION提示定义递归级别的数量(0327670为“无限制”-可能会使服务器崩溃)。顺便说一句,答案比更好PARSENAME,因为它是通用的:-)。+1
米哈尔Powaga

添加maxrecursion到此解决方案时,请牢记此问题及其答案:如何maxrecursion在Table-Valued-Function中为CTE 设置选项
米哈尔Powaga

具体来说,请参考Crisfole的答案 -他的方法在某种程度上减慢了速度,但比大多数其他选择要简单。
AHiggins

次要点,但用法并不相同,因为您更改了列名,因此s不再定义
Tim Abell

62

这里的大多数解决方案都使用while循环或递归CTE。我保证,如果您可以使用除空格以外的定界符,那么基于集合的方法将是更好的选择:

CREATE FUNCTION [dbo].[SplitString]
    (
        @List NVARCHAR(MAX),
        @Delim VARCHAR(255)
    )
    RETURNS TABLE
    AS
        RETURN ( SELECT [Value], idx = RANK() OVER (ORDER BY n) FROM 
          ( 
            SELECT n = Number, 
              [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
        );

用法示例:

SELECT Value FROM dbo.SplitString('foo,bar,blat,foo,splunge',',')
  WHERE idx = 3;

结果:

----
blat

您也可以将所需idx的参数添加为函数的参数,但我将其作为练习留给读者。

您不能使用SQL Server 2016中添加的本机STRING_SPLIT函数来执行此操作,因为不能保证输出将按照原始列表的顺序进行呈现。换句话说,如果您通过,3,6,1结果可能会按照该顺序,但可能1,3,6。我在这里要求社区的帮助来改善内置功能:

有了足够的定性反馈,他们实际上可以考虑进行以下一些增强:

有关拆分功能的更多信息,如果拆分来自应用程序层的字符串,为什么(并证明)while循环和递归CTE无法扩展,还有更好的选择:

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


1
最好的答案,恕我直言。在其他一些答案中,存在SQL递归限制为100的问题,但在这种情况下不是这样。非常快速和简单的实现。+2按钮在哪里?
T-moty,2015年

5
我使用逐字尝试这个功能:select * from DBO.SplitString('Hello John smith', ' ');和产生的输出是: 价值你好ELLO LLO罗Ø约翰·翁HNň史密斯MITH第i个^ h
wwmbes

2
@AaronBertrand GateKiller发布的原始问题涉及空格定界符。
wwmbes 16-10-26

1
@ user1255933已解决。
亚伦·伯特兰

1
@Michael是的,是的。如果您没有ALTER SCHEMA权限,那么您也没有表格可供选择;如果您没有SELECT权限,您也将无法从中选择表格。您总是可以要求某人为您创建函数。或在您可以创建它的地方创建它(即使是临时的,例如在tempdb中)。在2016年以后,您应该使用STRING_SPLIT(),而不是必须创建自己的函数。
亚伦·贝特朗

38

您可以利用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]

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


2
IMO的最佳解决方案,其他解决方案都有一定的局限性。这是快速的,可以解析包含许多元素的长字符串。
2012年

为什么要n降序排列?如果有3个项目,并且我们从1开始编号,那么第一个项目将是3,最后一个项目将是1 desc
柴刀-SOverflow

1
同意,在asc方向上会更直观。我遵循使用desc的parsename()约定
Nathan Skerl 2014年

3
一些关于它如何工作的解释将是很棒的
Tim Abell,2016年

在对多达3个字段进行解析的1亿行的测试中,ufn_ParseArray在25分钟后未完成,而REVERSE(PARSENAME(REPLACE(REVERSE('Hello John Smith'), ' ', '.'), 1)) @NothingsImpossible 则在1.5 分钟内完成。@hello_earth您的解决方案如何比较具有4个以上字段的更长字符串?
wwmbes 16-10-28

31

这个问题与字符串分割方法无关,而与如何获取第n个元素有关

这里所有的答案都使用递归做某种类型的字符串分割的,CTE,多发性CHARINDEXREVERSE并且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)')

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

SQL-Server 2016+的更新

遗憾的是,开发人员忘了用来返回零件的索引STRING_SPLIT。但是,使用SQL-Server 2016+,有JSON_VALUEOPENJSON

通过JSON_VALUE我们可以将位置作为索引数组传递。

对于OPENJSON文件明确规定:

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

像这样的字符串只1,2,3需要使用方括号:即可[1,2,3]
一串类似的单词this is an example需要是["this","is","an","example"]
这些是非常简单的字符串操作。只需尝试一下:

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,']'));

-有关位置安全的字符串分隔符(从零开始),请参见以下内容:

SELECT  JsonArray.[key] AS [Position]
       ,JsonArray.[value] AS [Part]
FROM OPENJSON('["' + REPLACE(@str,' ','","') + '"]') JsonArray

这篇文章中,我测试了各种方法并发现,这OPENJSON确实非常快。甚至比著名的“ delimitedSplit8k()”方法要快得多。

更新2-获取值类型安全

我们可以简单地通过使用doubleed 在数组中使用数组[[]]。这允许键入- WITH子句:

DECLARE  @SomeDelimitedString VARCHAR(100)='part1|1|20190920';

DECLARE @JsonArray NVARCHAR(MAX)=CONCAT('[["',REPLACE(@SomeDelimitedString,'|','","'),'"]]');

SELECT @SomeDelimitedString          AS TheOriginal
      ,@JsonArray                    AS TransformedToJSON
      ,ValuesFromTheArray.*
FROM OPENJSON(@JsonArray)
WITH(TheFirstFragment  VARCHAR(100) '$[0]'
    ,TheSecondFragment INT          '$[1]'
    ,TheThirdFragment  DATE         '$[2]') ValuesFromTheArray

回复:如果您的字符串可能包含禁止的字符...,您可以像这样简单地包装子字符串<x><![CDATA[x<&>x]]></x>
Salman A

@SalmanA,是的,-sections CDATA也可以解决这个问题...但是在强制转换之后它们消失了(更改为text()隐式转义)。我不喜欢引擎盖下的魔术,所以我更喜欢(SELECT 'Text with <&>' AS [*] FOR XML PATH(''))-方法。这对我来说看起来更干净,而且还是会发生...(有关CDATA和XML的更多信息)。
Shnugo

22

这是将要执行的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','**')

不适用于dbo.ethos_SplitString_fn('guy,wicks,was here',',')id部分的选择* ----------- ------------ -------------------------------------- 1人2芯
盖伊

2
用len()提防,因为如果它的参数后面有空格,它将不会返回正确的数字。例如len('-')=
2。– Rory

不适用于:从dbo.SplitString('foo,foo test ,,,,, foo',',')中选择*
cbp

1
修复cbp。选择@myString = substring(@ mystring,@ iSpaces + len(@deliminator),len(@myString)-charindex(@ deliminator,@ myString,0))
Alxwest

16

我在这里发布一种简单的解决方法

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


执行这样的功能

  select * from dbo.split('Hello John Smith',' ')

我喜欢这个解决方案。对其进行扩展以根据结果中的指定列返回标量值。
艾伦(Alan)

我在字符串中烧了一个'&'来使用它进行拆分
KeithL

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()));
  }
};

20
我想这太复杂了,因为我需要拥有Visual Studio,然后在服务器上启用CLR,然后创建和编译项目,最后将程序集添加到数据库中才能使用它。但是仍然是一个有趣的答案。
GuillermoGutiérrez2012年

3
@ guillegr123,不必太复杂。您可以下载和安装(免费!)SQL#,它是SQLCLR函数和proc的库。您可以从SQLsharp.com获得它。是的,我是作者,但String_Split包含在免费版本中。
所罗门·鲁兹基

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

取得成果。

id  item
1   Hello
2   John
3   Smith

1
我使用了您的答案,但是没有用,但是我进行了修改,并与所有工会一起使用,我正在使用sql 2005
天使

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

结果集为:

id  item
1   Hello
2   John
3   Smith
4   how
5   are
6   you

我在sql方面见过,这真的很棒,它对我的​​工作很有用,我对此表示感谢,谢谢!
Abdurrahman I.

当我看到它时,我真的很兴奋,因为它看起来很干净而且易于理解,但是不幸的是,由于不能将它放在UDF中EXECEXEC隐式调用存储过程,您不能在UDF中使用存储过程。
克里斯汀·哈马克

这完美地工作!我正在从这里开始使用函数(SplitStrings_Moden):sqlperformance.com/2012/07/t-sql-queries/split-strings#comments执行此操作,花了一个半小时来拆分数据并返回仅使用4个帐号时的行。我用表格上的左连接对您的版本进行了测试,其中包含了帐号数据,大约花了2或3秒钟!巨大的差异,完美无瑕!如果可以的话,我会给这20票!
MattE

8

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

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

注意FIELDINDEXTYPE

让一些带有标识符的表

sys.message.1234.warning.A45
sys.message.1235.error.O98
....

然后,你可以写

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

分割并铸造所有零件。


这是这里唯一允许您转换为特定类型并且效率中等的解决方案(CLR仍然是最高效的,但是此方法在大约9分钟内处理了8gb,10令牌,10M行表(aws m3服务器,4k iops)调配驱动器)
安德鲁·希尔

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

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

SELECT compatibility_level  
FROM sys.databases WHERE name = 'YourDBName';

诀窍在OFFSET 1 ROWS中,它将跳过第一项并返回第二项。如果您的索引是从0开始的,并且@X是保存要获取的项目索引的变量,则可以确定使用OFFSET @X ROWS
Gorgi Rankovski

好的,以前没有使用过...很高兴知道...我仍然更喜欢xml基于-split的方法,因为它允许获取类型安全的值,并且不需要子查询,但这是一个好一个 从我这边+1
Shnugo

3
这里的问题是STRING_SPLIT不保证返回结果的顺序。因此,您的项目1可能是我的项目1,也可能不是
。– user1443098

@GorgiRankovski,使用STRING_SPLITv2016 +版本的需求。在这种情况下,最好使用OPENJSONJSON_VALUE。您可能需要检查一下我的答案
Shnugo '19

6

我一直在寻找在线解决方案,以下内容对我来说很有效。 参考

然后调用这样的函数:

SELECT * FROM dbo.split('ram shyam hari gopal',' ')

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

您无法使用此功能轻松访问第N个项目。
比约恩·林德奎斯特(BjörnLindqvist)

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

和用法:

select dbo.GetStringPartByDelimeter ('a;b;c;d;e', ';', 3)

返回:

c

我喜欢将此解决方案作为返回单个子字符串的选项,而不是获取随后需要从中进行选择的已解析表。使用表结果有其用途,但是对于我所需要的它却可以完美地工作。
James H

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

像这样测试它:

select * from SplitWordList('Hello John Smith')

我经历了它,它完全像我想要的!即使我也可以自定义它,以忽略我选择的特殊字符!
维卡斯

5

以下示例使用递归CTE

更新 18.09.2013

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

关于SQLFiddle的演示


2


    Alter Function dbo.fn_Split
    (
    @Expression nvarchar(max),
    @Delimiter  nvarchar(20) = ',',
    @Qualifier  char(1) = Null
    )
    RETURNS @Results TABLE (id int IDENTITY(1,1), value nvarchar(max))
    AS
    BEGIN
       /* USAGE
            Select * From dbo.fn_Split('apple pear grape banana orange honeydew cantalope 3 2 1 4', ' ', Null)
            Select * From dbo.fn_Split('1,abc,"Doe, John",4', ',', '"')
            Select * From dbo.fn_Split('Hello 0,"&""&&&&', ',', '"')
       */

       -- Declare Variables
       DECLARE
          @X     xml,
          @Temp  nvarchar(max),
          @Temp2 nvarchar(max),
          @Start int,
          @End   int

       -- HTML Encode @Expression
       Select @Expression = (Select @Expression For XML Path(''))

       -- Find all occurences of @Delimiter within @Qualifier and replace with |||***|||
       While PATINDEX('%' + @Qualifier + '%', @Expression) > 0 AND Len(IsNull(@Qualifier, '')) > 0
       BEGIN
          Select
             -- Starting character position of @Qualifier
             @Start = PATINDEX('%' + @Qualifier + '%', @Expression),
             -- @Expression starting at the @Start position
             @Temp = SubString(@Expression, @Start + 1, LEN(@Expression)-@Start+1),
             -- Next position of @Qualifier within @Expression
             @End = PATINDEX('%' + @Qualifier + '%', @Temp) - 1,
             -- The part of Expression found between the @Qualifiers
             @Temp2 = Case When @End < 0 Then @Temp Else Left(@Temp, @End) End,
             -- New @Expression
             @Expression = REPLACE(@Expression,
                                   @Qualifier + @Temp2 + Case When @End < 0 Then '' Else @Qualifier End,
                                   Replace(@Temp2, @Delimiter, '|||***|||')
                           )
       END

       -- Replace all occurences of @Delimiter within @Expression with '</fn_Split><fn_Split>'
       -- And convert it to XML so we can select from it
       SET
          @X = Cast('<fn_Split>' +
                    Replace(@Expression, @Delimiter, '</fn_Split><fn_Split>') +
                    '</fn_Split>' as xml)

       -- Insert into our returnable table replacing '|||***|||' back to @Delimiter
       INSERT @Results
       SELECT
          "Value" = LTRIM(RTrim(Replace(C.value('.', 'nvarchar(max)'), '|||***|||', @Delimiter)))
       FROM
          @X.nodes('fn_Split') as X(C)

       -- Return our temp table
       RETURN
    END

2

您可以在SQL中拆分字符串而无需使用函数:

DECLARE @bla varchar(MAX)
SET @bla = 'BED40DFC-F468-46DD-8017-00EF2FA3E4A4,64B59FC5-3F4D-4B0E-9A48-01F3D4F220B0,A611A108-97CA-42F3-A2E1-057165339719,E72D95EA-578F-45FC-88E5-075F66FD726C'

-- http://stackoverflow.com/questions/14712864/how-to-query-values-from-xml-nodes
SELECT 
    x.XmlCol.value('.', 'varchar(36)') AS val 
FROM 
(
    SELECT 
    CAST('<e>' + REPLACE(@bla, ',', '</e><e>') + '</e>' AS xml) AS RawXml
) AS b 
CROSS APPLY b.RawXml.nodes('e') x(XmlCol);

如果您需要支持任意字符串(带有xml特殊字符)

DECLARE @bla NVARCHAR(MAX)
SET @bla = '<html>unsafe & safe Utf8CharsDon''tGetEncoded ÄöÜ - "Conex"<html>,Barnes & Noble,abc,def,ghi'

-- http://stackoverflow.com/questions/14712864/how-to-query-values-from-xml-nodes
SELECT 
    x.XmlCol.value('.', 'nvarchar(MAX)') AS val 
FROM 
(
    SELECT 
    CAST('<e>' + REPLACE((SELECT @bla FOR XML PATH('')), ',', '</e><e>') + '</e>' AS xml) AS RawXml
) AS b 
CROSS APPLY b.RawXml.nodes('e') x(XmlCol); 

1

我知道这是一个古老的问题,但我认为可以从我的解决方案中受益。

select 
SUBSTRING(column_name,1,CHARINDEX(' ',column_name,1)-1)
,SUBSTRING(SUBSTRING(column_name,CHARINDEX(' ',column_name,1)+1,LEN(column_name))
    ,1
    ,CHARINDEX(' ',SUBSTRING(column_name,CHARINDEX(' ',column_name,1)+1,LEN(column_name)),1)-1)
,SUBSTRING(SUBSTRING(column_name,CHARINDEX(' ',column_name,1)+1,LEN(column_name))
    ,CHARINDEX(' ',SUBSTRING(column_name,CHARINDEX(' ',column_name,1)+1,LEN(column_name)),1)+1
    ,LEN(column_name))
from table_name

SQL字段

优点:

  • 它用''分隔所有3个子字符串分隔符。
  • 一个人不能使用while循环,因为它会降低性能。
  • 无需进行数据透视,因为所有生成的子字符串将显示在一行中

局限性:

  • 必须知道总数。空格(子字符串)。

注意:该解决方案最多可以提供N个子字符串。

为了克服限制,我们可以使用以下ref

但是同样,以上解决方案不能在表中使用(实际上我无法使用它)。

我再次希望该解决方案可以为您提供帮助。

更新:如果“记录”> 50000,则不建议使用,LOOPS因为这会降低性能


1

TVF与递归一起使用的基于纯集合的解决方案CTE。你可以JOINAPPLY此功能的任何数据集。

create function [dbo].[SplitStringToResultSet] (@value varchar(max), @separator char(1))
returns table
as return
with r as (
    select value, cast(null as varchar(max)) [x], -1 [no] from (select rtrim(cast(@value as varchar(max))) [value]) as j
    union all
    select right(value, len(value)-case charindex(@separator, value) when 0 then len(value) else charindex(@separator, value) end) [value]
    , left(r.[value], case charindex(@separator, r.value) when 0 then len(r.value) else abs(charindex(@separator, r.[value])-1) end ) [x]
    , [no] + 1 [no]
    from r where value > '')

select ltrim(x) [value], [no] [index] from r where x is not null;
go

用法:

select *
from [dbo].[SplitStringToResultSet]('Hello John Smith', ' ')
where [index] = 1;

结果:

value   index
-------------
John    1

1

几乎所有其他答案都正在替换要分割的字符串,这浪费了CPU周期并执行了不必要的内存分配。

我在这里介绍了一种更好的方式来拆分字符串:http : //www.digitalruby.com/split-string-sql-server/

这是代码:

SET NOCOUNT ON

-- You will want to change nvarchar(MAX) to nvarchar(50), varchar(50) or whatever matches exactly with the string column you will be searching against
DECLARE @SplitStringTable TABLE (Value nvarchar(MAX) NOT NULL)
DECLARE @StringToSplit nvarchar(MAX) = 'your|string|to|split|here'
DECLARE @SplitEndPos int
DECLARE @SplitValue nvarchar(MAX)
DECLARE @SplitDelim nvarchar(1) = '|'
DECLARE @SplitStartPos int = 1

SET @SplitEndPos = CHARINDEX(@SplitDelim, @StringToSplit, @SplitStartPos)

WHILE @SplitEndPos > 0
BEGIN
    SET @SplitValue = SUBSTRING(@StringToSplit, @SplitStartPos, (@SplitEndPos - @SplitStartPos))
    INSERT @SplitStringTable (Value) VALUES (@SplitValue)
    SET @SplitStartPos = @SplitEndPos + 1
    SET @SplitEndPos = CHARINDEX(@SplitDelim, @StringToSplit, @SplitStartPos)
END

SET @SplitValue = SUBSTRING(@StringToSplit, @SplitStartPos, 2147483647)
INSERT @SplitStringTable (Value) VALUES(@SplitValue)

SET NOCOUNT OFF

-- You can select or join with the values in @SplitStringTable at this point.

0

带有服务器痛苦的递归CTE解决方案,对其进行测试

MS SQL Server 2008架构设置

create table Course( Courses varchar(100) );
insert into Course values ('Hello John Smith');

查询1

with cte as
   ( select 
        left( Courses, charindex( ' ' , Courses) ) as a_l,
        cast( substring( Courses, 
                         charindex( ' ' , Courses) + 1 , 
                         len(Courses ) ) + ' ' 
              as varchar(100) )  as a_r,
        Courses as a,
        0 as n
     from Course t
    union all
      select 
        left(a_r, charindex( ' ' , a_r) ) as a_l,
        substring( a_r, charindex( ' ' , a_r) + 1 , len(a_R ) ) as a_r,
        cte.a,
        cte.n + 1 as n
    from Course t inner join cte 
         on t.Courses = cte.a and len( a_r ) > 0

   )
select a_l, n from cte
--where N = 1

结果

|    A_L | N |
|--------|---|
| Hello  | 0 |
|  John  | 1 |
| Smith  | 2 |

0

虽然类似于josejuan的基于xml的答案,但我发现只处理xml路径一次,然后进行透视操作则效率更高:

select ID,
    [3] as PathProvidingID,
    [4] as PathProvider,
    [5] as ComponentProvidingID,
    [6] as ComponentProviding,
    [7] as InputRecievingID,
    [8] as InputRecieving,
    [9] as RowsPassed,
    [10] as InputRecieving2
    from
    (
    select id,message,d.* from sysssislog cross apply       ( 
          SELECT Item = y.i.value('(./text())[1]', 'varchar(200)'),
              row_number() over(order by y.i) as rn
          FROM 
          ( 
             SELECT x = CONVERT(XML, '<i>' + REPLACE(Message, ':', '</i><i>') + '</i>').query('.')
          ) AS a CROSS APPLY x.nodes('i') AS y(i)
       ) d
       WHERE event
       = 
       'OnPipelineRowsSent'
    ) as tokens 
    pivot 
    ( max(item) for [rn] in ([3],[4],[5],[6],[7],[8],[9],[10]) 
    ) as data

在8:30跑了

select id,
tokens.value('(/n[3])', 'varchar(100)')as PathProvidingID,
tokens.value('(/n[4])', 'varchar(100)') as PathProvider,
tokens.value('(/n[5])', 'varchar(100)') as ComponentProvidingID,
tokens.value('(/n[6])', 'varchar(100)') as ComponentProviding,
tokens.value('(/n[7])', 'varchar(100)') as InputRecievingID,
tokens.value('(/n[8])', 'varchar(100)') as InputRecieving,
tokens.value('(/n[9])', 'varchar(100)') as RowsPassed
 from
(
    select id, Convert(xml,'<n>'+Replace(message,'.','</n><n>')+'</n>') tokens
         from sysssislog 
       WHERE event
       = 
       'OnPipelineRowsSent'
    ) as data

在9:20跑了


0
CREATE FUNCTION [dbo].[fnSplitString] 
( 
    @string NVARCHAR(MAX), 
    @delimiter CHAR(1) 
) 
RETURNS @output TABLE(splitdata NVARCHAR(MAX) 
) 
BEGIN 
    DECLARE @start INT, @end INT 
    SELECT @start = 1, @end = CHARINDEX(@delimiter, @string) 
    WHILE @start < LEN(@string) + 1 BEGIN 
        IF @end = 0  
            SET @end = LEN(@string) + 1

        INSERT INTO @output (splitdata)  
        VALUES(SUBSTRING(@string, @start, @end - @start)) 
        SET @start = @end + 1 
        SET @end = CHARINDEX(@delimiter, @string, @start)

    END 
    RETURN 
END

和使用它

select *from dbo.fnSplitString('Querying SQL Server','')

0

如果有人只想获得一部分分隔文本,则可以使用此功能

选择* fromSplitStringSep('Word1 wordr2 word3','')

CREATE function [dbo].[SplitStringSep] 
(
    @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
  )

0

我大力发展

declare @x nvarchar(Max) = 'ali.veli.deli.';
declare @item nvarchar(Max);
declare @splitter char='.';

while CHARINDEX(@splitter,@x) != 0
begin
    set @item = LEFT(@x,CHARINDEX(@splitter,@x))
    set @x    = RIGHT(@x,len(@x)-len(@item) )
     select @item as item, @x as x;
end

唯一应注意的是点'。@x的末端始终应该在那里。


0

建立在@NothingsImpossible解决方案的基础上,或者说,对投票最多的答案(仅在公认的答案以下)发表评论,我发现以下快速而又肮脏的解决方案可以满足我自己的需求-它具有完全位于SQL域中的优势。

给定一个字符串“ first; second; third; thingth; fifth”,例如,我想获得第三个标记。仅当我们知道该字符串将具有多少个标记时,此方法才有效-在这种情况下为5。因此,我的操作方式是将最后两个标记切掉(内部查询),然后将前两个标记切掉(外部查询)

我知道这很丑陋,并且涵盖了我所处的特定条件,但是为了防止有人发现它有用而将其发布。干杯

select 
    REVERSE(
        SUBSTRING(
            reverse_substring, 
            0, 
            CHARINDEX(';', reverse_substring)
        )
    ) 
from 
(
    select 
        msg,
        SUBSTRING(
            REVERSE(msg), 
            CHARINDEX(
                ';', 
                REVERSE(msg), 
                CHARINDEX(
                    ';',
                    REVERSE(msg)
                )+1
            )+1,
            1000
        ) reverse_substring
    from 
    (
        select 'first;second;third;fourth;fifth' msg
    ) a
) b

仅当我们知道该字符串将具有多少个标记时,这才起作用 -一个突破性的限制...
Shnugo

0
declare @strng varchar(max)='hello john smith'
select (
    substring(
        @strng,
        charindex(' ', @strng) + 1,
        (
          (charindex(' ', @strng, charindex(' ', @strng) + 1))
          - charindex(' ',@strng)
        )
    ))

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.