Oto jedno rozwiązanie. Przetestowałem to na 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;
Dołączam c2.notes jako klucz GROUP BY, ponieważ możesz mieć więcej niż jeden wiersz z notatkami innymi niż null na wartości driver_id, car_id.
Wynik na podstawie przykładowych danych:
+------+-----------+--------+-------+
| id | driver_id | car_id | notes |
+------+-----------+--------+-------+
| 2 | 1 | 1 | NULL |
| 4 | 2 | 1 | NULL |
| 8 | 3 | 2 | hi |
| 9 | 5 | 3 | NULL |
+------+-----------+--------+-------+
Odnośnie usuwania. W przykładowych danych zawsze jest to najwyższa wartość identyfikatora na identyfikator kierowcy i identyfikator samochodu, którą chcesz zachować. Jeśli możesz na tym polegać, możesz wykonać usuwanie wielu tabel, które usuwa wszystkie wiersze, dla których istnieje wiersz o wyższym identyfikatorze i tym samym identyfikatorze kierowcy i identyfikatorze samochodu:
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;
To naturalnie pomija wszystkie przypadki, w których istnieje tylko jeden wiersz z daną parą wartości driver_id i car_id, ponieważ warunki sprzężenia wewnętrznego wymagają dwóch wierszy z różnymi wartościami identyfikatora.
Ale jeśli nie możesz polegać na najnowszym identyfikatorze na grupę, który chcesz zachować, rozwiązanie jest bardziej złożone. Prawdopodobnie jest to bardziej skomplikowane niż to, które warto rozwiązać w jednym zdaniu, więc zrób to w dwóch zdaniach.
To też przetestowałem, po dodaniu kilku kolejnych wierszy do testowania:
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 |
+----+--------+-----------+-------+
Najpierw usuń wiersze z notatkami o wartości null, w których istnieje wiersz z notatkami innymi niż null.
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 |
+----+--------+-----------+-------+
Po drugie, usuń wszystkie wiersze oprócz najwyższego identyfikatora z każdej grupy duplikatów.
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 |
+----+--------+-----------+-------+