【问题标题】:Fastest technique to deleting duplicate data删除重复数据的最快技术
【发布时间】:2010-08-17 21:56:14
【问题描述】:

搜索 stackoverflow.com 后,我发现了几个问题,询问如何删除重复项,但都没有解决速度问题。

在我的例子中,我有一个包含 10 列的表,其中包含 500 万个精确的行重复。此外,我在 10 列中的 9 列中至少有一百万行重复。我目前的技术(到目前为止)需要 3 小时 来删除这 500 万行。这是我的过程:

-- Step 1:  **This step took 13 minutes.** Insert only one of the n duplicate rows into a temp table
select
    MAX(prikey) as MaxPriKey, -- identity(1, 1)
    a,
    b,
    c,
    d,
    e,
    f,
    g,
    h,
    i
into #dupTemp
FROM sourceTable
group by
    a,
    b,
    c,
    d,
    e,
    f,
    g,
    h,
    i
having COUNT(*) > 1

接下来,

-- Step 2: **This step is taking the 3+ hours**
-- delete the row when all the non-unique columns are the same (duplicates) and
-- have a smaller prikey not equal to the max prikey
delete 
from sourceTable
from sourceTable
inner join #dupTemp on  
    sourceTable.a = #dupTemp.a and
    sourceTable.b = #dupTemp.b and
    sourceTable.c = #dupTemp.c and
    sourceTable.d = #dupTemp.d and
    sourceTable.e   = #dupTemp.e and
    sourceTable.f = #dupTemp.f and
    sourceTable.g = #dupTemp.g and
    sourceTable.h = #dupTemp.h and
    sourceTable.i   = #dupTemp.i and
    sourceTable.PriKey != #dupTemp.MaxPriKey  

关于如何加快速度或更快的方法的任何提示?请记住,对于不完全重复的行,我将不得不再次运行它。

非常感谢。

更新:
我不得不停止第 2 步在 9 小时标记处运行。 我尝试了 OMG Ponies 的方法,只用了 40 分钟就完成了。 我用 Andomar 的批量删除尝试了我的第 2 步,它在我停止它之前运行了 9 个小时。 更新: 使用 OMG Ponies 的方法运行一个具有较少字段的类似查询以消除一组不同的重复项,并且该查询仅运行了 4 分钟(8000 行)。

下次有机会我会尝试 cte 技术,但是,我怀疑 OMG Ponies 的方法很难被击败。

【问题讨论】:

  • 对上面的查询进行了一些简单的优化 - 您不需要在顶部查询的 SELECT 中包含 a、b、c 等 - 您只需要 PriKey,然后删除 HAVING - 然后,在第二个查询中只需 DELETE FROM sourceTable WHERE PriKey NOT IN (SELECT DT.MaxPriKey FROM #dupTemp DT)

标签: sql sql-server sql-server-2008 etl


【解决方案1】:

存在呢:

DELETE FROM sourceTable
 WHERE EXISTS(SELECT NULL
                FROM #dupTemp dt
               WHERE sourceTable.a = dt.a 
                 AND sourceTable.b = dt.b 
                 AND sourceTable.c = dt.c 
                 AND sourceTable.d = dt.d 
                 AND sourceTable.e = dt.e 
                 AND sourceTable.f = dt.f 
                 AND sourceTable.g = dt.g 
                 AND sourceTable.h = dt.h 
                 AND sourceTable.i = dt.i 
                 AND sourceTable.PriKey < dt.MaxPriKey)

【讨论】:

  • 请解释为什么您认为这种方式会更快。
  • @sub13: EXISTS 与 JOIN 或 IN 不同 - 它在条件的第一次匹配时返回 true。理论上,更少的工作应该等于更快的查询。在相关说明中,this article 将解释和对比一些选项。
  • EXISTS() 中的所有列都需要非空吗?
  • @subt13:不,但是如果您要删除的数据中存在 NULL 列 - 仅出于性能考虑,最好知道这一点。
  • 这可以与 Andomar 的批量删除答案和 a_horse_with_no_name 的真实表与临时表答案相结合吗?
【解决方案2】:

您能否让原始表在短时间内不可用?

我认为最快的解决方案是创建一个没有重复的新表。基本上是您与临时表一起使用的方法,而是创建一个“常规”表。

然后删除原始表并将中间表重命名为与旧表相同的名称。

【讨论】:

  • 是的。普通表比临时表更快吗?请原谅我的无知:)
  • 可能是迄今为止提出的最快的解决方案 - 如果有外键等。如果你不小心,这会很痛苦并且容易出错,但绝对值得考虑。
  • @subt13:您需要常规表,因为您要保留它;)(与您的临时表相反)@WillA:是的,您是对的,需要注意约束。
  • 我明白了。我不必担心约束或外键。
