实际上,具有当前局限性的问题是一个相当棘手的挑战。我整个晚上都在考虑解决方案(理解解决方案永远不会有用)。我不会在野外使用该解决方案,我只是试图找出是否可以仅使用 MySQL。
我的表述中的问题:是否可以编写一系列 DELETE 语句来从没有唯一约束的两列表中删除重复行?
问题:
- 行没有标识键或主键,因此应该想出一种方法来引用应该保留的单行
- 我们需要以某种方式对行进行分组,即先应用顺序然后应用条件,但支持
ORDER BY 的DELETE 形式只能有WHERE 子句,不支持HAVING。即满足条件后应用顺序。
- 如果值按集群主键排列,我们就不需要对行进行排序,但我们没有。
假设我们有一张桌子:
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 > 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。
希望,这可能对某人有用 =)