T-SQL:与字符串连接相反 - 如何将字符串拆分为多个记录

kri*_*tof 136 t-sql sql-server sql-server-2005

可能重复:
SQL中的拆分字符串

我在SQL中看到了几个与字符串连接相关的问题.我想知道你将如何处理相反的问题:将昏迷分隔的字符串拆分为数据行:

可以说我有桌子:

userTypedTags(userID,commaSeparatedTags) 'one entry per user
tags(tagID,name)
Run Code Online (Sandbox Code Playgroud)

并希望将数据插入表中

userTag(userID,tagID) 'multiple entries per user
Run Code Online (Sandbox Code Playgroud)

灵感来自数据库中没有哪些标签?

编辑

谢谢你的答案,实际上有一个值得接受,但我只能选择一个,而Cade Roux提出的递归解决方案对我来说似乎很干净.它适用于SQL Server 2005及更高版本.

对于早期版本的SQL Server,可以使用miies提供的解决方案.对于使用文本数据类型,wcm answer会很有帮助.再次感谢.

Cad*_*oux 148

这里记录了这个问题的各种解决方案,包括这个小宝石:

CREATE FUNCTION dbo.Split (@sep char(1), @s varchar(512))
RETURNS table
AS
RETURN (
    WITH Pieces(pn, start, stop) AS (
      SELECT 1, 1, CHARINDEX(@sep, @s)
      UNION ALL
      SELECT pn + 1, stop + 1, CHARINDEX(@sep, @s, stop + 1)
      FROM Pieces
      WHERE stop > 0
    )
    SELECT pn,
      SUBSTRING(@s, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END) AS s
    FROM Pieces
  )
Run Code Online (Sandbox Code Playgroud)

  • 很棒的功能.可以使用nchar()和nvarchar().另请参阅下面的可变长度分隔符的建议. (2认同)
  • 警告:输入字符串较大(大约1000个字符以上)失败."在语句完成之前,最大递归100已用尽." (2认同)

Nat*_*ler 85

您也可以使用XML实现此效果,如此处所示,这消除了所提供的答案的限制,这些答案似乎都以某种方式包含递归.我在这里所做的特殊用法允许最多32个字符的分隔符,但是可以增加它需要的大.

create FUNCTION [dbo].[Split] (@sep VARCHAR(32), @s VARCHAR(MAX))
RETURNS TABLE
AS
    RETURN
    (
        SELECT r.value('.','VARCHAR(MAX)') as Item
        FROM (SELECT CONVERT(XML, N'<root><r>' + REPLACE(REPLACE(REPLACE(@s,'& ','&amp; '),'<','&lt;'), @sep, '</r><r>') + '</r></root>') as valxml) x
        CROSS APPLY x.valxml.nodes('//root/r') AS RECORDS(r)
    )
Run Code Online (Sandbox Code Playgroud)

然后你可以使用以下方法调用它:

SELECT * FROM dbo.Split(' ', 'I hate bunnies')
Run Code Online (Sandbox Code Playgroud)

哪个回报:

-----------
|I        |
|---------|
|hate     |
|---------|
|bunnies  |
-----------
Run Code Online (Sandbox Code Playgroud)


我应该注意,我实际上并不讨厌兔子......它只是出于某种原因突然出现在我脑海中.
以下是我在内联表值函数中使用相同方法得出的最接近的事情.不要使用它,这是非常不足的!这只是为了参考.

CREATE FUNCTION [dbo].[Split] (@sep VARCHAR(32), @s VARCHAR(MAX))
RETURNS TABLE
AS
    RETURN
    (
        SELECT r.value('.','VARCHAR(MAX)') as Item
        FROM (SELECT CONVERT(XML, N'<root><r>' + REPLACE(@s, @sep, '</r><r>') + '</r></root>') as valxml) x
        CROSS APPLY x.valxml.nodes('//root/r') AS RECORDS(r)
    )
Run Code Online (Sandbox Code Playgroud)

  • 编辑号7将第一个函数更改为内联函数.这是令人困惑的,因为答案讨论了两种解决方案之间的差异.以防其他人感到困惑并看到此评论 (3认同)

use*_*603 18

我使用此功能(SQL Server 2005及更高版本).

create function [dbo].[Split]
(
    @string nvarchar(4000),
    @delimiter nvarchar(10)
)
returns @table table
(
    [Value] nvarchar(4000)
)
begin
    declare @nextString nvarchar(4000)
    declare @pos int, @nextPos int

    set @nextString = ''
    set @string = @string + @delimiter

    set @pos = charindex(@delimiter, @string)
    set @nextPos = 1
    while (@pos <> 0)
    begin
        set @nextString = substring(@string, 1, @pos - 1)

        insert into @table
        (
            [Value]
        )
        values
        (
            @nextString
        )

        set @string = substring(@string, @pos + len(@delimiter), len(@string))
        set @nextPos = @pos
        set @pos = charindex(@delimiter, @string)
    end
    return
