Sql 从连接的字符串中删除重复值

Sql 从连接的字符串中删除重复值,sql,sql-server,sql-server-2008,Sql,Sql Server,Sql Server 2008,我有下表: Object Field Values ------------------------------------ 1 1 A;A;A;B;A;A 2 1 A;B;C;C 2 2 X 3 1 X;Y;Z 3 2 V;V;V;V;V;V;V;V;V;V;V 如何在此表中仅从串联的值中选择唯一值?因此: Object Field Values --------------

我有下表:

Object  Field  Values
------------------------------------
1       1      A;A;A;B;A;A
2       1      A;B;C;C
2       2      X
3       1      X;Y;Z
3       2      V;V;V;V;V;V;V;V;V;V;V
如何在此表中仅从串联的
值中选择唯一值?因此:

Object  Field  Values
---------------------
1       1      A;B
2       1      A;B;C
2       2      X
3       1      X;Y;Z
3       2      V
在任何脚本语言中,我都会循环使用
值中的值,在
上分解并使用一些逻辑过滤掉重复项来循环该数组。但是,我只需要使用SQL(Server 2008)来完成这项工作

有谁能告诉我这是否可以以及如何做到


非常感谢您的帮助:-)

要完成此操作,请先创建一个拆分函数。这是我使用的一个,但是如果您在internet上搜索(甚至如此)SQL Server Split Function(SQL Server拆分函数),如果您不喜欢,您会发现许多备选方案:

ALTER FUNCTION [dbo].[Split](@StringToSplit NVARCHAR(MAX), @Delimiter NCHAR(1))
RETURNS TABLE
AS
RETURN
(   
    SELECT  ID = ROW_NUMBER() OVER(ORDER BY n.Number),
            Position = Number,
            Value = SUBSTRING(@StringToSplit, Number, CHARINDEX(@Delimiter, @StringToSplit + @Delimiter, Number) - Number)
    FROM    (   SELECT  TOP (LEN(@StringToSplit) + 1) Number = ROW_NUMBER() OVER(ORDER BY a.object_id)
                FROM    sys.all_objects a
                        CROSS JOIN sys.all_objects b
            ) n
    WHERE   SUBSTRING(@Delimiter + @StringToSplit + @Delimiter, n.Number, 1) = @Delimiter
);
然后您可以拆分字段,以便运行:

SELECT  t.Object, t.Field, s.Value
FROM    T
        CROSS APPLY dbo.Split(t.[Values], ';') AS s
将改变这一点:

Object  Field  Values
------------------------------------
1       1      A;A;A;B;A;A
进入:

然后可以应用
DISTINCT
运算符:

SELECT  DISTINCT t.Object, t.Field, s.Value
FROM    T
        CROSS APPLY dbo.Split(t.[Values], ';') AS s;
给予:

Object  Field  Values
------------------------------------
1       1      A
1       1      B
然后,您可以将行连接回一个列,给出最终查询:

SELECT  t.Object, t.Field, [Values] = STUFF(x.value('.', 'NVARCHAR(MAX)'), 1, 1, '')
FROM    T
        CROSS APPLY 
        (   SELECT  DISTINCT ';' + s.Value
            FROM    dbo.Split(t.[Values], ';') AS s
            FOR XML PATH(''), TYPE
        ) AS s (x)

SQL Fiddle似乎已关闭,但创建拆分函数后,下面是一个完整的工作示例:

CREATE TABLE #T (Object INT, Field INT, [Values] VARCHAR(MAX));
INSERT #T
VALUES
    (1, 1, 'A;A;A;B;A;A'),
    (2, 1, 'A;B;C;C'),
    (2, 2, 'X'),
    (3, 1, 'X;Y;Z'),
    (3, 2, 'V;V;V;V;V;V;V;V;V;V;V');

SELECT  t.Object, t.Field, [Values] = STUFF(x.value('.', 'NVARCHAR(MAX)'), 1, 1, '')
FROM    #T AS T
        CROSS APPLY 
        (   SELECT  DISTINCT ';' + s.Value
            FROM    dbo.Split(t.[Values], ';') AS s
            FOR XML PATH(''), TYPE
        ) AS s (x);
