如何拆分分隔字符串以便访问单个项目?

How do I split a delimited string so I can access individual items?

提问人:GateKiller 提问时间:8/6/2008 最后编辑:miken32GateKiller 更新时间:6/27/2023 访问量:938344

问:

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

取一个字符串“你好约翰·史密斯”。如何按空格拆分字符串并访问索引 1 处应返回“John”的项目?

sql-server t-sql 拆分

评论

3赞 Jarrod Dixon 3/9/2010
另见 stackoverflow.com/questions/314824/...
5赞 Shnugo 7/12/2016
这里的最高答案是——至少对我来说——相当老式且相当过时。过程轨迹、循环、递归、CLR、函数、多行代码......阅读“积极”的答案以找到更多最新的方法可能会很有趣。
0赞 Gorgi Rankovski 4/5/2018
我添加了一个具有最新方法的新答案:stackoverflow.com/a/49669994/632604
0赞 José Diz 9/4/2019
尝试获取列表的第 n 个元素 -> portosql.wordpress.com/2019/05/27/enesimo-elemento-lista
1赞 Greg 1/7/2021
@TimAbell,文档指出“不能保证顺序与输入字符串中子字符串的顺序匹配”。

答:

194赞 Jonesinator 8/6/2008 #1

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

评论

1赞 Beth 9/29/2010
为什么而不是?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)
12赞 Filip De Vos 3/18/2011
@GateKiller 此解决方案不支持 Unicode,它使用硬编码的 numeric(18,3),这并不能使其成为可行的“可重用”函数。
4赞 jjxtra 5/27/2015
这可行,但会分配大量内存并浪费 CPU。
2赞 qJake 4/4/2017
从 SQL Server 2016 开始,现在有一个内置函数STRING_SPLIT,它将拆分字符串并返回可在语句或其他位置使用的单列表结果。SELECT
0赞 Brandon Griffin 5/3/2017
可惜我工作的人不在 2016 年。但是,我会记住这一点,以防他们从鞋子里弄出来。在此期间提供了很好的解决方案。我将其实现为一个函数,并添加了分隔符作为参数。
-1赞 Dillie-O 8/6/2008 #2

好吧,我的并不是那么简单,但这是我用来将逗号分隔的输入变量拆分为单个值的代码,并将其放入表变量中。我敢肯定,您可以稍微修改一下以基于空格进行拆分,然后对该表变量执行基本的 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 中编写一个简单的方法,该方法将拆分字符串,然后将其公开为存储过程/函数。

评论

0赞 cstruter 1/28/2011
可以在 .NET 中执行此操作的示例(CLR 过程/函数)可在此处找到 cstruter.com/blog/260
23赞 brendan 8/6/2008 #3

这是一个可以做到这一点的 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','**')

评论

0赞 Guy 10/20/2008
不适用于从 dbo.ethos_SplitString_fn('guy,wicks,was here',',') id part ----------- -------------------------------------------------- 1 guy 2 wick
2赞 Rory 10/18/2009
注意 len(),因为如果它的参数有尾随空格,它将不会返回正确的数字,例如 len(' - ') = 2。
0赞 cbp 4/14/2010
不适用于:select * from dbo。SplitString('foo,foo 测试,,,,foo',',')
1赞 Alxwest 5/21/2012
修复 cbp..选择 @myString = substring(@mystring,@iSpaces + len(@deliminator),len(@myString) - charindex(@deliminator,@myString,0))
6赞 Seibar 8/6/2008 #4

试试这个:

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

评论

0赞 Vikas 9/15/2010
我已经经历过了,它完全像我想要的!甚至我也可以自定义它以忽略我选择的特殊字符!
368赞 Nathan Bedford 8/6/2008 #5

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

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

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

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

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

评论

