【问题标题】:Mysql - deleting duplicatesMysql - 删除重复项
【发布时间】:2012-10-29 19:54:28
【问题描述】:

我有一个带有唯一索引的条形码列的表。数据已在每个条形码的末尾加载了额外的字符(-xx)以防止重复,但是一旦我去掉后缀,就会有很多重复。以下是数据示例:

itemnumber  barcode

17912       2-14
18082       2-1
21870       2-10
29219       2-8

然后我创建了两个临时表,marty 和 manny,都带有 itemnumber 和剥离的条形码。所以,两个表都包含

itemnumber  barcode

17912       2
18082       2
21870       2
29219       2

我试图删除除 marty 表中条形码“2”的​​第一个条目(以及所有其他条形码)之外的所有条目。然后我希望用正确的第一个条目更新原始表,并且用户可以在应用程序中及时修复重复项。

所以,这是我的查询,要删除每个条形码的 marty 表中除第一个条目之外的所有条目

DELETE FROM marty
  WHERE itemnumber NOT IN
    (SELECT MIN(itemnumber) FROM manny GROUP BY barcode)

marty 和 manny 有 130,000 行。查询花费了 24 小时,然后没有正确完成。与服务器的连接崩溃,查询没有进行所有更新。

有没有更好的方法来解决这个问题,而不是我们的子查询,我认为这会导致延迟?并且 group by 可能也因记录如此之多而放慢了速度。

谢谢

【问题讨论】:

  • itemnumber 是唯一的吗?如果是,我认为您的方法最终会奏效,尽管效率低下,因为它会在整个“manny”表中搜索“marty”的每一行。

标签: mysql sql duplicates


【解决方案1】:

另一个变体:此变体无需任何用于删除重复项的临时表即可工作:

 Delete m1
 From Marty m1
 join Marty m2 
    on m1.barcode = m2.barcode 
    and m1.itemnumber > m2.itemnumber

【讨论】:

    【解决方案2】:

    这是一种避免使用NOT IN 的两阶段方法。它也不使用临时表“manny”。首先,将“marty”连接到自身以挑选出 itemnumber != min(itemnumber) 的行。使用UPDATE 将这些行的barcode 设置为NULL。使用DELETE 进行第二次传递,然后删除在第一阶段标记的所有行。

    对于本例,我将“marty”的barcode 列拆分为两列;可以对原始格式的表格进行一些修改(需要动态拆分列值)。

    select * from marty;
    +------------+---------+---------+
    | itemnumber | barcode | subcode |
    +------------+---------+---------+
    |      17912 |       2 |      14 |
    |      18082 |       2 |       1 |
    |      21870 |       2 |      10 |
    |      29219 |       2 |       8 |
    |      30133 |       3 |       5 |
    |      30134 |       3 |       7 |
    |      30139 |       3 |       9 |
    |      30142 |       3 |      12 |
    +------------+---------+---------+
    8 rows in set (0.00 sec)
    
    UPDATE
      (marty m1
       JOIN
         (SELECT barcode,
                 MIN(itemnumber) AS itemnumber
          FROM marty
          GROUP BY barcode) m2
       USING(barcode))
    SET m1.barcode = NULL WHERE m1.itemnumber != m2.itemnumber;
    
    mysql> select * from marty;
    +------------+---------+---------+
    | itemnumber | barcode | subcode |
    +------------+---------+---------+
    |      17912 |       2 |      14 |
    |      18082 |    NULL |       1 |
    |      21870 |    NULL |      10 |
    |      29219 |    NULL |       8 |
    |      30133 |       3 |       5 |
    |      30134 |    NULL |       7 |
    |      30139 |    NULL |       9 |
    |      30142 |    NULL |      12 |
    +------------+---------+---------+
    8 rows in set (0.00 sec)
    
    DELETE FROM marty WHERE barcode IS NULL;
    

    【讨论】:

      【解决方案3】:

      当使用IN 处理非常大的集合时,MySQL 的速度非常慢。脚本替代方案:

      使用脚本将一个长的itemnumber = X OR itemnumber = y OR itemnumber = z 子句(块大小~1000)和INSERT 匹配的行(即在您之前的查询中不会是DELETEd 的行)构造到一个新表中, TRUNCATE 现有的并使用INSERT INTO marty SELECT * FROM marty_tmp 将新表的内容加载回旧表。

      您可能希望锁定表或在事务中运行最终的TRUNCATEINSERT

      编辑:

      • 从脚本中查询SELECT MIN(itemnumber) FROM manny GROUP BY barcode,将结果存储在desiredItemNumbers数组中
      • 批量处理 1000 个desiredItemNumber 并构造此查询:INSERT INTO manny_tmp SELECT * FROM manny WHERE itemnumber = desiredItemNumbers[0] OR itemnumber = desiredItemNumbers[1] ...。重新运行此查询,直到您用尽所需的ItemNumbers 数组(注意,最后一个查询可能少于 1000 个所需的ItemNumbers)。
      • 您现在有了一个表格,其中包含您在 DELETEd 其余部分时会留下的结果,因此交换 martymarty_tmp 表格的内容。
      • TRUNCATE marty
      • INSERT INTO marty SELECT * FROM marty_tmp

      【讨论】:

      • 嗨,安迪,我不完全遵循“构造一个长......”我仍然需要识别重复条形码列表的第一个项目编号。这就是我遇到问题的地方,我知道实现这一目标的唯一方法是使用 select in 和 group by,这太慢了。马丁麦克
      【解决方案4】:

      如果您仍然要创建临时表,如何使用“INSERT INTO”或“CREATE TABLE .. AS ...”来构建您的表,基于:

      SELECT MIN(itemnumber) AS itemnumber, barcode
        FROM marty
        GROUP BY barcode
      

      【讨论】:

      • 格伦,太好了。很好很简单,真的很快,而且效果很好。我只需要使用 substr(barcode,1,length(barcode)) 来去除已使用的后缀。谢谢一百万。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-10-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-14
      • 1970-01-01
      相关资源
      最近更新 更多