编辑

根据您关于不能创建表或修改DDL的评论,我想我可以解释一下您也不能创建函数的情况。您可以将上述拆分函数扩展到查询中,因此实际上不需要创建函数:

CREATE TABLE #T (Object INT, Field INT, [Values] VARCHAR(MAX));
INSERT #T
VALUES
    (1, 1, 'A;A;A;B;A;A'),
    (2, 1, 'A;B;C;C'),
    (2, 2, 'X'),
    (3, 1, 'X;Y;Z'),
    (3, 2, 'V;V;V;V;V;V;V;V;V;V;V');

SELECT  t.Object,
        t.Field,
        [Values] = STUFF(x.value('.', 'NVARCHAR(MAX)'), 1, 1, '')
FROM    #T AS T
        CROSS APPLY 
        (   SELECT  DISTINCT ';' + SUBSTRING(t.[Values], Number, CHARINDEX(';', t.[Values] + ';', Number) - Number)
            FROM    (   SELECT  TOP (LEN(t.[Values]) + 1) Number = ROW_NUMBER() OVER(ORDER BY a.object_id)
                        FROM    sys.all_objects a
                                CROSS JOIN sys.all_objects b
                    ) n
            WHERE   SUBSTRING(';' + t.[Values] + ';', n.Number, 1) = ';'
            FOR XML PATH(''), TYPE
        ) AS s (x);

为此,首先创建一个拆分函数。这是我使用的一个,但是如果您在internet上搜索(甚至如此)SQL Server Split Function(SQL Server拆分函数),如果您不喜欢,您会发现许多备选方案:

ALTER FUNCTION [dbo].[Split](@StringToSplit NVARCHAR(MAX), @Delimiter NCHAR(1))
RETURNS TABLE
AS
RETURN
(   
    SELECT  ID = ROW_NUMBER() OVER(ORDER BY n.Number),
            Position = Number,
            Value = SUBSTRING(@StringToSplit, Number, CHARINDEX(@Delimiter, @StringToSplit + @Delimiter, Number) - Number)
    FROM    (   SELECT  TOP (LEN(@StringToSplit) + 1) Number = ROW_NUMBER() OVER(ORDER BY a.object_id)
                FROM    sys.all_objects a
                        CROSS JOIN sys.all_objects b
            ) n
    WHERE   SUBSTRING(@Delimiter + @StringToSplit + @Delimiter, n.Number, 1) = @Delimiter
);
然后您可以拆分字段,以便运行:

SELECT  t.Object, t.Field, s.Value
FROM    T
        CROSS APPLY dbo.Split(t.[Values], ';') AS s
将改变这一点:

Object  Field  Values
------------------------------------
1       1      A;A;A;B;A;A
进入:

然后可以应用
DISTINCT
运算符:

SELECT  DISTINCT t.Object, t.Field, s.Value
FROM    T
        CROSS APPLY dbo.Split(t.[Values], ';') AS s;
给予:

Object  Field  Values
------------------------------------
1       1      A
1       1      B
然后,您可以将行连接回一个列,给出最终查询:

SELECT  t.Object, t.Field, [Values] = STUFF(x.value('.', 'NVARCHAR(MAX)'), 1, 1, '')
FROM    T
        CROSS APPLY 
        (   SELECT  DISTINCT ';' + s.Value
            FROM    dbo.Split(t.[Values], ';') AS s
            FOR XML PATH(''), TYPE
        ) AS s (x)

SQL Fiddle似乎已关闭,但创建拆分函数后,下面是一个完整的工作示例:

CREATE TABLE #T (Object INT, Field INT, [Values] VARCHAR(MAX));
INSERT #T
VALUES
    (1, 1, 'A;A;A;B;A;A'),
    (2, 1, 'A;B;C;C'),
    (2, 2, 'X'),
    (3, 1, 'X;Y;Z'),
    (3, 2, 'V;V;V;V;V;V;V;V;V;V;V');

