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

cha*_*win 5 sql t-sql sql-server

我正在使用的数据相当复杂,所以我将提供一个更简单的示例,以便我可以将其扩展到我正在处理的内容.

注意:我已经找到了一种方法,但它非常慢且不可扩展.它适用于小型数据集,但是如果我将它应用到需要运行的实际表中,则需要永远.

我需要删除表中的所有重复数据子集.删除重复行很容易,但我发现找到一种有效的方法来删除重复的子集.

例:

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
Run Code Online (Sandbox Code Playgroud)

所以在这个例子中,从GroupID 1开始,我需要删除子集'b'或子集'c',因为它们都包含值1,2,3并不重要.对于GroupID 2,没有任何集重复,因此不会删除任何集.

这是我用来小规模解决这个问题的代码.它工作得很好,但是当应用到超过100万条记录时...你可以想象它会非常慢(后来我被告知记录的数量,我得到的样本数据要小得多)......:

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
Run Code Online (Sandbox Code Playgroud)

我在这里所做的就是按GroupID和Subset进行分组,并将所有值连接成逗号分隔的字符串......然后将其分组并分组在GroupID和Value列表上,并获取MIN子集.

pwi*_*cox 1

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

在下面的“getChecksums”cte 中,我按组和子集进行分组,并根据您的“值”列进行校验和。

在“maybeBadSubsets”cte 中,我在每个聚合上放置了一个 row_number,以便在校验和匹配的情况下识别第 2+ 行。

最后,我删除如此标识的任何子组。

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
            );
Run Code Online (Sandbox Code Playgroud)

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

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

编辑:

我没有注意到,但正如OP注意到的那样,checksum_agg似乎在错误命中/未命中方面表现得很差。我怀疑这可能是由于输入的简单性造成的。我变了

cs = checksum_agg(value)
Run Code Online (Sandbox Code Playgroud)

以上至

cs = checksum_agg(convert(int,hashbytes('md5', convert(char(1),value))))
Run Code Online (Sandbox Code Playgroud)

并得到了更好的结果。但我不知道它在更大的数据集上的表现如何。