从非常大的表中删除重复的子集

时间:2019-02-06 22:10:14

标签: sql sql-server tsql

我正在使用的数据非常复杂,因此我将提供一个简单的示例,以便希望将其扩展到我正在处理的内容。

注意:我已经找到了一种方法,但是它非常慢且不可扩展。它适用于小型数据集,但是如果我将其应用于需要在其上运行的实际表上,那将花费很多时间。

我需要删除表中数据的全部重复子集。删除重复的行很容易,但是我一直在寻找一种有效的方法来删除重复的子集。

示例:

GroupID  Subset Value
-------  ----   ----
1        a      1
1        a      2
1        a      3

1        b      1
1        b      3
1        b      5

1        c      1
1        c      3
1        c      5


2        a      1
2        a      2
2        a      3

2        b      4
2        b      5
2        b      6

2        c      1
2        c      3
2        c      6

因此,在此示例中,我将需要从GroupID 1中删除子集“ b”或子集“ c”,因为这两个都包含值1,2,3,所以这无关紧要。对于GroupID 2,没有任何集合可以重复,因此都不会删除。

这是我用来解决此问题的代码。它很好用,但是当应用于10百万条记录时...您可以想象它会非常慢(后来我被告知记录数,我得到的样本数据要小得多)...:

DECLARE @values TABLE (GroupID INT NOT NULL, SubSet VARCHAR(1) NOT NULL, [Value] INT NOT NULL)
INSERT INTO @values (GroupID, SubSet, [Value])
VALUES  (1,'a',1),(1,'a',2),(1,'a',3)  ,(1,'b',1),(1,'b',3),(1,'b',5)  ,(1,'c',1),(1,'c',3),(1,'c',5),
        (2,'a',1),(2,'a',2),(2,'a',3)  ,(2,'b',2),(2,'b',4),(2,'b',6)  ,(2,'c',1),(2,'c',3),(2,'c',6)

SELECT *
FROM @values v
ORDER BY v.GroupID, v.SubSet, v.[Value]

SELECT x.GroupID, x.NameValues, MIN(x.SubSet)
FROM (
    SELECT t1.GroupID, t1.SubSet
        , NameValues = (SELECT ',' + CONVERT(VARCHAR(10), t2.[Value]) FROM @values t2 WHERE t1.GroupID = t2.GroupID AND t1.SubSet = t2.SubSet ORDER BY t2.[Value] FOR XML PATH(''))
    FROM @values t1
    GROUP BY t1.GroupID, t1.SubSet
) x
GROUP BY x.GroupID, x.NameValues

我在这里要做的就是按GroupID和Subset分组,然后将所有值连接到一个逗号分隔的字符串中……然后将其分组到GroupID和Value列表中,并获取MIN子集。

3 个答案:

答案 0 :(得分:4)

我会选择这样的东西:

;with cte as
(
    select v.GroupID, v.SubSet, checksum_agg(v.Value) h, avg(v.Value) a
    from @values v
    group by v.GroupID, v.SubSet
)

delete v
from @values v
join
(
    select c1.GroupID, case when c1.SubSet > c2.SubSet then c1.SubSet else c2.SubSet end SubSet
    from cte c1
    join cte c2 on c1.GroupID = c2.GroupID and c1.SubSet <> c2.SubSet and c1.h = c2.h and c1.a = c2.a
)x on v.GroupID = x.GroupID and v.SubSet = x.SubSet

select *
from @values

答案 1 :(得分:2)

来自Checksum_Agg

  

CHECKSUM_AGG结果不取决于行中的顺序   桌子。

这是因为它是以下值的总和:1 + 2 + 3 = 3 + 2 + 1 = 3 + 3 = 6

HashBytes旨在为两个输入产生不同的值,这些输入仅在字节顺序和其他差异上有所不同。 (很有可能两个输入(可能长度截然不同)可能散列为相同的值。您不能采用任意输入并将其压缩为绝对唯一的16字节值。)

以下代码演示了如何使用HashBytes为每个GroupId / Subset返回。

-- Thanks for the sample data!
DECLARE @values TABLE (GroupID INT NOT NULL, SubSet VARCHAR(1) NOT NULL, [Value] INT NOT NULL)
INSERT INTO @values (GroupID, SubSet, [Value])
VALUES  (1,'a',1),(1,'a',2),(1,'a',3)  ,(1,'b',1),(1,'b',3),(1,'b',5)  ,(1,'c',1),(1,'c',3),(1,'c',5),
        (2,'a',1),(2,'a',2),(2,'a',3)  ,(2,'b',2),(2,'b',4),(2,'b',6)  ,(2,'c',1),(2,'c',3),(2,'c',6);

SELECT *
FROM @values v
ORDER BY v.GroupID, v.SubSet, v.[Value];

with
  DistinctGroups as (
    select distinct GroupId, Subset
      from @Values ),
  GroupConcatenatedValues as (
    select GroupId, Subset, Convert( VarBinary(256), (
      select Convert( VarChar(8000), Cast( Value as Binary(4) ), 2 ) AS [text()]
        from @Values as V
        where V.GroupId = DG.GroupId and V.SubSet = DG.SubSet
        order by Value
        for XML Path('') ), 2 ) as GroupedBinary
     from DistinctGroups as DG )
  -- To see the intermediate results from the CTE you can use one of the
  --   following two queries instead of the last   select :
  --   select * from DistinctGroups;
  --   select * from GroupConcatenatedValues;
  select GroupId, Subset, GroupedBinary, HashBytes( 'MD4', GroupedBinary ) as Hash
    from GroupConcatenatedValues
    order by GroupId, Subset;

答案 2 :(得分:1)

您可以在一组行上使用checksum_agg()。如果校验和相同,则有力的证据表明分组字段中的“值”列相等。

在下面的'getChecksums'cte中,我按组和子集分组,并根据“值”列添加了校验和。

在'maybeBadSubsets'cte中,我在每个聚合上放置了一个row_number,目的是在校验和匹配的情况下识别第二+行。

最后,我删除如此确定的所有子组。

with

    getChecksums as (

        select      groupId,
                    subset,
                    cs = checksum_agg(value)
        from        @values v
        group by    groupId,
                    subset 

    ),

    maybeBadSubsets as (

        select      groupId,
                    subset,
                    cs,

                    deleteSubset = 
                        case 
                        when    row_number() over (
                                    partition by groupId, cs 
                                    order by subset
                                ) > 1 
                        then 1
                        end

        from        getChecksums

    )

    delete      v 
    from        @values v
    where       exists (
                    select  0
                    from    maybeBadSubsets mbs
                    where   v.groupId = mbs.groupId
                    and     v.SubSet = mbs.subset
                    and     mbs.deleteSubset = 1
            );

我不知道匹配校验和的确切可能性是多少。如果您对误报率不满意,仍然可以使用它以一种更具算法性的方法消除一些分支,从而大大提高性能。

注意:CTE在性能方面可能会有古怪之处。如果发现查询引擎正在为@values的每一行运行“ maybeBadSubsets”,则可能需要在使用前将其结果放入临时表或表变量中。但是我相信只要存在,您就可以了。

编辑:

我没有抓住它,但是正如OP所注意到的,就错误命中/未命中而言,checksum_agg的表现似乎很差。我怀疑这可能是由于输入的简单性。我改变了

cs = checksum_agg(value)

高于

cs = checksum_agg(convert(int,hashbytes('md5', convert(char(1),value))))

并获得更好的结果。但是我不知道它在更大的数据集上的表现。