【问题标题】:Delete duplicate records without creating a temporary table删除重复记录而不创建临时表
【发布时间】:2011-12-21 12:42:53
【问题描述】:

我有一个包含许多重复记录的表:

shop
ID     tax_id
1      10
1      10
1      11
2      10
2      12
2      10
2      10

我想删除所有重复记录而不创建临时表。 更新查询后,表格应如下所示:

shop
ID     tax_id
1      10
1      11
2      10
2      12

【问题讨论】:

  • 临时表有什么问题?
  • 什么是重复?如果 id/tax_id 具有相同的值,或者如果您有多个具有相同 ID 的行(ID 应该是 PK,应该是唯一的 - 或者您的“ID”是 FK)
  • @SergeiTulentsev:需要空间。
  • 这是暂时的。它暂时占用空间。大多数人都可以忍受这一点。 :-)
  • @Tomalak,我们实际上不知道表中有多少(不同的值/值)。我们不知道是否有索引(我相信没有)。如果表很大,没有索引就很难使用。如果它很小 - 临时表将是最简单的解决方案。除了权限之外,我没有看到任何不使用它们的理由。

标签: php mysql


【解决方案1】:

这是一个就地解决方案(但不是单行)

找出最大 id:

select max(id) as maxid 
  from shop;

记住这个值。假设它等于 1000;

重新插入唯一值,带偏移量:

insert into shop (id, tax_id) 
select distinct id + 1000, tax_id 
  from shop;

删除旧值:

delete from shop
  where id <= 1000;

恢复正常的id:

update shop
  set id = id - 1000;

利润!

【讨论】:

  • 到目前为止,此解决方案是唯一符合所有条件的解决方案。你应该赞成它。 :-)
  • 复制条目并不是很好的做法或实际操作,想象一下在具有多于少量记录的表上实现这一点 INSERT 语句不分青红皂白,因此只会存储两个(可能是错误的取决于表格是否已排序)示例中的条目。
  • 这不是你每秒运行 500 次的操作。这是数据损坏修复。即使需要几秒钟,也应该是可以接受的(鉴于情况)。我不知道你在说什么不分青红皂白,但这个解决方案正好给出了 OP 正在寻找的答案。
  • 这也可能是一个临时表。
  • @TomalakGeret'kal 然而事实并非如此。 :-) 也许用户没有创建表的权限。
【解决方案2】:

工作解决方案。

//Sql query to find duplicates
SELECT id, tax_id, count(*) - 1 AS cnt 
  FROM shop 
  GROUP BY id
  HAVING cnt > 1

--- res

+------+--------+-----+
| id   | tax_id | cnt |
+------+--------+-----+
|    1 |     10 |   2 |
|    2 |     10 |   3 |
+------+--------+-----+


//Iterate through results with your language of choice
DELETE 
  FROM shop 
  WHERE id=<res id> 
    AND tax_id=<res tax_id> 
  LIMIT <cnt - 1>

---res (iterated)

+------+--------+
| id   | tax_id |
+------+--------+
|    1 |     10 |
|    1 |     11 |
|    2 |     12 |
|    2 |     10 |
+------+--------+

这两个查询需要一小段 php 来执行删除操作

$res = mysql_query("SELECT id, tax_id, count(*) - 1 AS cnt 
                      FROM shop 
                      GROUP BY id
                      HAVING cnt > 1")
while($row = mysql_fetch_assoc($res)){
    mysql_query("DELETE 
                   FROM shop 
                   WHERE id=".$row['id']."
                       AND tax_id=". $row['tax_id']."
                   LIMIT ".$row['cnt'] -1 . ");
}

编辑:最近重温了这篇文章,值得一提的是,这里有一个使用临时列的替代解决方案,无需脚本语言。

ALTER TABLE shop ADD COLUMN place INT;

SET @i = 1

UPDATE shop SET place = @i:= @i + 1;

DELETE FROM shop WHERE place NOT IN (SELECT place FROM items GROUP BY id, tax_id);

ALTER TABLE shop DROP COLUMN place;

【讨论】:

  • 有内存限制,我会选择类似的东西。
  • 谢谢谢尔盖,你很有建设性,尽管是可耻的 cmets,给了我必要的动力。
【解决方案3】:

首先,您可以通过在这两个字段上创建唯一索引来防止这种情况发生,以供将来参考。

至于解决方法,在mysql中新建一个结构相同的表shopnew,或者在生成recordList的时候直接删除表中的每条记录(确保有备份!):

//Get every record from mysql
$sSQL = "Select ID, tax_id from shop";
$oRes = mysql_query($sSQL);
$aRecordList = array();
while($aRow = mysql_fetch_assoc($oRes)){
      //If record is a duplicate, it will be 'overwritten' 
      $aRecordList[$aRow['id'].".".$aRow['tax_id']] =1; 
}
//You could delete every record from shop here, if you dont want an additional table
//recordList now only contains unique records
foreach($aRecordList as $sRecord=>$bSet){
   $aExpRecord = explode(".",$sRecord);
   mysql_query("INSERT INTO shopnew set id=".$aExpRecord[0].", tax_id = ".$aExpRecord[1]
 }

【讨论】:

  • 在示例中,ID 和 tax_id 都不是唯一的。每个商店都有多个关联的 tax_id。
  • 是的,但是您可以在两者上创建唯一索引!这就是你要找的。因此,组合的两个字段不能已经存在。检查:mysqlfaqs.net/mysql-faqs/Indexes/Unique-Key-or-Index/…
  • 是的,所以?他的解决方案保持了对 (id, tax_id) 的唯一性(至少,据我所知)。虽然它违反了不创建新表的条件。
  • 啊,我看错了你的解决方案。现在说得通了 :) 第 7 行缺少右括号。
  • 对,在 SO textarea 中编码有点困难,应该在我的 IDE 中这样做:)