SELECT  t.Object, t.Field, [Values] = STUFF(x.value('.', 'NVARCHAR(MAX)'), 1, 1, '')
FROM    #T AS T
        CROSS APPLY 
        (   SELECT  DISTINCT ';' + s.Value
            FROM    dbo.Split(t.[Values], ';') AS s
            FOR XML PATH(''), TYPE
        ) AS s (x);
编辑

根据您关于不能创建表或修改DDL的评论,我想我可以解释一下您也不能创建函数的情况。您可以将上述拆分函数扩展到查询中,因此实际上不需要创建函数:

CREATE TABLE #T (Object INT, Field INT, [Values] VARCHAR(MAX));
INSERT #T
VALUES
    (1, 1, 'A;A;A;B;A;A'),
    (2, 1, 'A;B;C;C'),
    (2, 2, 'X'),
    (3, 1, 'X;Y;Z'),
    (3, 2, 'V;V;V;V;V;V;V;V;V;V;V');

SELECT  t.Object,
        t.Field,
        [Values] = STUFF(x.value('.', 'NVARCHAR(MAX)'), 1, 1, '')
FROM    #T AS T
        CROSS APPLY 
        (   SELECT  DISTINCT ';' + SUBSTRING(t.[Values], Number, CHARINDEX(';', t.[Values] + ';', Number) - Number)
            FROM    (   SELECT  TOP (LEN(t.[Values]) + 1) Number = ROW_NUMBER() OVER(ORDER BY a.object_id)
                        FROM    sys.all_objects a
                                CROSS JOIN sys.all_objects b
                    ) n
            WHERE   SUBSTRING(';' + t.[Values] + ';', n.Number, 1) = ';'
            FOR XML PATH(''), TYPE
        ) AS s (x);

就像没有CTE的标量值函数一样

ALTER FUNCTION [SplitRemoveDupes] (
@String VARCHAR(MAX)
,@Delimiter VARCHAR(5)
)
RETURNS VARCHAR(MAX)
AS
BEGIN

DECLARE @SplitLength INT 
DECLARE @DedupedValues VARCHAR(MAX) 

DECLARE @SplittedValues TABLE 
( 
  OccurenceId SMALLINT IDENTITY(1,1), 
  SplitValue VARCHAR(200) 
)  

    WHILE LEN(@String) > 0
    BEGIN
        SELECT @SplitLength = (
                CASE CHARINDEX(@Delimiter, @String)
                    WHEN 0
                        THEN LEN(@String)
                    ELSE CHARINDEX(@Delimiter, @String) - 1
                    END
                )

    INSERT INTO @SplittedValues
    SELECT SUBSTRING(@String, 1, @SplitLength)

    SELECT @String = (
            CASE (LEN(@String) - @SplitLength)
                WHEN 0
                    THEN ''
                ELSE RIGHT(@String, LEN(@String) - @SplitLength - 1) END)

END

SET @DedupedValues=(SELECT DISTINCT STUFF((
            SELECT DISTINCT (@Delimiter + SplitValue)
            FROM @SplittedValues s
            ORDER BY (@Delimiter +  SplitValue)
                        FOR XML PATH('')
            ), 1, 1, '') AS a
FROM @SplittedValues ss)

RETURN @DedupedValues 
END
称之为内联

SELECT Object, Field, [dbo].[SplitRemoveDupes](Values,';') From Table

就像没有CTE的标量值函数一样

ALTER FUNCTION [SplitRemoveDupes] (
@String VARCHAR(MAX)
,@Delimiter VARCHAR(5)
)
RETURNS VARCHAR(MAX)
AS
BEGIN

DECLARE @SplitLength INT 
DECLARE @DedupedValues VARCHAR(MAX) 

DECLARE @SplittedValues TABLE 
( 
  OccurenceId SMALLINT IDENTITY(1,1), 
  SplitValue VARCHAR(200) 
)  

    WHILE LEN(@String) > 0
    BEGIN
        SELECT @SplitLength = (
                CASE CHARINDEX(@Delimiter, @String)
                    WHEN 0
                        THEN LEN(@String)
                    ELSE CHARINDEX(@Delimiter, @String) - 1
                    END
                )

    INSERT INTO @SplittedValues
    SELECT SUBSTRING(@String, 1, @SplitLength)

    SELECT @String = (
            CASE (LEN(@String) - @SplitLength)
                WHEN 0
                    THEN ''
                ELSE RIGHT(@String, LEN(@String) - @SplitLength - 1) END)

