Как выбрать и / или удалить все кроме одной строки каждого набора дубликатов в таблице? - PullRequest
7 голосов
/ 24 июня 2011

Допустим, у меня есть таблица MySQL с четырьмя столбцами:

ID DRIVER_ID CAR_ID ПРИМЕЧАНИЯ (NULL для большинства строк)

У меня есть несколько повторяющихся строк, в которых DRIVER_ID и CAR_ID совпадают. Для каждой пары DRIVER_ID и CAR_ID я хочу одну строку. Если одна из строк в наборе имеет НЕ-НУЛЬНЫЕ НОТЫ, я хочу эту, но в остальном это не имеет значения.

так что если у меня есть:

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

Я хочу сохранить следующие идентификаторы: 9, 8, а затем по одному из [3,4] и [1,2].

Это огромный стол, и неуклюжие методы, которые я пробовал, безумно медленны, до такой степени, что, я уверен, я все делаю неправильно. Как я могу эффективно а) выбрать список идентификаторов для удаления? б) удалить их в одном запросе?

(И да, я знаю сделку с составными ключами. Здесь это не проблема.)

РЕДАКТИРОВАТЬ: Извините, забыл указать, что это был MySQL.

Некоторые вещи, которые я пробовал до сих пор:

select ID, COUNT(DRIVER_ID) rowcount from CARS_DRIVERS group by CAR_ID,DRIVER_ID HAVING rowcount > 1;

даст мне один идентификатор на группу. Однако не обязательно оставлять строку с NOTES, если она есть. Это также даст мне только один идентификатор на дубликат группы. В некоторых случаях существует более 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);

Это намного медленнее и все еще не решает проблему NOTES. Он имеет преимущество в получении самой старой строки для каждой группы, которая, если я не могу легко выделить в поле NOTES, может быть прокси для этого. Если строка в наборе имеет NOTES, я считаю, что она всегда самая старая (с наименьшим ID), но я не уверена.

Некоторый дополнительный контекст: DRIVER_ID и CAR_ID не являются реальными именами столбцов, и в таблице есть другие столбцы. Я пытался разобрать информацию, чтобы понять причину проблемы, но из комментария W4M я вижу, что это выглядит как домашнее задание. Реальная сделка заключается в том, что я смотрю на очень неоптимизированную базу данных (обычно это не входит в мои обязанности), и при попытке избавиться от этих ошибок перед добавлением ключа операция выполняется вечно. Как в часах. Стол большой, но, конечно, это не оправдывает. Я пытаюсь использовать свой ограниченный опыт в SQL и найти способ сделать это. Не важно, хороша ли она, я могу сидеть в командной строке и перебирать кучу запросов, если это необходимо. Но я заметил, что ВЫБОР идентификаторов, которые являются кандидатами на удаление, занимает всего несколько секунд, и хотя таблица огромна, общее количество удаляемых строк составляет менее 10 КБ, поэтому должен быть способ сделать это без какого-либо сценария, который занимает целые выходные до финиша.

Ответы [ 2 ]

7 голосов
/ 25 июня 2011

Вот одно из решений. Я проверял это на 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;

Я включаю 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  |
+------+-----------+--------+-------+

По поводу удаления. В данных вашего примера это всегда самое высокое значение id для driver_id & car_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;

Естественно, это пропускает любые случаи, когда существует только одна строка с данной парой значений driver_id и car_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   |
+----+--------+-----------+-------+

Сначала удалите строки с нулевыми примечаниями, где существует строка с ненулевыми примечаниями.

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   |
+----+--------+-----------+-------+

Во-вторых, удалите все, кроме строки с самым высоким идентификатором, из каждой группы дубликатов.

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   |
+----+--------+-----------+-------+
0 голосов
/ 24 июня 2011

Так как это очень похоже на домашнюю работу, я не буду давать ответ. Вы хотите выполнить левое соединение и / или выполнить отдельный запрос.

http://dev.mysql.com/doc/refman/5.0/en/distinct-optimization.html

РЕДАКТИРОВАТЬ Полностью не проверено:

select distinct(t1.car_id) from cars_drivers t1 where t1.car_id = t1.driver_id and notes != null;

обрабатывает вызов там, где вы хотите заметки. Если этот список равен нулю, вы хотите запустить это:

select distinct(t1.car_id) from cars_drivers t1 where t1.car_id = t1.driver_id;
...