在SQL Server中拆分包含带引号分隔符的分隔文本

时间:2012-05-23 04:31:34

标签: sql sql-server tsql

以下是Passing a varchar full of comma delimited values to a SQL Server IN function

我想拆分一些以逗号分隔的文本,但我需要允许使用嵌入式逗号:

  DECLARE @text NVARCHAR(1000) = 'abc,def,"ghi,jkl",mno';

我期待的结果是:

  abc
  def
  ghi,jkl
  mno

这是我用来分割CSV文本的功能。

它使用循环,因此如果性能有问题,您可以使用以下建议进行调整:https://stackoverflow.com/a/878964/482595

CREATE FUNCTION uf_Split
( 
    @Text NVARCHAR(MAX), 
    @Delimiter CHAR(1),
    @Quote CHAR(1)
) 
RETURNS @Result TABLE 
( 
    [Index] INT NOT NULL IDENTITY(1, 1), 
    [Value] NVARCHAR(4000) NULL,
    [CharPos] INT
)
AS 
BEGIN 
    DECLARE @start BIGINT; SET @start = 1
    DECLARE @end BIGINT; SET @end = 1

    IF @Text is null
    BEGIN
      RETURN
    END 

    WHILE 1=1 
    BEGIN 
        SET @end = 
            CASE
                WHEN CHARINDEX(@Quote, @Text, @start) = @start THEN CHARINDEX(@Quote + @Delimiter, @Text, @start + 1)
                ELSE CHARINDEX(@Delimiter, @Text, @start)
            END

        IF ISNULL(@end, 0) = 0 
        BEGIN 
            -- Delimiter could not be found in the remainder of the text:
            INSERT @Result([Value], [CharPos]) VALUES(SUBSTRING(@Text, @start, DATALENGTH(@Text)), @start)
            BREAK 
        END 
        ELSE IF (CHARINDEX(@Quote, @Text, @start) = @start) AND (CHARINDEX(@Quote + @Delimiter, @Text, @start + 1) = @end)
        BEGIN
            INSERT @Result([Value], [CharPos]) VALUES(SUBSTRING(@Text, @start + 1, @end - @start - 1), @start)
            SET @start = @end + 2
        END
        ELSE 
        BEGIN
            INSERT @Result([Value], [CharPos]) VALUES(SUBSTRING(@Text, @start, @end - @start), @start)
            SET @start = @end + 1
        END
    END 

    RETURN
END
GO

3 个答案:

答案 0 :(得分:3)

我在t-sql中使用两相分割拍摄了一张照片。我非常有兴趣看到其他人如何处理这个问题。如果这些字符串很大或者您希望处理大型行集,我会查看其他选项,可能是BULK INSERT或CLR。

declare @data nvarchar(1000) = 'abc,def,"ghi,jkl",mno,"yak","yak,123"';


declare @x xml;
select  @x = cast('<d>' + replace(@data, '"', '</d><d>') + '</d>' as xml);

;with c(d,i)
as  (   select  p.n.value('.', 'nvarchar(max)') AS data,
                case
                    when left(p.n.value('.', 'nvarchar(max)'), 1) = ',' then 1
                    when right(p.n.value('.', 'nvarchar(max)'), 1) = ',' then 1 
                    else 0 
                end
        from    @x.nodes('/d') p(n)
    )       
select  d
from    c
where   i = 0 and len(d) > 0
union all
select  p.n.value('.', 'nvarchar(max)')
from    (   select cast('<d>' + replace(d, ',', '</d><d>') + '</d>' as xml)
            from c
            where   i=1
        ) d(x)
cross
apply   d.x.nodes('/d')p(n)
where   len(p.n.value('.', 'nvarchar(max)')) > 0;

答案 1 :(得分:0)

这样做的最佳方法是在函数中为嵌入式逗号定义特殊情况,当您在字符串的开头拆分嵌入式逗号并删除该子字符串时。

答案 2 :(得分:0)

很棒的解决方案,谢谢分享!我确实做了一个更改以适合我的情况。我们的某些数据已嵌入&,这会导致非法字符错误。为了解决该问题,我使用了REPLACE函数将其从&更改为&amp;,以便可以在XML中进行解析。然后,当我需要将其改回时,将值&amp;替换为&。我相信有更好的方法可以解决此问题,但绝对可以解决我们的问题。下面是带有我的更改的代码示例。

DECLARE @data NVARCHAR(MAX) = 'abc,def,"ghi,jkl",mno,"yak","yak,123","zzzz & yyyy"';

SELECT @data = REPLACE(REPLACE(REPLACE(@data, '&', '&amp;'), '<', '&lt;'), '>', '&gt;'); /**** Replace characters (&, <, >) ****/

DECLARE @x XML = CAST('<d>' + REPLACE(@data, '"', '</d><d>') + '</d>' AS XML);

;WITH c (d,i) AS
    (
        SELECT  p.n.value('.', 'NVARCHAR(MAX)') AS DATA,
                CASE
                    WHEN LEFT(p.n.value('.', 'NVARCHAR(MAX)'), 1) = ','
                        THEN 1
                    WHEN RIGHT(p.n.value('.', 'NVARCHAR(MAX)'), 1) = ','
                        THEN 1 
                    ELSE 0 
                END
        FROM @x.nodes('/d') p(n)
    )

SELECT REPLACE(REPLACE(REPLACE([z].[d], '&amp;', '&'), '&lt;', '<'), '&gt;', '>') AS d /**** Restore characters (&, <, >) ****/
FROM (
        SELECT d
        FROM c
        WHERE i = 0
            AND LEN(d) > 0

        UNION ALL

        SELECT p.n.value('.', 'NVARCHAR(MAX)')
        FROM (
                SELECT cast('<d>' + replace(d, ',', '</d><d>') + '</d>' AS XML)
                FROM c
                WHERE i = 1
            ) d(x)
        CROSS APPLY d.x.nodes('/d')p(n)
        WHERE LEN(p.n.value('.', 'NVARCHAR(MAX)')) > 0
    ) AS z
ORDER BY d;