END

SET @DedupedValues=(SELECT DISTINCT STUFF((
            SELECT DISTINCT (@Delimiter + SplitValue)
            FROM @SplittedValues s
            ORDER BY (@Delimiter +  SplitValue)
                        FOR XML PATH('')
            ), 1, 1, '') AS a
FROM @SplittedValues ss)

RETURN @DedupedValues 
END
称之为内联

SELECT Object, Field, [dbo].[SplitRemoveDupes](Values,';') From Table

以下是一个独立的解决方案:

DECLARE @t table(Object int, Field int, [Values] varchar(max))
INSERT @t values
(1, 1, 'A;A;A;B;A;A'),
(2, 1, 'A;B;C;C'),
(3, 1, 'X'),
(4, 1, 'X;Y;Z'),
(5, 1, 'V;V;V;V;V;V;V;V;V;V;V')

SELECT t.Object, t.Field, x.[NewValues]
FROM @t t
CROSS APPLY
(
  SELECT STUFF((
     SELECT distinct ';'+t.c.value('.', 'VARCHAR(2000)') value
     FROM (
         SELECT x = CAST('<t>' + 
               REPLACE([Values], ';', '</t><t>') + '</t>' AS XML)
     ) a
     CROSS APPLY x.nodes('/t') t(c)
        for xml path(''), type 
    ).value('.', 'varchar(max)'), 1, 1, '') [NewValues] 
) x
根据@GarethD的评论,这可能执行缓慢

测试数据:

create table #t(Object int identity(1,1), Field int, [Values] varchar(max))
INSERT #t values
(1, 'A;A;A;B;A;A'),(1, 'A;B;C;C'), (1, 'X'), (1, 'X;Y;Z'),(1, 'V;V;V;V;V;V;V;V;V;V;V')

insert #t select field, [values] from #t union all select field, [values] from #t union all select field, [values] from #t
insert #t select field, [values] from #t union all select field, [values] from #t union all select field, [values] from #t
insert #t select field, [values] from #t union all select field, [values] from #t union all select field, [values] from #t
insert #t select field, [values] from #t union all select field, [values] from #t union all select field, [values] from #t
insert #t select field, [values] from #t union all select field, [values] from #t union all select field, [values] from #t
insert #t select field, [values] from #t union all select field, [values] from #t union all select field, [values] from #t
性能测试我的脚本:

SELECT t.Object, t.Field, x.[NewValues]
FROM #t t
CROSS APPLY
(
  SELECT STUFF((
     SELECT distinct ';'+t.c.value('.', 'VARCHAR(2000)') value
     FROM (
         SELECT x = CAST('<t>' + 
               REPLACE([Values], ';', '</t><t>') + '</t>' AS XML)
     ) a
     CROSS APPLY x.nodes('/t') t(c)
        for xml path(''), type 
    ).value('.', 'varchar(max)'), 1, 1, '') [NewValues] 
) x
结果6秒


如果任何行的值为空,此脚本也将崩溃。

这里有一个独立的解决方案:

DECLARE @t table(Object int, Field int, [Values] varchar(max))
INSERT @t values
(1, 1, 'A;A;A;B;A;A'),
(2, 1, 'A;B;C;C'),
(3, 1, 'X'),
(4, 1, 'X;Y;Z'),
(5, 1, 'V;V;V;V;V;V;V;V;V;V;V')

SELECT t.Object, t.Field, x.[NewValues]
FROM @t t
CROSS APPLY
(
  SELECT STUFF((
     SELECT distinct ';'+t.c.value('.', 'VARCHAR(2000)') value
     FROM (
         SELECT x = CAST('<t>' + 
               REPLACE([Values], ';', '</t><t>') + '</t>' AS XML)
     ) a
     CROSS APPLY x.nodes('/t') t(c)
        for xml path(''), type 
    ).value('.', 'varchar(max)'), 1, 1, '') [NewValues] 
) x
根据@GarethD的评论,这可能执行缓慢

