Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/sql-server-2005/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Sql server T-SQL:与字符串连接相反-如何将字符串拆分为多个记录_Sql Server_Sql Server 2005_Tsql - Fatal编程技术网

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

Sql server T-SQL:与字符串连接相反-如何将字符串拆分为多个记录,sql-server,sql-server-2005,tsql,Sql Server,Sql Server 2005,Tsql,可能重复: 我在SQL中看到过。 我想知道如何处理相反的问题:将分隔字符串拆分为数据行: 假设我有桌子: userTypedTags(userID,commaSeparatedTags) 'one entry per user tags(tagID,name) 并希望将数据插入表中 userTag(userID,tagID) 'multiple entries per user 受问题启发 编辑 谢谢你的回答,事实上不止一个应该被接受,但我只能选择一个,递归的方法对我来说似乎很干净。它在S

可能重复:

我在SQL中看到过。 我想知道如何处理相反的问题:将分隔字符串拆分为数据行:

假设我有桌子:

userTypedTags(userID,commaSeparatedTags) 'one entry per user
tags(tagID,name)
并希望将数据插入表中

userTag(userID,tagID) 'multiple entries per user
受问题启发

编辑

谢谢你的回答,事实上不止一个应该被接受,但我只能选择一个,递归的方法对我来说似乎很干净。它在SQLServer2005及更高版本上工作

对于早期版本的SQL Server,可以使用该解决方案。 对于使用文本数据类型将很有帮助。再次感谢。

我使用此功能(SQL Server 2005及以上版本)

创建函数[dbo].[Split]
(
@字符串nvarchar(4000),
@分隔符nvarchar(10)
)
返回@table
(
[价值]nvarchar(4000)
)
开始
声明@nextString nvarchar(4000)
声明@pos int,@nextPos int
设置@nextString=''
设置@string=@string+@分隔符
设置@pos=charindex(@delimiter,@string)
设置@nextPos=1
而(@pos 0)
开始
设置@nextString=substring(@string,1,@pos-1)
插入@table
(
[价值]
)
价值观
(
@下串
)
设置@string=substring(@string、@pos+len(@delimiter)、len(@string))
设置@nextPos=@pos
设置@pos=charindex(@delimiter,@string)
结束
返回
结束

这是我不久前写的。它假定分隔符是逗号,并且单个值不超过127个字符。它可以很容易地修改

它的优点是不限于4000个字符

祝你好运

ALTER Function [dbo].[SplitStr] ( 
        @txt text 
) 
Returns @tmp Table 
        ( 
                value varchar(127)
        ) 
as 
BEGIN 
        declare @str varchar(8000) 
                , @Beg int 
                , @last int 
                , @size int 

        set @size=datalength(@txt) 
        set @Beg=1 


        set @str=substring(@txt,@Beg,8000) 
        IF len(@str)<8000 set @Beg=@size 
        ELSE BEGIN 
                set @last=charindex(',', reverse(@str)) 
                set @str=substring(@txt,@Beg,8000-@last) 
                set @Beg=@Beg+8000-@last+1 
        END 

        declare @workingString varchar(25) 
                , @stringindex int 



        while @Beg<=@size Begin 
                WHILE LEN(@str) > 0 BEGIN 
                        SELECT @StringIndex = CHARINDEX(',', @str) 

                        SELECT 
                                @workingString = CASE 
                                        WHEN @StringIndex > 0 THEN SUBSTRING(@str, 1, @StringIndex-1) 
                                        ELSE @str 
                                END 

                        INSERT INTO 
                                @tmp(value)
                        VALUES 
                                (cast(rtrim(ltrim(@workingString)) as varchar(127)))
                        SELECT @str = CASE 
                                WHEN CHARINDEX(',', @str) > 0 THEN SUBSTRING(@str, @StringIndex+1, LEN(@str)) 
                                ELSE '' 
                        END 
                END 
                set @str=substring(@txt,@Beg,8000) 

                if @Beg=@size set @Beg=@Beg+1 
                else IF len(@str)<8000 set @Beg=@size 
                ELSE BEGIN 
                        set @last=charindex(',', reverse(@str)) 
                        set @str=substring(@txt,@Beg,8000-@last) 
                        set @Beg=@Beg+8000-@last+1 

                END 
        END     

        return
