2010-07-21 91 views
4

我看到了创建具有唯一行的替代临时MySQL表的解决方案,但我不喜欢这个想法,因为我的表非常大,并且将它们移动起来很麻烦(并且如果会有的话会产生很大的问题移动中的错误)。这是一个很好的解决方案来删除重复的MySQL行吗?

但是,我找到了以下内容。你怎么看待这个(重复检查的地方是“field_name”)?

DELETE FROM table1 
USING table1, table1 as vtable 
WHERE (NOT table1.ID=vtable.ID) 
AND (table1.field_name=vtable.field_name) 

有人说这应该有效,但我不太确定。你怎么看?此外,将索引都改变这个命令的性能,比如说,在“field_name”上有一个索引?

编辑:有没有什么办法可以在运行之前测试查询?据我所知,MySQL不支持对DELETE查询进行“解释”。

+0

我正要发送另一个示例查询,但是,你有没有测试过这个吗?在我看来,这两个记录将被删除。 – Fosco 2010-07-21 19:18:25

+0

您可以用“选择”替换“删除”以测试您的查询。 – 2010-07-21 19:28:09

回答

4

请注意,您显示的查询将删除两个重复项。我会假设你想保留一个或另一个。

这是我会怎么写这个查询:

DELETE t1 FROM table1 AS t1 JOIN table1 AS t2 
    ON t1.id > t2.id AND t1.field_name = t2.field_name; 

通过使用大于代替未等于到,只删除一行(后来的一个),而不是两个。

(id,field_name)上的复合索引可能有帮助。您应该使用MySQL的EXPLAIN来确认以获取优化报告。但EXPLAIN只支持SELECT查询,所以你应该运行等效SELECT确认优化:

EXPLAIN SELECT * FROM table1 AS t1 JOIN table1 AS t2 
    ON t1.id > t2.id AND t1.field_name = t2.field_name; 

你还问有关测试。我建议你test数据库复制包含重复的表行的一个示例:

CREATE TABLE test.table1test SELECT * FROM realdb.table1 LIMIT 10000; 

现在,直到你满意的解决方案DELETE是正确的,你可以对你的样本数据的实验。

USE test; 
SET autocommit = 0; 
DELETE ... 
ROLLBACK; 

我建议你命名你从头表中test数据库的东西从你的真表的不同之处在你的真正的数据库。以防万一您在运行实验性的DELETE时意外地仍在使用您的真实数据库作为默认数据库!


回复您的意见:

USE test是mysql客户端内置的命令。它将test数据库设置为默认数据库。当你在查询中命名表而不用数据库名限定它们时,这将成为默认数据库。请参见http://dev.mysql.com/doc/refman/5.1/en/use.html

SET autocommit = 0会关闭隐式提交每个查询的事务的默认行为。因此,您必须明确指定COMMITROLLBACK命令完成交易。请参见http://dev.mysql.com/doc/refman/5.1/en/commit.html

当您尝试时使用ROLLBACK是值得的,因为它放弃了在该事务中所做的更改。这是一种快速返回到测试数据的初始状态的方法,以便您可以尝试其他实验。

DELETE t1不是拼写错误。 DELETE删除行,而不是整个表。 t1是满足语句条件(尽管条件可能包括表中的每一行)的每个的别名。见多表的描述删除在http://dev.mysql.com/doc/refman/5.1/en/delete.html

有点像当您运行PHP中的循环,并使用一个变量来遍历循环:for ($i=0; $i<100; ++$i) ...变量$i呈现一系列的值,每个通过循环的时间有不同的价值。

下面演示了我的解决方案如何删除多个副本。我在test数据库跑了这一点,我直接从我的命令窗口中粘贴的结果:

mysql> create table table1 (id serial primary key, field_name varchar(10)); 
Query OK, 0 rows affected (0.45 sec) 

mysql> insert into table1 (field_name) 
     values (42), (42), (42), (42), (42), (42); 
Query OK, 6 rows affected (0.00 sec) 
Records: 6 Duplicates: 0 Warnings: 0 

mysql> select * from table1; 
+----+------------+ 
| id | field_name | 
+----+------------+ 
| 1 | 42   | 
| 2 | 42   | 
| 3 | 42   | 
| 4 | 42   | 
| 5 | 42   | 
| 6 | 42   | 
+----+------------+ 
6 rows in set (0.00 sec) 

mysql> delete t1 from table1 t1 join table1 t2 
     on t1.id > t2.id and t1.field_name = t2.field_name; 
Query OK, 5 rows affected (0.00 sec) 

mysql> select * from table1; 
+----+------------+ 
| id | field_name | 
+----+------------+ 
| 1 | 42   | 
+----+------------+ 
1 row in set (0.00 sec) 
+0

感谢您的帮助!你的答案已经得到了满意的答复,所以我只是假设你的答案是最好的解决方案(并不是说别人不好)。请介意解释一下,代码块是以“USE test; SET ...”开头的意思吗?另外,为了确保,大于号的使用将确保所有重复项都被删除,即使有多于一个重复项(比如5)?非常感谢。 – 2010-07-21 21:47:16

+0

也忘了问:你的解决方案的第一块代码不是拼写错误,对吗?你把“删除t1”。这并不意味着它会删除整个桌子或其他东西吗?对不起所有的问题,这只是对我来说有点复杂=) – 2010-07-21 21:52:53

+1

只是想停下来说再次感谢你的这个辉煌的写作。我在两年多之后仍然提到它! – 2012-12-02 04:17:39

0

该查询应该工作。有索引会改变性能,但它实际上取决于表的大小。

至于测试这个,我会复制一部分数据到一个临时表,然后在临时表上运行命令,然后在真正的表上运行它。

在执行任何主要批处理作业之前,请务必备份表格,以便始终可以回滚。

0

我用的方法避免了JOIN条件,应该是显著快:

DELETE FROM table1 WHERE id NOT IN (SELECT MIN(x.id) FROM table1 AS x GROUP BY x.field_name); 

子选择集要保持ID列表。这将允许您为每个field_name保留一个唯一的行。 DELETE语句将删除所有额外的重复行。

另外,是的,field_name字段上的索引将提高查询的性能。

+0

实际上,在MySQL中任何对'GROUP BY'的使用都会调用一个临时表,这会严重影响性能。 – 2010-07-21 19:28:32

+0

@Bill - 在我的一个测试数据库上做了一个快速测试。当然,只有30,000行,但在我使用它的情况下,“GROUP BY”明显优于“JOIN”。 – thetaiko 2010-07-21 19:44:24

+0

嗯,那很好。我希望它也适用于OP的数据库。 – 2010-07-21 19:46:46

相关问题