【解决方案3】:

批量删除行的瓶颈通常是 SQL Server 必须建立的事务。您可以通过将删除拆分为较小的事务来大大加快速度。例如,一次删除 100 行:

while 1=1
    begin

    delete top 100
    from sourceTable 
    ...

    if @@rowcount = 0
        break
    end

【讨论】:

  • 这是一个非常有趣的想法。我一定会试试这个。
  • 顺便说一句:我不认为删除前 100 是有效的语法
  • @subt13: 是——见SQL Server 2008 BOL - DELETE
  • SQL Server 的 TOP 语法必须包含带有 DELETE 或 UPDATE 的括号,因此它应该是 DELETE TOP (100) ...
【解决方案4】:

...基于上面的 OMG Ponies 评论,一种更紧凑的 CTE 方法。这种方法在您(无论出于何种原因)没有主键的表上效果很好 - 您可以在所有列上拥有相同的行。

;WITH cte AS (
 SELECT ROW_NUMBER() OVER 
          (PARTITION BY a,b,c,d,e,f,g,h,i ORDER BY prikey DESC) AS sequence
    FROM sourceTable
)
DELETE
FROM cte
WHERE sequence > 1

【讨论】:

  • 酷。我以为我在帮忙,结果我得到了帮助。这比我的建议表现更好。
  • 这很紧凑,但我对速度更感兴趣。根据我对 ctes 的阅读和看到的内容,在我的情况下,它们只是语法糖。但是,如果我错了,请纠正我。
  • @subt13:您必须在比较各种选项之间的实际查询计划后告诉我们。
【解决方案5】:

很多不同的东西。首先是这样的工作(做一个选择来确保,甚至可以放入它自己的临时表,#recordsToDelete):

delete  
from sourceTable 
left join #dupTemp on   
       sourceTable.PriKey = #dupTemp.MaxPriKey   
where #dupTemp.MaxPriKey  is null

接下来你可以索引临时表,在 prikey 上放一个索引

如果您在临时表中有要删除的记录,则可以批量删除,这通常比使用删除锁定整个表要快。

【讨论】:

【解决方案6】:

这是一个版本,您可以将两个步骤合并为一个步骤。

WITH cte AS
    ( SELECT prikey, ROW_NUMBER() OVER (PARTITION BY a,b,c,d,e,f,g,h,i ORDER BY
        prikey DESC) AS sequence
    FROM sourceTable
    )

DELETE
FROM sourceTable
WHERE prikey IN
    ( SELECT prikey
    FROM cte
    WHERE sequence > 1
    ) ;

顺便问一下,有没有可以暂时删除的索引?

【讨论】:

  • Martin Smith 前几天展示了 CTE 可以作为 DELETE 源引用,其功能类似于可更新视图。
  • 是的,这是一个很酷的功能,我只是不确定与旧时尚#temp 表相比的效率。在这么多行上做任何事情都需要一段时间。我有一个聚集索引。如果需要更多,我当然可以添加它们。
【解决方案7】:

如果您使用的是 Oracle 数据库,我最近发现从总持续时间和 CPU 消耗的角度来看,以下语句的性能最好。 我已经执行了几个不同数据大小的测试,从几十行到几千行,总是在一个循环中。我使用 TKProf 工具来分析结果。

与上面的 ROW_NUMBER() 解决方案相比,这种方法占用了原始时间的 2/3,消耗了大约 50% 的 CPU 时间。它的行为似乎是线性的,即它应该在任何输入数据大小下给出类似的结果。

请随时给我您的反馈。不知道有没有更好的方法。

DELETE FROM sourceTable
WHERE
    ROWID IN(
        -- delete all
        SELECT ROWID
        FROM sourceTable t
        MINUS
        -- but keep every unique row
        SELECT
            rid
        FROM
            (
            SELECT a,b,c,d,e,f,g,h,i, MAX(ROWID) KEEP (DENSE_RANK FIRST ORDER BY ROWID) AS RID
            FROM sourceTable t
            GROUP BY a,b,c,d,e,f,g,h,i
            )
    )
;

【讨论】:

    猜你喜欢
    • 2012-05-23
    • 2010-11-02
    • 2013-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-03
    • 2020-07-01
    • 2012-06-05
    相关资源
    最近更新 更多