104赞 Nathan Bedford 7/1/2009
谢谢扫罗...我应该指出,这个解决方案对于真正的开发来说确实是一个糟糕的解决方案。PARSENAME 只需要四个部分,因此使用包含四个以上部分的字符串会导致它返回 NULL。UDF 解决方案显然更好。
33赞 Factor Mystic 7/12/2010
这是一个很棒的黑客,也让我哭泣,这样的东西对于在真实语言中如此简单的东西是必要的。
37赞 NothingsImpossible 5/14/2012
为了使索引以“正确”的方式工作,也就是说,从 1 开始,我用 REVERSE(REVERSE(PARSENAME(REPLACE(REVERSE('Hello John Smith'), ' ', '.'), 1)) -- 返回 Hello
4赞 Bacon Bits 12/8/2014
@FactorMystic第一范式形式要求您不要在单个字段中放置多个值。从字面上看,这是RDBMS的第一条规则。未提供函数,因为它会鼓励糟糕的数据库设计,并且数据库永远不会被优化为使用以这种格式存储的数据。RDBMS没有义务帮助开发人员做一些愚蠢的事情,而这些事情本来就不是为了处理。正确答案永远是“像我们 40 年前告诉你的那样规范化你的数据库”。SQL和RDBMS都不应该归咎于糟糕的设计。SPLIT()
11赞 Tim Abell 6/24/2016
@BaconBits虽然我在理论上同意,但在实践中,像这样的工具在规范化你之前的人制作的糟糕设计时很有用。
111赞 vzczc 8/6/2008 #6

首先,创建一个函数(使用 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赞 Pking 11/7/2012
它很优雅,但由于递归深度的限制,仅适用于 100 个元素。
4赞 Michał Powaga 3/14/2013
@Pking,不,默认值是(以防止无限循环)。使用 MAXRECURSION 提示来定义递归级别的数量(to ,是“无限制” - 可能会压垮服务器)。顺便说一句,答案比 好得多,因为它是通用的:-)。+11000327670PARSENAME
0赞 Michał Powaga 3/15/2013
在此解决方案中,请记住这个问题及其答案:如何在表值函数中为 CTE 设置 maxrecursion 选项maxrecursion
0赞 AHiggins 7/31/2015
具体来说,参考 Crisfole 的答案 - 他的方法在一定程度上减慢了它的速度,但比大多数其他选项更简单。
0赞 Tim Abell 6/24/2016
次要点,但用法不会保持不变,因为您更改了列名称,因此不再定义s
39赞 nathan_jr 10/28/2008 #7

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

用法(在我的笔记本电脑上在 3 秒内输出 40mil 行)

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

清理

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

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

评论

2赞 Pking 12/6/2012
最好的解决方案IMO,其他的都有某种限制..这速度很快,可以解析包含许多元素的长字符串。
0赞 hatchet - done with SOverflow 10/29/2014
为什么降序?如果有三个项目,我们从 1 开始编号,那么第一个项目将是数字 3,最后一个将是数字 1。如果删除它,它不会提供更直观的结果吗?desc
1赞 nathan_jr 10/29/2014
同意,在asc方向上会更直观。我遵循使用 desc 的 parsename() 约定
3赞 Tim Abell 6/24/2016
关于这是如何工作的解释会很棒
0赞 wwmbes 10/28/2016
在对多达 3 个字段的 1 亿行进行解析的测试中,ufn_ParseArray 在 25 分钟后没有完成,而 @NothingsImpossible 在 1.5 分钟内完成。@hello_earth 您的解决方案在具有 4 个以上字段的较长字符串上如何比较?REVERSE(PARSENAME(REPLACE(REVERSE('Hello John Smith'), ' ', '.'), 1))
6赞 kta 11/20/2011 #8

我一直在网上寻找解决方案,下面对我有用。编号

你像这样调用函数:

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

评论

0赞 Stand with Gaza 10/3/2014
使用此功能无法轻松访问第 N 项。
10赞 Damon Drake 7/20/2012 #9

在我看来,你们把事情搞得太复杂了。只需创建一个 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赞 Guillermo Gutiérrez 9/27/2012
我想这太复杂了,因为我需要有Visual Studio,然后在服务器上启用CLR,然后创建和编译项目,最后将程序集添加到数据库中,以便使用它。但仍然是一个有趣的答案。
3赞 Solomon Rutzky 8/19/2013
@guillegr123,它不必很复杂。您可以免费下载和安装 SQL#,这是一个 SQLCLR 函数和过程库。您可以从 SQLsharp.com .是的,我是作者,但String_Split包含在免费版本中。
1赞 Prahalad Gaggar 1/24/2013 #10

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

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 FIDDLE

优势:

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

局限性:

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

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

为了克服这个限制,我们可以使用以下参考

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

我再次希望这个解决方案可以帮助一些人。

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

16赞 Sivaganesh Tamilvendhan 1/30/2013 #11

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

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

评论

0赞 Alan 2/23/2013
我喜欢这个解决方案。展开它以根据结果中的指定列返回标量值。
0赞 KeithL 11/14/2018
我被字符串中的“&”烧毁了,要用这个拆分
10赞 Frederic 3/2/2013 #12

使用和声明呢?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赞 angel 8/13/2013
我使用了您的答案但没有用,但是我进行了修改,这适用于Union All,我正在使用SQL 2005
5赞 Oleksandr Fedorenko 3/14/2013 #13

以下示例使用递归 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赞 mkaj 6/20/2013 #14

这是我的解决方案,可能会对某人有所帮助。修改了上面 Jonesinator 的答案。

如果我有一串带分隔符的 INT 值,并且想要返回一个 INT 表(然后我可以加入)。例如“1,20,3,343,44,6,8765”

创建 UDF:

IF OBJECT_ID(N'dbo.ufn_GetIntTableFromDelimitedList', N'TF') IS NOT NULL
    DROP FUNCTION dbo.[ufn_GetIntTableFromDelimitedList];
GO

CREATE FUNCTION dbo.[ufn_GetIntTableFromDelimitedList](@String NVARCHAR(MAX),                 @Delimiter CHAR(1))

RETURNS @table TABLE 
(
    Value INT NOT NULL
)
AS 
BEGIN
DECLARE @Pattern NVARCHAR(3)
SET @Pattern = '%' + @Delimiter + '%'
DECLARE @Value NVARCHAR(MAX)

WHILE LEN(@String) > 0
    BEGIN
        IF PATINDEX(@Pattern, @String) > 0
        BEGIN
            SET @Value = SUBSTRING(@String, 0, PATINDEX(@Pattern, @String))
            INSERT INTO @table (Value) VALUES (@Value)

            SET @String = SUBSTRING(@String, LEN(@Value + @Delimiter) + 1, LEN(@String))
        END
        ELSE
        BEGIN
            -- Just the one value.
            INSERT INTO @table (Value) VALUES (@String)
            RETURN
        END
    END

RETURN
END
GO

然后得到表格结果:

SELECT * FROM dbo.[ufn_GetIntTableFromDelimitedList]('1,20,3,343,44,6,8765', ',')

1
20
3
343
44
6
8765

在加入声明中:

SELECT [ID], [FirstName]
FROM [User] u
JOIN dbo.[ufn_GetIntTableFromDelimitedList]('1,20,3,343,44,6,8765', ',') t ON u.[ID] = t.[Value]

1    Elvis
20   Karen
3    David
343  Simon
44   Raj
6    Mike
8765 Richard

如果要返回 NVARCHAR 列表而不是 INT,则只需更改表定义:

RETURNS @table TABLE 
(
    Value NVARCHAR(MAX) NOT NULL
)
9赞 angel 8/13/2013 #15

我使用了 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

评论

0赞 Abdurrahman I. 3/24/2016
这真的很棒,我在 sql 中见过的东西,它对我的工作有用,我很感激,谢谢!
0赞 Kristen Hammack 8/10/2016
当我看到它时,我真的很兴奋,因为它看起来很干净,很容易理解,但不幸的是,你不能把它放在 UDF 中,因为 . 隐式调用存储过程,并且不能在 UDF 中使用存储过程。EXECEXEC
0赞 MattE 3/9/2019
这效果很好!我正在研究从这里使用函数(SplitStrings_Moden):sqlperformance.com/2012/07/t-sql-queries/split-strings#comments 这样做,并且仅使用 4 个帐号时需要一分半钟才能拆分数据并返回行。我用表上的左联接和帐号数据测试了你的版本,花了 2 或 3 秒!巨大的差异,完美无缺!如果可能的话,我会给这 20 票!
0赞 Lomak 6/1/2022
容易受到 sql 注入的影响
2赞 T-Rex 11/5/2013 #16


    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

73赞 Aaron Bertrand 11/13/2013 #17

这里的大多数解决方案都使用 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

您也可以将 you want 作为参数添加到函数中,但我会将其作为练习留给读者。idx

不能STRING_SPLIT使用 SQL Server 2016SQL Server 2016SQL Server 2016SQL Server 2016SQL Server 2016SQL Server 2016SQL Server 2016SQL Server 2016SQL Server 2016SQL Server 2016SQL Server 2016SQL Server 2016SQL Server 2016SQL Server 2016SQL Server 2016SQL Server 2016SQL Server 2016SQL Server 2016SQL Server 2016SQL Server 2016SQL Server 2016SQL Server 201换句话说,如果传入,结果可能会按该顺序排列,但也可能是 .我在这里请求社区帮助改进内置功能:3,6,11,3,6

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

更多关于拆分函数的信息,为什么(并证明)循环和递归 CTE 无法扩展,以及更好的替代方案,如果拆分来自应用层的字符串:

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

评论

1赞 T-moty 10/21/2015
最佳答案,恕我直言。在其他一些答案中,存在 SQL 递归限制为 100 的问题,但在这种情况下不是。非常快速和非常简单的实现。+2 按钮在哪里?
5赞 wwmbes 10/11/2016
我逐字尝试了这个函数的用法: 产生的输出是: Value Hello ello llo lo o John ohn hn n smith mith th hselect * from DBO.SplitString('Hello John smith', ' ');
2赞 wwmbes 10/26/2016
@AaronBertrand GateKiller 发布的原始问题涉及空格分隔符。
1赞 Aaron Bertrand 12/18/2019
@user1255933解决了这个问题。
1赞 Aaron Bertrand 2/27/2020
@Michael 是的,这是真的。如果您没有 ALTER SCHEMA 权限,您也不会有可供选择的表,如果您没有 SELECT 权限,则无法从中进行选择 您可以随时要求某人为您创建函数。或者在可以创建它的位置创建它(即使是临时的,比如在 tempdb 中)。在 2016+ 上,您应该使用 STRING_SPLIT(),而不是您必须自己创建的函数。
0赞 dani herrera 1/16/2014 #18

具有服务器痛点的递归 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 |
-2赞 Matt Watson 3/20/2014 #19

这是一个 SQL UDF,它可以拆分一个字符串并只抓取某个部分。

create FUNCTION [dbo].[udf_SplitParseOut]
(
    @List nvarchar(MAX),
    @SplitOn nvarchar(5),
    @GetIndex smallint
)  
returns varchar(1000)
AS  

BEGIN

DECLARE @RtnValue table 
(

    Id int identity(0,1),
    Value nvarchar(MAX)
) 


    DECLARE @result varchar(1000)

    While (Charindex(@SplitOn,@List)>0)
    Begin
        Insert Into @RtnValue (value)
        Select Value = ltrim(rtrim(Substring(@List,1,Charindex(@SplitOn,@List)-1)))
        Set @List = Substring(@List,Charindex(@SplitOn,@List)+len(@SplitOn),len(@List))
    End

    Insert Into @RtnValue (Value)
    Select Value = ltrim(rtrim(@List))

    select @result = value from @RtnValue where ID = @GetIndex

    Return @result
END
-2赞 Mohsen 5/1/2014 #20

一个简单的优化算法:

ALTER FUNCTION [dbo].[Split]( @Text NVARCHAR(200),@Splitor CHAR(1) )
RETURNS @Result TABLE ( value NVARCHAR(50)) 
AS
BEGIN
    DECLARE @PathInd INT
    Set @Text+=@Splitor
    WHILE LEN(@Text) > 0
    BEGIN
        SET @PathInd=PATINDEX('%'+@Splitor+'%',@Text)
        INSERT INTO  @Result VALUES(SUBSTRING(@Text, 0, @PathInd))
        SET @Text= SUBSTRING(@Text, @PathInd+1, LEN(@Text))
    END
        RETURN 
END
-2赞 Katherine Elizabeth Lightsey 8/20/2014 #21

一段时间以来,我一直在使用递归 cte 使用 vzczc 的答案,但一直想更新它以处理可变长度分隔符,以及处理带有前导和滞后“分隔符”的字符串,例如当您有一个带有记录的 csv 文件时:

“鲍勃”,“史密斯”,“桑尼维尔”,“CA”

或者当您处理六个部分的 FQN 时,如下所示。我广泛使用它们来记录subject_fqn,以便进行审计、错误处理等,parsename 只处理四个部分:

[netbios_name].[machine_name].[instance].[database].[schema].[table].[column]

这是我的更新版本,感谢 vzczc 的原始帖子!

select * from [utility].[split_string](N'"this"."string"."gets"."split"."and"."removes"."leading"."and"."trailing"."quotes"', N'"."', N'"', N'"');

select * from [utility].[split_string](N'"this"."string"."gets"."split"."but"."leaves"."leading"."and"."trailing"."quotes"', N'"."', null, null);

select * from [utility].[split_string](N'[netbios_name].[machine_name].[instance].[database].[schema].[table].[column]', N'].[', N'[', N']');

create function [utility].[split_string] ( 
  @input       [nvarchar](max) 
  , @separator [sysname] 
  , @lead      [sysname] 
  , @lag       [sysname]) 
returns @node_list table ( 
  [index]  [int] 
  , [node] [nvarchar](max)) 
  begin 
      declare @separator_length [int]= len(@separator) 
              , @lead_length    [int] = isnull(len(@lead), 0) 
              , @lag_length     [int] = isnull(len(@lag), 0); 
      -- 
      set @input = right(@input, len(@input) - @lead_length); 
      set @input = left(@input, len(@input) - @lag_length); 
      -- 
      with [splitter]([index], [starting_position], [start_location]) 
           as (select cast(@separator_length as [bigint]) 
                      , cast(1 as [bigint]) 
                      , charindex(@separator, @input) 
               union all 
               select [index] + 1 
                      , [start_location] + @separator_length 
                      , charindex(@separator, @input, [start_location] + @separator_length) 
               from   [splitter] 
               where  [start_location] > 0) 
      -- 
      insert into @node_list 
                  ([index],[node]) 
        select [index] - @separator_length                   as [index] 
               , substring(@input, [starting_position], case 
                                                            when [start_location] > 0 
                                                                then 
                                                              [start_location] - [starting_position] 
                                                            else 
                                                              len(@input) 
                                                        end) as [node] 
        from   [splitter]; 
      -- 
      return; 
  end; 
go 
1赞 jjxtra 8/27/2014 #22

几乎所有其他答案都在替换被拆分的字符串,这会浪费 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.
8赞 josejuan 11/11/2014 #23

此模式工作正常,您可以泛化

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

注意 FIELD、INDEXTYPE

让一些带有类似标识符的表

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

劈裂和铸造所有零件。

评论

0赞 Andrew Hill 12/8/2014
这是这里唯一允许您强制转换为特定类型的解决方案,并且效率适中(CLR 仍然是最有效的,但此方法在大约 9 分钟内处理 8GB、10 个令牌、10M 行表(aws m3 服务器,4k iops 预置驱动器)
0赞 Andrew Hill 12/8/2014 #24

虽然与 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赞 Savas Adar 12/20/2014 #25
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','')
1赞 Andrey Morozov 1/13/2015 #26

使用递归的纯基于集合的解决方案。您可以将此函数添加到任何数据集。TVFCTEJOINAPPLY

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
0赞 nazim hatipoglu 2/13/2015 #27

如果有人只想获得分隔文本的一部分,可以使用这个

select * from 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赞 Ali CAKIL 10/15/2015 #28

我对此进行了解释,

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的那一端总是应该在那里。

2赞 Stefan Steiger 10/23/2015 #29

您可以在 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); 
8赞 Ramazan Binarbasi 1/8/2016 #30

另一个通过分界函数获取字符串的第 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

评论

0赞 James H 3/30/2016
我喜欢这个解决方案作为返回单个子字符串的选项,而不是获取一个解析的表,然后你需要从中进行选择。使用表格结果有其用途,但对于我需要的东西,这非常有效。
40赞 Shnugo 7/9/2016 #31

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

这里的所有答案都是使用递归、s、multiple 和 、 发明函数、调用 CLR 方法、数字表、s ...大多数答案涵盖多行代码。CTECHARINDEXREVERSEPATINDEXCROSS APPLY

但是 - 如果你真的只想要一种方法来获取第 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+ 更新

遗憾的是,开发人员忘记返回带有 .但是,使用 SQL-Server 2016+ 时,有 和 .STRING_SPLITJSON_VALUEOPENJSON

有了 我们可以将位置作为索引数组传入。JSON_VALUE

对于文档明确指出:OPENJSON

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

像这样的字符串只需要括号:.
一串单词,如 need to be .
这些都是非常简单的字符串操作。试试看:
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

在这篇文章中,我测试了各种方法,发现这真的很快。甚至比著名的“delimitedSplit8k()”方法快得多......OPENJSON

更新 2 - 获取类型安全值

我们可以简单地使用 doubled 来使用数组中的数组。这允许类型化的 -clause:[[]]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

评论

0赞 Salman A 1/28/2019
回复:如果您的字符串可能包含禁止的字符...您可以简单地像这样包装子字符串。<x><![CDATA[x<&>x]]></x>
0赞 Shnugo 1/28/2019
@SalmanA,是的,-sections 也可以处理这个问题......但是在演员阵容之后,他们就消失了(改为隐含地逃脱了)。我不喜欢引擎盖下的魔法,所以我更喜欢 - 方法。这在我看来更干净,无论如何都会发生......(有关 CDATA 和 XML 的更多信息)。CDATAtext()(SELECT 'Text with <&>' AS [*] FOR XML PATH(''))
0赞 Martin Smith 12/22/2023
在JSON版本中执行之前,应调用 dbfiddle.uk/mWShPwRPREPLACESTRING_ESCAPE(@SomeDelimitedString, 'JSON')
0赞 Smart003 7/14/2016 #32
declare @strng varchar(max)='hello john smith'
select (
    substring(
        @strng,
        charindex(' ', @strng) + 1,
        (
          (charindex(' ', @strng, charindex(' ', @strng) + 1))
          - charindex(' ',@strng)
        )
    ))
0赞 hello_earth 10/31/2016 #33

基于@NothingsImpossible解决方案,或者更确切地说,对得票最多的答案(就在接受的答案下方)发表评论,我发现以下快速而肮脏的解决方案满足了我自己的需求 - 它的好处是仅在 SQL 域中。

给定一个字符串“first;第二;第三;第四;第五“,比如说,我想得到第三个代币。只有当我们知道字符串将有多少个标记时,这才有效 - 在本例中为 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

评论

0赞 Shnugo 4/9/2018
只有当我们知道字符串将有多少个令牌时,这才有效 - 一个突破性限制......
0赞 Victor Hugo Terceros 9/5/2017 #34

SQL Server 2016 开始,我们string_split

DECLARE @string varchar(100) = 'Richard, Mike, Mark'

SELECT value FROM string_split(@string, ',')

评论

0赞 Johnie Karr 12/28/2017
这很好,但它并没有解决获得第 n 个结果的问题。
0赞 Shnugo 9/14/2018
STRING_SPLIT不保证退回相同的订单。但是确实(见我的答案(更新部分)OPENJSON)
0赞 uzr 1/2/2018 #35

使用 STRING_SPLIT 的新式方法需要 SQL Server 2016 及更高版本。

DECLARE @string varchar(100) = 'Hello John Smith'

SELECT
    ROW_NUMBER() OVER (ORDER BY value) AS RowNr,
    value
FROM string_split(@string, ' ')

结果:

RowNr   value
1       Hello
2       John
3       Smith

现在可以从行号中获取第 n 个元素。

评论

2赞 Shnugo 9/14/2018
STRING_SPLIT不保证退回相同的订单。但是确实(见我的答案(更新部分)OPENJSON)
0赞 zipppy 3/22/2018 #36

亚伦·伯特兰(Aaron Bertrand)的回答很好,但有缺陷。它不能准确地将空格作为分隔符处理(就像原始问题中的示例一样),因为 length 函数会去除尾随空格。

以下是他的代码,但进行了小幅调整以允许使用空格分隔符:

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+'x')-1) = @Delim
      ) AS y
    );