end
Run Code Online (Sandbox Code Playgroud)


Mar*_*ith 11

对于将字符串拆分为单词的特殊情况,我遇到了SQL Server 2008的另一个解决方案.

with testTable AS
(
SELECT 1 AS Id, N'how now brown cow' AS txt UNION ALL
SELECT 2, N'she sells sea shells upon the sea shore' UNION ALL
SELECT 3, N'red lorry yellow lorry' UNION ALL
SELECT 4, N'the quick brown fox jumped over the lazy dog'
)

SELECT display_term, COUNT(*) As Cnt
 FROM testTable
CROSS APPLY sys.dm_fts_parser('"' + txt + '"', 1033, 0,0)
GROUP BY display_term
HAVING COUNT(*) > 1
ORDER BY Cnt DESC
Run Code Online (Sandbox Code Playgroud)

返回

display_term                   Cnt
------------------------------ -----------
the                            3
brown                          2
lorry                          2
sea                            2
Run Code Online (Sandbox Code Playgroud)

  • 有趣,但必须注意它**要求**"全文搜索"安装和可用 (4认同)

Ror*_*ory 7

稍微修改上面的解决方案,使其适用于可变长度分隔符.

create FUNCTION dbo.fn_Split2 (@sep nvarchar(10), @s nvarchar(4000))
RETURNS table
AS
RETURN (
    WITH Pieces(pn, start, stop) AS (
      SELECT 1, 1, CHARINDEX(@sep, @s)
      UNION ALL
      SELECT pn + 1, stop + (datalength(@sep)/2), CHARINDEX(@sep, @s, stop + (datalength(@sep)/2))
      FROM Pieces
      WHERE stop > 0
    )
    SELECT pn,
      SUBSTRING(@s, start, CASE WHEN stop > 0 THEN stop-start ELSE 4000 END) AS s
    FROM Pieces
  )
Run Code Online (Sandbox Code Playgroud)

注意:我使用了datalength(),因为如果有尾随空格,len()会报错.


Tom*_*lak 7

这是一个Split与2005之前的SQL Server版本兼容的函数.

CREATE FUNCTION dbo.Split(@data nvarchar(4000), @delimiter nvarchar(100))  
RETURNS @result table (Id int identity(1,1), Data nvarchar(4000)) 
AS  
BEGIN 
    DECLARE @pos   INT
    DECLARE @start INT
    DECLARE @len   INT
    DECLARE @end   INT

    SET @len   = LEN('.' + @delimiter + '.') - 2
    SET @end   = LEN(@data) + 1
    SET @start = 1
    SET @pos   = 0

    WHILE (@pos < @end)
    BEGIN
        SET @pos = CHARINDEX(@delimiter, @data, @start)
        IF (@pos = 0) SET @pos = @end

        INSERT @result (data) SELECT SUBSTRING(@data, @start, @pos - @start)
        SET @start = @pos + @len
    END

    RETURN
END
Run Code Online (Sandbox Code Playgroud)

  • 用于避免递归的+1(因为SQL Server做得很差),避免使用XML(因为SQL没有用于转义特殊XML字符的简单API),还避免使用CLR代码(因为某些公司的数据中心不允许自定义共享SQL Server实例上的代码). (2认同)

say*_*yap 7

使用CLR,这是一个更简单的替代方案,适用于所有情况,但比接受的答案快40%:

using System;
using System.Collections;
using System.Data.SqlTypes;
using System.Text.RegularExpressions;
using Microsoft.SqlServer.Server;

public class UDF
{
    [SqlFunction(FillRowMethodName="FillRow")]
    public static IEnumerable RegexSplit(SqlString s, SqlString delimiter)
    {
        return Regex.Split(s.Value, delimiter.Value);
    }

    public static void FillRow(object row, out SqlString str)
    {
        str = new SqlString((string) row);
    }
}
Run Code Online (Sandbox Code Playgroud)

当然,它仍然比PostgreSQL慢8倍regexp_split_to_table.

  • 你是如何对此进行基准的? (3认同)

Yan*_*met 6

SELECT substring(commaSeparatedTags,0,charindex(',',commaSeparatedTags))
Run Code Online (Sandbox Code Playgroud)

会给你第一个标签.你可以通过类似的方式继续获得第二个,依此类推,每次将substring和charindex组合为一层.这是一个直接的解决方案,但它只适用于非常少的标签,因为查询的大小变得非常快并且变得不可读.然后转到功能,如本文其他更复杂的答案所述.