假设我有一个包含四列的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所以必须有一种方法可以在没有一些脚本的情况下实现这一点.整个周末结束.
这是一个解决方案.我在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)