7赞 Gorgi Rankovski 4/5/2018 #37

如果数据库的兼容级别为 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';

评论

0赞 Gorgi Rankovski 4/9/2018
诀窍在于 OFFSET 1 ROWS,它将跳过第一项并返回第二项。如果您的索引是从 0 开始的,并且 @X 是保存要获取的项目索引的变量,则可以肯定地执行 OFFSET @X ROWS
0赞 Shnugo 4/9/2018
好吧,以前没有用过这个......很高兴知道...我仍然更喜欢基于 -split 的方法,因为它允许获取值类型安全并且不需要子查询,但这是一个很好的方法。+1 从我这边xml
3赞 user1443098 4/8/2019
这里的问题是STRING_SPLIT不能保证返回结果的顺序。因此,您的项目 1 可能是也可能不是我的项目 1。
0赞 Shnugo 7/1/2019
@GorgiRankovski,使用 v2016+ 的需求。在这种情况下,最好使用 或 .你可能想看看我的答案STRING_SPLITOPENJSONJSON_VALUE
1赞 VinceL 4/27/2018 #38

下面是一个函数,它将完成拆分字符串和访问项目 X 的问题目标:

CREATE FUNCTION [dbo].[SplitString]
(
   @List       VARCHAR(MAX),
   @Delimiter  VARCHAR(255),
   @ElementNumber INT
)
RETURNS VARCHAR(MAX)
AS
BEGIN

       DECLARE @inp VARCHAR(MAX)
       SET @inp = (SELECT REPLACE(@List,@Delimiter,'_DELMTR_') FOR XML PATH(''))

       DECLARE @xml XML
       SET @xml = '<split><el>' + REPLACE(@inp,'_DELMTR_','</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

用法:

SELECT dbo.SplitString('Hello John Smith', ' ', 2)

结果:

John

评论

0赞 Shnugo 6/29/2018
这很复杂......不需要 .你可以直接把放进去(见我的回答)。顺便说一句:标量函数的性能非常差。更好的是内联 TVF,即使它只返回一行中的一个单元格.......nodes()XQuery.value()
0赞 adudley 7/21/2023
这很慢,但它确实有效,谢谢。[与 ChatGPT 吐出的垃圾不同.. 也许 stackoverflow 需要一个新名字,因为当 ChatGPT 不能时,Stackoverflow 可以]
0赞 Sam K 8/21/2018 #39

解析名字和姓氏的简单解决方案

DECLARE @Name varchar(10) = 'John Smith'

-- Get First Name
SELECT SUBSTRING(@Name, 0, (SELECT CHARINDEX(' ', @Name)))

-- Get Last Name
SELECT SUBSTRING(@Name, (SELECT CHARINDEX(' ', @Name)) + 1, LEN(@Name))

就我而言(在许多其他情况下,似乎......),我有一个名字和姓氏列表,用一个空格分隔。这可以直接在 select 语句中用于解析名字和姓氏。

-- i.e. Get First and Last Name from a table of Full Names
SELECT SUBSTRING(FullName, 0, (SELECT CHARINDEX(' ', FullName))) as FirstName,
SUBSTRING(FullName, (SELECT CHARINDEX(' ', FullName)) + 1, LEN(FullName)) as LastName,
From FullNameTable
0赞 GGadde 9/18/2018 #40

我知道它很晚,但我最近有这个要求并提出了以下代码。我没有选择使用用户定义的函数。希望这会有所帮助。

SELECT 
    SUBSTRING(
                SUBSTRING('Hello John Smith' ,0,CHARINDEX(' ','Hello John Smith',CHARINDEX(' ','Hello John Smith')+1)
                        ),CHARINDEX(' ','Hello John Smith'),LEN('Hello John Smith')
            )
-1赞 Dave Mason 5/7/2019 #41

我意识到这是一个非常古老的问题,但从 SQL Server 2016 开始,有一些用于解析 JSON 数据的函数可用于专门解决 OP 的问题,而无需拆分字符串或求助于用户定义的函数。若要访问分隔字符串的特定索引处的项目,请使用该函数。但是,需要格式正确的JSON数据:字符串必须用双引号括起来,分隔符必须是逗号,整个字符串用方括号括起来。JSON_VALUE",[]

DECLARE @SampleString NVARCHAR(MAX) = '"Hello John Smith"';
--Format as JSON data.
SET @SampleString = '[' + REPLACE(@SampleString, ' ', '","') + ']';
SELECT 
    JSON_VALUE(@SampleString, '$[0]') AS Element1Value,
    JSON_VALUE(@SampleString, '$[1]') AS Element2Value,
    JSON_VALUE(@SampleString, '$[2]') AS Element3Value;

输出

Element1Value         Element2Value       Element3Value
--------------------- ------------------- ------------------------------
Hello                 John                Smith

(1 row affected)
-1赞 GBGOLC 11/2/2019 #42

使用 SQL Server 2016 及更高版本。使用此代码对字符串执行 TRIM 操作,忽略 NULL 值,并按正确的顺序应用行索引。它还适用于空格分隔符:

DECLARE @STRING_VALUE NVARCHAR(MAX) = 'one, two,,three, four,     five'

SELECT ROW_NUMBER() OVER (ORDER BY R.[index]) [index], R.[value] FROM
(
    SELECT
        1 [index], NULLIF(TRIM([value]), '') [value] FROM STRING_SPLIT(@STRING_VALUE, ',') T
    WHERE
        NULLIF(TRIM([value]), '') IS NOT NULL
) R
-1赞 Eralper 2/22/2020 #43

如果您查看以下有关使用 SQL 拆分字符串的 SQL 教程,您将发现许多可用于在 SQL Server 上拆分给定字符串的函数

例如,SplitAndReturnNth UDF 函数可用于使用分隔符拆分文本,并将第 N 个片段作为函数的输出返回

select dbo.SplitAndReturnNth('Hello John Smith',' ',2)

enter image description here

0赞 Ludovic Aubert 5/6/2021 #44
CREATE TABLE test(
    id int,
    adress varchar(100)
);
INSERT INTO test VALUES(1, 'Ludovic Aubert, 42 rue de la Victoire, 75009, Paris, France'),(2, 'Jose Garcia, 1 Calle de la Victoria, 56500 Barcelona, Espana');

SELECT id, value, COUNT(*) OVER (PARTITION BY id) AS n, ROW_NUMBER() OVER (PARTITION BY id ORDER BY (SELECT NULL)) AS rn, adress
FROM test
CROSS APPLY STRING_SPLIT(adress, ',')

enter image description here

评论

0赞 Ludovic Aubert 5/6/2021
现代 SQL > TSQL
2赞 Salman A 12/2/2021 #45

SQL Server 2022 支持以下签名 STRING_SPLIT

STRING_SPLIT ( string , separator [ , enable_ordinal ] )

当 flag 设置为 1 时,结果将包含一个名为的列,该列由输入字符串中子字符串的从 1 开始的位置组成:enable_ordinalordinal

SELECT *
FROM STRING_SPLIT('hello john smith', ' ', 1)

| value | ordinal |
|-------|---------|
| hello | 1       |
| john  | 2       |
| smith | 3       |

这使我们能够做到这一点:

SELECT value
FROM STRING_SPLIT('hello john smith', ' ', 1)
WHERE ordinal = 2

| value |
|-------|
| john  |
0赞 Josef B. 2/3/2022 #46

@Aaron Bertrand 的修改功能

CREATE FUNCTION [dbo].[SplitString]
(
    @List NVARCHAR(MAX),
    @Delim VARCHAR(255),
    @Idx int
)
RETURNS NVARCHAR(1000)
AS
BEGIN
    DECLARE @ValueTable TABLE(String NVARCHAR(50), Ind int)
    DECLARE @Value NVARCHAR(50)
    BEGIN
    INSERT INTO @ValueTable
    SELECT Value, idx FROM
        (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
          ) AS R WHERE idx = @Idx
    SET @Value = (SELECT String FROM @ValueTable)
    END
    RETURN @Value
END
GO