如何选择和/或删除表中每组重复项的所有行?

NCh*_*ase 7 mysql duplicates

假设我有一个包含四列的MySQL表:

ID DRIVER_ID CAR_ID NOTES(大多数行为NULL)

我有一堆重复的行,其中DRIVER_ID和CAR_ID是相同的.对于每对DRIVER_ID和CAR_ID,我想要一行.如果集合中的一行具有非NULL NOTES,我想要那个,但是否则无关紧要.

所以,如果我有:

ID  |  DRIVER_ID  |  CAR_ID  |  NOTES
1      1             1          NULL
2      1             1          NULL
3      1             2          NULL
4      1             2          NULL
5      2             3          NULL
6      2             3          NULL
7      2             3          NULL
8      2             3          hi
9      3             5          NULL
Run Code Online (Sandbox Code Playgroud)

我想保留以下ID:9,8,然后是[3,4]和[1,2]各一个.

这是一张巨大的桌子,而且我尝试过的笨重的方法非常缓慢,我确信自己一切都错了.我怎样才能有效地a)选择要删除的ID列表?b)在同一个查询中删除它们?

(是的,我知道复合键的处理.这不是问题.)

编辑:对不起,忘了指定这是MySQL.

到目前为止我尝试过的一些东西:

select ID, COUNT(DRIVER_ID) rowcount from CARS_DRIVERS group by CAR_ID,DRIVER_ID HAVING rowcount > 1;
Run Code Online (Sandbox Code Playgroud)

我会给每个组一个ID.但是,如果有一行,它不一定会留下带有NOTES的行.每个重复组也只能得到一个ID.在某些情况下,有20多个重复组合,所以我需要反复迭代,将每个组缩小到一行.

select distinct t1.ID from CARS_DRIVERS t1 where exists (select * from CARS_DRIVERS t2 where t2.CAR_ID = t1.CAR_ID and t2.DRIVER_ID = t1.DRIVER_ID and t2.id > t1.id);
Run Code Online (Sandbox Code Playgroud)

这要慢得多,但仍然没有真正解决NOTES问题.它确实具有为每个组获取最旧行的优势,如果我不能轻易地在NOTES字段上隔离,则可以代表它.如果一个集合中的一行有NOTES,我相信它总是最老的一个(ID最低的那个),但我不确定.

一些额外的上下文:DRIVER_ID和CAR_ID不是真正的列名,表中还有其他列.我试图提取信息以解决问题的根源,但我从W4M的评论中看到,这使它看起来像一个家庭作业.真正的问题是,我正在寻找一个非常优化的数据库(通常不是我的权限),并且在添加密钥之前试图摆脱这些欺骗时,操作将永远持续下去.如,小时.该表很大但肯定不合理.我正试图利用我有限的SQL专业知识,找到一种方法来完成这项工作.如果它很漂亮无关紧要,我可以坐在命令行,并在必要时强行进行一系列查询.但是我注意到,SELECTing ID作为删除的候选者只需要几秒钟,虽然表很大,但要删除的行总数少于10k所以必须有一种方法可以在没有一些脚本的情况下实现这一点.整个周末结束.

Bil*_*win 7

这是一个解决方案.我在MySQL 5.5.8上测试了这个.

SELECT MAX(COALESCE(c2.id, c1.id)) AS id,
 c1.driver_id, c1.car_id,
 c2.notes AS notes
FROM cars_drivers AS c1
LEFT OUTER JOIN cars_drivers AS c2
 ON (c1.driver_id,c1.car_id) = (c2.driver_id,c2.car_id) AND c2.notes IS NOT NULL
GROUP BY c1.driver_id, c1.car_id, c2.notes;
Run Code Online (Sandbox Code Playgroud)

我将c2.notes包含为GROUP BY键,因为每个driver_id,car_id值可能有多行非空注释.

结果使用您的示例数据:

+------+-----------+--------+-------+
| id   | driver_id | car_id | notes |
+------+-----------+--------+-------+
|    2 |         1 |      1 | NULL  |
|    4 |         2 |      1 | NULL  |
|    8 |         3 |      2 | hi    |
|    9 |         5 |      3 | NULL  |
+------+-----------+--------+-------+
Run Code Online (Sandbox Code Playgroud)

关于删除.在您的示例数据中,它始终是您要保留的每个driver_id和car_id的最高ID值.如果您可以依赖它,则可以执行多表删除,删除具有较高id值且存在相同driver_id&car_id的行的所有行:

DELETE c1 FROM cars_drivers AS c1 INNER JOIN cars_drivers AS c2
 ON (c1.driver_id,c1.car_id) = (c2.driver_id,c2.car_id) AND c1.id < c2.id;
Run Code Online (Sandbox Code Playgroud)

这自然会跳过任何只存在一行且具有给定的driver_id和car_id值对的情况,因为内连接的条件需要两行具有不同的id值.

但是,如果您不能依赖每个组的最新ID是您想要保留的那个,那么解决方案就更复杂了.它可能比在一个语句中解决它更复杂,所以在两个语句中这样做.

在添加了几行进行测试之后,我也对此进行了测试:

INSERT INTO cars_drivers VALUES (10,2,3,NULL), (11,2,3,'bye');

+----+--------+-----------+-------+
| id | car_id | driver_id | notes |
+----+--------+-----------+-------+
|  1 |      1 |         1 | NULL  |
|  2 |      1 |         1 | NULL  |
|  3 |      1 |         2 | NULL  |
|  4 |      1 |         2 | NULL  |
|  5 |      2 |         3 | NULL  |
|  6 |      2 |         3 | NULL  |
|  7 |      2 |         3 | NULL  |
|  8 |      2 |         3 | hi    |
|  9 |      3 |         5 | NULL  |
| 10 |      2 |         3 | NULL  |
| 11 |      2 |         3 | bye   |
+----+--------+-----------+-------+
Run Code Online (Sandbox Code Playgroud)

首先删除具有空注释的行,其中存在具有非空注释的行.

DELETE c1 FROM cars_drivers AS c1 INNER JOIN cars_drivers AS c2
 ON (c1.driver_id,c1.car_id) = (c2.driver_id,c2.car_id)
WHERE c1.notes IS NULL AND c2.notes IS NOT NULL;

+----+--------+-----------+-------+
| id | car_id | driver_id | notes |
+----+--------+-----------+-------+
|  1 |      1 |         1 | NULL  |
|  2 |      1 |         1 | NULL  |
|  3 |      1 |         2 | NULL  |
|  4 |      1 |         2 | NULL  |
|  8 |      2 |         3 | hi    |
|  9 |      3 |         5 | NULL  |
| 11 |      2 |         3 | bye   |
+----+--------+-----------+-------+
Run Code Online (Sandbox Code Playgroud)

其次,从每组重复项中删除除最高id行之外的所有行.

DELETE c1 FROM cars_drivers AS c1 INNER JOIN cars_drivers AS c2
 ON (c1.driver_id,c1.car_id) = (c2.driver_id,c2.car_id) AND c1.id < c2.id;

+----+--------+-----------+-------+
| id | car_id | driver_id | notes |
+----+--------+-----------+-------+
|  2 |      1 |         1 | NULL  |
|  4 |      1 |         2 | NULL  |
|  9 |      3 |         5 | NULL  |
| 11 |      2 |         3 | bye   |
+----+--------+-----------+-------+
Run Code Online (Sandbox Code Playgroud)