【解决方案4】:

也许这会有所帮助:

$query="SELECT * FROM shop ORDER BY id";
$rez=$dbh->query($query);
$multi=$rez->fetchAll(PDO::FETCH_ASSOC);
foreach ($multi as $key=>$row){
$rest=array_slice($multi,$key+1);
foreach ($rest as $rest){
    if(($row['id']==$rest['id']) && ($row['tax_id']==$rest['tax_id'])){
        $dbh->query("DELETE FROM shop WHERE id={$rest['id']} and tax_id=     {$rest['tax_id']}");

    }
}

}

第一个 foreach 迭代每一行,第二个进行比较。 我正在使用 PDO,但当然,您可以通过程序方式进行。

【讨论】:

    【解决方案5】:

    实际上,具有当前局限性的问题是一个相当棘手的挑战。我整个晚上都在考虑解决方案(理解解决方案永远不会有用)。我不会在野外使用该解决方案,我只是试图找出是否可以仅使用 MySQL。

    我的表述中的问题:是否可以编写一系列 DELETE 语句来从没有唯一约束的两列表中删除重复行?

    问题:

    1. 行没有标识键或主键,因此应该想出一种方法来引用应该保留的单行
    2. 我们需要以某种方式对行进行分组,即先应用顺序然后应用条件,但支持ORDER BYDELETE 形式只能有WHERE 子句,不支持HAVING。即满足条件后应用顺序。
    3. 如果值按集群主键排列,我们就不需要对行进行排序,但我们没有。

    假设我们有一张桌子:

    CREATE TABLE  `tablename` (
      `a_id` int(10) unsigned NOT NULL,
      `b_id` int(10) unsigned NOT NULL,
      KEY `Index_1` (`a_id`,`b_id`)
    ) ENGINE=InnoDB COLLATE utf8_bin;
    

    我添加了一个键(不是 UNIQUE 或 PRIMARY)以加快查找速度并希望在分组中使用它。

    您可以为表格提供一些值:

    INSERT INTO tablename (a_id, b_id) VALUES (2, 3), (1, 1), (2, 2), (1,4);
    INSERT INTO tablename (a_id, b_id) VALUES (2, 3), (1, 1), (2, 2), (1,4);
    INSERT INTO tablename (a_id, b_id) VALUES (2, 3), (1, 1), (2, 2), (1,4);
    

    作为一个副作用,键变成了覆盖索引,当我们从表中进行选择时,显示的值会被排序,但是当我们删除时,值会按照我们插入它们的顺序读取。

    现在,让我们看看下面的查询:

    SELECT @c, @a_id as a, @b_id as b, a_id, b_id
    FROM tablename, (SELECT @a_id:=0, @b_id:=0, @c:=0) as init
    WHERE (@c:=IF(LEAST(@a_id=(@a_id:=a_id), @b_id=(@b_id:=b_id)), @c+1, 1)) >= 1
    ;
    

    及其结果:

    @c, a, b, a_id, b_id
     1, 1, 1,    1,    1
     2, 1, 1,    1,    1
     3, 1, 1,    1,    1
     1, 1, 4,    1,    4
     2, 1, 4,    1,    4
     3, 1, 4,    1,    4
     1, 2, 2,    2,    2
     2, 2, 2,    2,    2
     3, 2, 2,    2,    2
     1, 2, 3,    2,    3
     2, 2, 3,    2,    3
     3, 2, 3,    2,    3
    

    使用Index_1 自动对结果进行排序,并在@c 列中枚举重复对(a_id, b_id)。那就是我们现在的任务是删除@c &gt; 1 所在的所有行。我们唯一的问题是强制 MySQL 在删除时使用Index_1,这在不应用附加条件的情况下相当棘手。但我们可以通过对a_id 使用相等检查或多重相等检查来做到这一点:

    DELETE FROM t
    USING tablename t FORCE INDEX (Index_1)
    JOIN (SELECT @a_id:=0, @b_id:=0, @c:=0) as init
    WHERE a_id IN (1)
      AND (@c:=IF(LEAST(@a_id=(@a_id:=a_id), @b_id=(@b_id:=b_id)), @c+1, 1)) > 1;
    
    DELETE FROM t
    USING tablename t FORCE INDEX (Index_1)
    JOIN (SELECT @a_id:=0, @b_id:=0, @c:=0) as init
    WHERE a_id IN (2)
      AND (@c:=IF(LEAST(@a_id=(@a_id:=a_id), @b_id=(@b_id:=b_id)), @c+1, 1)) > 1;
    
    SELECT * FROM tablename t;
    
    a_id, b_id
       1,    1
       1,    4
       2,    2
       2,    3
    

    我不能把所有可能的a_id 放在IN() 中,因为MySQL 会理解索引在这种情况下是无用的,并且查询不会删除所有重复项(仅相邻),但是说有10 个不同的a_id 我可以删除两个 DELETE 语句中的重复项,每个 IN 将有 5 个显式 id。

    希望,这可能对某人有用 =)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-04-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-10
      • 2014-12-19
      • 2018-11-05
      相关资源
      最近更新 更多