END 
ALTER函数[dbo]。[SplitStr](
@文本
) 
返回@tmp表
( 
varchar值(127)
) 
作为
开始
声明@str varchar(8000)
,@Beg int
,@last int
,@size int
设置@size=datalength(@txt)
设置@Beg=1
设置@str=substring(@txt,@Beg,8000)
如果len(@str)为0,则子字符串(@str,1,@StringIndex-1)
ELSE@str
结束
插入
@tmp(价值)
价值观
(将(rtrim(ltrim(@workingString))转换为varchar(127)))
选择@str=CASE
当CHARINDEX(',',@str)>0时,则子字符串(@str,@StringIndex+1,LEN(@str))
否则“
结束
结束
设置@str=substring(@txt,@Beg,8000)
如果@Beg=@size set@Beg=@Beg+1
else IF len(@str)

我会给你第一个标签。通过将substring和charindex组合在一起,每次加深一层,您可以类似地获得第二层,以此类推。这是一个即时解决方案,但它只适用于很少的标记,因为查询的大小增长非常快,并且变得不可读。然后继续讨论函数,如本帖其他更复杂的答案所述。

这个问题有多种解决方案,包括这个小宝石:

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
  )
对上面的内容稍作修改,使其可以使用可变长度分隔符

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
  )

注意:我使用了datalength(),因为len()会错误地报告是否有尾随空格。

您也可以使用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)
    )
返回:

-----------
|I        |
|---------|
|hate     |
|---------|
|bunnies  |
-----------

我应该注意,我其实并不讨厌兔子。。。出于某种原因,它突然出现在我的脑海里。
下面是我在内联表值函数中使用相同方法所能想到的最接近的东西。别用它,它效率太低了!这里只是参考一下

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)
    )
创建函数[dbo].[Split](@sep VARCHAR(32),@s VARCHAR(MAX))
返回表
作为
返回
(
选择r.value('.','VARCHAR(MAX')作为项目
从(选择转换(XML,N''+替换(@s,@sep'')+'')为valxml)x
交叉应用x.valxml.nodes('//root/r')作为记录(r)
)

对于将字符串拆分为单词的特殊情况,我遇到了另一种针对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
返回

display_term                   Cnt
------------------------------ -----------
the                            3
brown                          2
lorry                          2
sea                            2

这里有一个
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
创建函数dbo.Split(@data-nvarchar(4000),@delimiter-nvarchar(100))
返回@result table(Id int identity(1,1),Data nvarchar(4000))
作为
开始
声明@pos INT
声明@start INT
声明@lenint
声明@end INT
设置@len=len('.+@delimiter+'.')-2
设置@end=LEN(@data)+1
设置@start=1
设置@pos=0
而(@pos<@end)
开始
设置@pos=CHARINDEX(@delimiter、@data、@start)
如果(@pos=0)设置@pos=@end
插入@result(data)选择子字符串(@data、@start、@pos-@start)
设置@start=@pos+@len
结束
返回
结束

使用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);
    }
}
当然,它仍然比PostgreSQL的
regexp\u split\u to\u table

慢了8倍,因为我发现“Cade Roux”的答案在超过某个字符串大小时不起作用,所以我对“Nathan Wheeler”的答案投了赞成票

几点

-我发现添加DISTINCT关键字可以提高性能

-Nathan的答案只有在标识符小于等于5个字符时才有效,当然你可以调整它…如果你要拆分的项目是INT标识符,就像我一样,你可以在下面告诉我们和我一样
display_term                   Cnt
------------------------------ -----------
the                            3
brown                          2
lorry                          2
sea                            2
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
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);
    }
}
CREATE FUNCTION [dbo].Split
(
    @sep VARCHAR(32), 
    @s VARCHAR(MAX)
)
RETURNS 
    @result TABLE (
        Id INT NULL
    )   
AS
BEGIN
    DECLARE @xml XML
    SET @XML = N'<root><r>' + REPLACE(@s, @sep, '</r><r>') + '</r></root>'

    INSERT INTO @result(Id)
    SELECT DISTINCT r.value('.','int') as Item
    FROM @xml.nodes('//root//r') AS RECORDS(r)

    RETURN
END
create function [dbo].[Split](@string varchar(max), @separator varchar(10))
returns @splited table ( stringPart varchar(max) )
with execute as caller
as
begin
    declare @stringPart varchar(max);
    set @stringPart = '';

    while charindex(@separator, @string) > 0
    begin
        set @stringPart = substring(@string, 0, charindex(@separator, @string));
        insert into @splited (stringPart) values (@stringPart);
        set @string = substring(@string, charindex(@separator, @string) + len(@separator), len(@string) + 1);
    end

    return;
end
go
declare @example varchar(max);
set @example = 'one;string;to;rule;them;all;;';

select * from [dbo].[Split](@example, ';');