测试数据:

create table #t(Object int identity(1,1), Field int, [Values] varchar(max))
INSERT #t values
(1, 'A;A;A;B;A;A'),(1, 'A;B;C;C'), (1, 'X'), (1, 'X;Y;Z'),(1, 'V;V;V;V;V;V;V;V;V;V;V')

insert #t select field, [values] from #t union all select field, [values] from #t union all select field, [values] from #t
insert #t select field, [values] from #t union all select field, [values] from #t union all select field, [values] from #t
insert #t select field, [values] from #t union all select field, [values] from #t union all select field, [values] from #t
insert #t select field, [values] from #t union all select field, [values] from #t union all select field, [values] from #t
insert #t select field, [values] from #t union all select field, [values] from #t union all select field, [values] from #t
insert #t select field, [values] from #t union all select field, [values] from #t union all select field, [values] from #t
性能测试我的脚本:

SELECT t.Object, t.Field, x.[NewValues]
FROM #t t
CROSS APPLY
(
  SELECT STUFF((
     SELECT distinct ';'+t.c.value('.', 'VARCHAR(2000)') value
     FROM (
         SELECT x = CAST('<t>' + 
               REPLACE([Values], ';', '</t><t>') + '</t>' AS XML)
     ) a
     CROSS APPLY x.nodes('/t') t(c)
        for xml path(''), type 
    ).value('.', 'varchar(max)'), 1, 1, '') [NewValues] 
) x
结果6秒



如果任何一行的值为空,这个脚本也会崩溃。

你应该规范化你的表,不要有这样的数据。谢谢,但这是我必须处理的。这是我得到的输入,我对DB设计没有影响,也不能创建表。我建议使用标量函数。你需要编写SQL函数并传递值,该函数必须有代码来删除重复项。如何创建值?这可能是删除重复项的正确位置。你应该规范化你的表,不要有那样的数据。谢谢,但这是我必须处理的。这是我得到的输入,我对DB设计没有影响,也不能创建表。我建议使用标量函数。你需要编写SQL函数并传递值,该函数必须有代码来删除重复项。如何创建值?这可能是删除重复项的正确位置。这是有史以来最棒的答案。非常感谢:这是有史以来最棒的答案。多谢各位:D@Anon它必须有一个价值。我想varchar(max)可能会更好。这是一个很好的整洁解决方案,但也有可能,而且它不处理特殊的xml字符。i、 e试图拆分
”>;>@谢谢你的评论。这可能会在特殊字符上失败。我比较了我们的脚本,发现了性能问题。但是,它不在我的脚本中。关于我的性能问题,你是对的,但这不是由于拆分函数。这是因为我一步一步地构建查询来解释它,而不是选择最有效的方法。我已经用更有效的方法更新了我的答案,使用相同的分割函数。然后重复您的测试,在初始数据中添加一行:
(1,REPLICATE('A;B;C;D;',1000))
。导致-XML拆分:~90秒。数字分割:~6秒。这就是我所说的性能差异,这是由于XML在块中的传输方式造成的。@Anon它必须有一个值。我想varchar(max)可能会更好。这是一个很好的整洁解决方案,但也有可能,而且它不处理特殊的xml字符。i、 e试图拆分
”>;>@谢谢你的评论。这可能会在特殊字符上失败。我比较了我们的脚本,发现了性能问题。但是,它不在我的脚本中。关于我的性能问题,你是对的,但这不是由于拆分函数。这是因为我一步一步地构建查询来解释它,而不是选择最有效的方法。我已经用更有效的方法更新了我的答案,使用相同的分割函数。然后重复您的测试,在初始数据中添加一行:
(1,REPLICATE('A;B;C;D;',1000))
。导致-XML拆分:~90秒。数字分割:~6秒。这就是我所说的性能差异,这是由于XML在块中的传输方式。