【问题标题】:Remove duplicated subsets from very large table从非常大的表中删除重复的子集
【发布时间】:2019-02-09 05:31:47
【问题描述】:

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

注意:我已经找到了一种方法,但它非常缓慢且不可扩展。它在小型数据集上效果很好,但如果我将它应用到它需要运行的实际表上,则需要很长时间。

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

例子:

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 子集。

【问题讨论】:

  • 您好,您是否尝试同时执行此查询,但使用 off cte ?只是为了比较性能建议。
  • @pascalsanchez 不,我没有,但我会试一试。我想知道是否有办法通过递归 CTE 来实现这一点,但我认为对于大表来说这不会更有效。

标签: sql sql-server tsql


【解决方案1】:

我会选择这样的:

;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

【讨论】:

  • 很好,通过了第一次测试,给了我想要的结果。我将在完整的数据集上对其进行测试,看看它在超过 1000 万条记录上的表现。
  • 刚刚在更大的数据集上运行它,肯定比我的原始代码运行得更快。谢谢!
  • @chadwin,很酷。我已经更新了查询,你可以试试新版本,可能会快一点。
  • 所以我更深入地研究了这一点。似乎使用 CHECKSUM_AGG 和 AVG 仍然会产生一些错误匹配,但百分比非常低。但是,我已经扩展了您的方法,并包含了其他聚合字段,如 SUM、COUNT、MIN、MAX 等。似乎 CHECKSUM_AGG、AVG、COUNT、SUM 是迄今为止唯一一个与我的原始代码完美匹配的字段.但我正在测试聚合函数的其他组合,看看哪些表现更好,但仍然产生相同的结果。
  • 如果您有兴趣...我对使用 CHECKSUM_AGG、AVG、COUNT、SUM、MIN、MAX 的所有 64 种可能组合进行了测试...产生的两个聚合函数的唯一组合零错误匹配是 SUM & (MIN 或 MAX) ||| CHECKSUM_AGG & SUM。
【解决方案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;

【讨论】:

    【解决方案3】:

    您可以在一组行上使用 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
                );
    

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

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

    编辑:

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

    cs = checksum_agg(value)
    

    高于

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

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

    【讨论】:

    • 谢谢!我会试试这个,看看它的表现如何。对于我的具体用途,它不需要完美,我们只是试图减少数据。如果还剩下一些dup,那可能会很好。我实际上想过使用校验和,但我不知道如何使它工作,我完全忘记了它的聚合函数。
    • 嗯,它看起来确实可能存在错误,因为它从 GroupID 2 中删除了子集“b”,而它不应该有。我会试着找出原因。
    • 啊,好吧,想通了,看起来不仅 CHECKSUM_AGG 会产生误报,而且我没有考虑误报。它删除了具有匹配校验和值但实际上不是等效子集的子集。
    • 嘿,我想现在您将使用 Kirill 的 cte 方法。但是,您能否尝试一下上面提到的编辑更改以满足我的好奇心?
    • 会的!星期五我会回到办公室,对不起,我不得不让你保持悬念。大声笑
    猜你喜欢
    • 2020-11-19
    • 2019-02-23
    • 1970-01-01
    • 2019-01-25
    • 2012-04-06
    • 1970-01-01
    • 1970-01-01
    • 2015-01-01
    • 2014-07-26
    相关资源
    最近更新 更多