从具有 NULL 列的大表中删除重复项,这也需要考虑

Posted

技术标签:

【中文标题】从具有 NULL 列的大表中删除重复项,这也需要考虑【英文标题】:Removing duplicates from a large table with NULL columns that also need to be taken into account 【发布时间】:2017-12-08 02:26:49 【问题描述】:

我有兴趣从一个非常大的表(40+ 百万条记录,3+ GB 大小)中删除重复项,在确定重复项时还必须计算 NULL 列。 5.5.56-MariaDB,表格为InnoDB格式。

我遇到的问题是一些推荐的方法在 NULL 字段比较时不起作用。例如,我有一个表,它有大约十几个列,其中许多列可以包含 NULL 值,并且我希望 NULL 被视为任何其他值,因为如果一行在相同列中具有相同的 NULL 值,则它被认为是骗人的。

以下技术不起作用:

create table tmp like mytable;
alter table tmp ADD UNIQUE INDEX(field1,field2, field3,...);
insert IGNORE into tmp (select * from mytable);

此时,tmp 应该已删除所有重复项,但唯一索引中忽略了 NULL 列,因此它不起作用。如果它确实有效,那么我可以重命名这些表,或者截断原始表并复制过来(假设我想在原始表中保留一个主键)。

在处理 NULL 列时,删除大文件中的重复数据最有效的方法是什么?

附加说明:我有一个名为“recno”的字段,它是需要忽略的自动增量。

【问题讨论】:

您最终会得到少于一半的行数吗?或者更多?到目前为止,重点是前者。后者导致DELETEing dups。 如果只有几列可以NULL(或只有几列组合),那我可能有一个设计。有多少? 我估计最多可能有 10-20% 的数据库被复制。不过可能更多。表中大约有 23 个不同类型的列,其中大多数可以有一个 NULL 值,包括一堆 VARCHAR(2) 保存“Y”“N”或 NULL,这会导致问题。 NULL 是有意义的,需要在检测欺骗时加以考虑。 以下查询已被证明有效,但它已经运行了超过 48 小时.. 必须有更快的方法:DELETE a FROM t1_test as a, t1_test as b WHERE (a.provnum =b.provnum) AND (a.field1=b.field1 OR (a.field1 IS NULL AND b.field1 IS NULL)) [20 其他列] AND (a.recno>b.recno); 我有几个大表我必须运行它。 【参考方案1】:

您可以使用如下查询来选择它们:

  SELECT field1, field2, field3, count(1)
    FROM t
GROUP BY field1, field2, field3
  HAVING count(1) > 1

然后用代码删除它们或使用某种控制逻辑,无论 mysql 中可用什么。我相信还有其他方法可以做到这一点。

【讨论】:

我需要一个 mysql/mariadb 特定的解决方案。我不确定你的建议是什么。 @TrentThree,该语法适用于 MySql 和 MariaDb。 可以添加额外的控制逻辑吗?我不确定这将如何工作......上面的查询只是显示了欺骗记录?假设我有一个名为“recno”的rowid列,我可以在recno =(select rowid ....)的某种表中使用它吗? 另外,对于有 40+ 百万条记录的表,GROUP BY 不是一个非常耗时的查询吗? 不知道时间,你得测试一下。 Group By 有效,我不知道理论,但我确信 mysql 已经弄清楚了。至于控制逻辑,我正在考虑使用游标或 T-SQL 或 C 或 Java 来删除查询的结果。我不知道mysql中有什么可用的。很高兴你明白了。【参考方案2】:

哦,那是一个挑战。

CREATE TABLE _t LIKE real;
INSERT INTO _t
    SELECT MIN(recno),   -- or MAX, if you prefer
           a,b,c,...,w   -- all the other columns
        FROM real
        GROUP BY
           a,b,c,...,w,
           ((a IS NULL) << 0) |
           ((b IS NULL) << 1) |
           ...
           ((w IS NULL) << 22) ;
-- Note: This will not work for more than 64 columns.

-- Put it in place (with no downtime):
RENAME TABLE real TO old, _t TO real;
DROP TABLE old;

【讨论】:

【参考方案3】:

为什么不这样做呢?

create table tmp as
    select distinct t.*
    from mytable t;

编辑:

如果你想要第一个最小值的所有字段:

create table tmp as
    select min(id), field1, field2, . . .
    from mytable t
    group by field1, field2, . . .

【讨论】:

我还有一个需要忽略的自动增量字段。 @TrentThree 。 . .那就别用*了,把你关心的列都列出来。 有什么办法可以保存recno A.I.列? @TrentThree 。 . .你可以使用min() min() 在 select 查询中似乎不起作用 - 不确定如何在查询中混合不同的列和非不同的列..【参考方案4】:

这是我使用的方法。我最终不得不重新创建表并重命名它,但是对于一个 3.1GB 和 4200 万条记录的数据库(大约 977,000 次重复),处理时间约为 90 分钟):

drop table if exists tmp;
create table tmp AS
select distinct field1, field2, field3, ...
 from mytable;
-- add back my rowid
ALTER TABLE `tmp` ADD `recno` INT UNSIGNED NOT NULL AUTO_INCREMENT 
-- add any indexes you were using

-- rename tables
rename table mytable to table_hold;
rename table tmp to mytable;

【讨论】:

以上是关于从具有 NULL 列的大表中删除重复项,这也需要考虑的主要内容,如果未能解决你的问题,请参考以下文章

从 MySQL 中具有不同列的表的多个连接结果中删除重复项

从 SQL Server 中的大表中删除大部分数据的策略

使用laravel从大表中删除重复项[关闭]

为 Access 中的 JOIN 中的第二个重复行返回 null

从大表中选择非空字段

从mysql中的大表中快速选择随机行