Her er en løsning. Jeg testede dette på 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;
Jeg inkluderer c2.notes som en GROUP BY-nøgle, fordi du måske har mere end én række med ikke-null-noter pr. værdier af driver_id,car_id.
Resultat ved hjælp af dine eksempeldata:
+------+-----------+--------+-------+
| id | driver_id | car_id | notes |
+------+-----------+--------+-------+
| 2 | 1 | 1 | NULL |
| 4 | 2 | 1 | NULL |
| 8 | 3 | 2 | hi |
| 9 | 5 | 3 | NULL |
+------+-----------+--------+-------+
Angående sletning. I dine eksempeldata er det altid den højeste id-værdi pr. driver_id &car_id, som du vil beholde. Hvis du kan stole på det, kan du foretage en multi-table sletning, der sletter alle rækker, for hvilke der findes en række med en højere id-værdi og samme 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;
Dette springer naturligvis alle tilfælde over, hvor der kun eksisterer én række med et givet par af driver_id &car_id værdier, fordi betingelserne for den indre join kræver to rækker med forskellige id værdier.
Men hvis du ikke kan stole på, at det seneste id pr. gruppe er det, du vil beholde, er løsningen mere kompleks. Det er sandsynligvis mere komplekst, end det er værd at løse i én sætning, så gør det i to sætninger.
Jeg testede også dette efter at have tilføjet et par rækker mere til test:
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 |
+----+--------+-----------+-------+
Slet først rækker med nul-noter, hvor der findes en række med ikke-nul-noter.
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 |
+----+--------+-----------+-------+
For det andet skal du slette alle undtagen rækken med højeste id fra hver gruppe af dubletter.
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 |
+----+--------+-----------+-------+