从数百万条记录中删除重复行的有效方法

时间:2016-05-10 12:54:31

标签: sql-server sql-server-2008 duplicates database-performance query-performance

我希望找到一种从我的数据库中删除重复记录的有效方法。首先,我使用了一个使用连接等的存储过程,这导致查询执行速度非常慢。现在,我正在尝试一种不同的方法。请考虑以下问题:

/* QUERY A */

SELECT *
FROM my_table
WHERE col1 = value
  AND col2 = value
  AND col3 = value

此查询仅在12秒内执行,结果为182.400条记录。表中的行数目前为420.930.407,并且col1和col3已编入索引。

下一个查询:

/* QUERY B */

WITH ALL_RECORDS AS
  (SELECT id
   FROM my_table
   WHERE col1 = value
     AND col2 = value
     AND col3 = value)
SELECT *
FROM ALL_RECORDS

这个查询花了不到2秒的时间,并给出了表中182.400条记录的所有id(根据where子句)。

然后,我的上一个查询是一个查询,它选择在我要分组的列上分组的所有记录的最低(第一个)ID,以检查重复项:

/* QUERY C */

SELECT MIN(id)
FROM my_table
WHERE col1 = value
  AND col2 = value
  AND col3 = value
GROUP BY col1,
         col2,
         col3,
         col4,
         col5,
         col6

同样,此查询在不到2秒的时间内执行。结果是30.400,这意味着182.400条记录中有30.400条唯一记录是唯一的。

现在,我想删除(或者,首先选择以确保我的查询正确)所有非独特的记录。所以,我想从my_table中删除182.400 - 30.400 = 152.000条记录。

我以为我将最后两个查询合并:根据col1,col2和col3(查询B)上的where子句获取属于我的数据集的所有id,然后删除/全选来自该数据集的记录,其id不在唯一记录ID的id列表中(查询C)。

然而,当我从查询B中选择所有查询B.id NOT IN查询C时,查询不需要2,4或12(14或16)秒,但似乎需要永远(在1之后显示20.000条记录)分钟,2分钟后大约40.000,所以我取消了查询,因为它会找到152.000条记录,这将花费8分钟这样)。

WITH ALL_RECORDS AS
  (SELECT id
   FROM my_table
   WHERE col1 = value
     AND col2 = value
     AND col3 = value)
SELECT id
FROM ALL_RECORDS
WHERE id NOT IN
    (SELECT MIN(id)
     FROM my_table
     WHERE col1 = value
       AND col2 = value
       AND col3 = value
     GROUP BY col1,
              col2,
              col3,
              col4,
              col5,
              col6)

我知道NOT IN速度很慢,但我无法理解它的速度有多慢(因为两个查询都不会在不到2秒的时间内执行)。

有没有人对我如何解决这个难题有一些好的建议?

------------------补充资料------------------

以前的解决方案是以下存储过程。出于某种原因,它在我的接受环境中完美地执行,但不在我的生产环境中。目前,我们有超过4亿条生产记录和200多万条接受记录,所以这可能是一个原因。

DELETE my_table
FROM my_table
LEFT OUTER JOIN
  (SELECT MIN(id) AS RowId,
          col1,
          col2,
          col3,
          col4,
          col5,
          col6
   FROM my_table
   WHERE col1 = value
     AND col2 = value
     AND col3 = value
   GROUP BY col1,
            col2,
            col3,
            col4,
            col5,
            col6) AS KeepRows ON my_table.id = KeepRows.RowId
WHERE KeepRows.RowId IS NULL
  AND my_table.col1 = value
  AND my_table.col2 = value
  AND my_table.col3 = value

我已将此解决方案基于stackoverflow上的另一个答案(目前无法找到它),但我觉得我应该能够基于在几秒钟内执行的查询B和C创建查询。 ..

2 个答案:

答案 0 :(得分:1)

将两个2秒查询组合在一起通常不会导致单个4秒查询,因为与基础表不同,查询很少被编入索引。

此类任务的常用方法是将您要保留的id缓存在临时表中,相应地对其进行索引,然后在left join中使用它(或{{ 1}} - 我打赌生成的执行计划实际上是相同的。)

如果您将在主表上使用索引,则可以获得更多性能。例如,我认为not in应该为您的代码提供一些提升(列不一定按此顺序提及,通常取决于它们的基数)。

答案 1 :(得分:1)

with dupl as (
select row_number() over(partition by col1,col2,col3,col4,col5,col6 order by id) rn,
id,col1,col2,col3,col4,col5,col6
from myTable
)
delete dupl where rn>1