2009-08-24 3 views
2

У меня есть таблица, где я сохраняю все изменения строк, которые когда-либо возникали. Проблема в том, что в начале приложения возникла ошибка, которая создавала кучу копий каждой строки.SQL Удаление дубликатов по одной строке за один раз

таблица выглядит примерно так:

copies 
|ID |CID |DATA 
| 1 | 1 | DA 
| 2 | 2 | DO 
| 2 | 3 | DO (copy of CID 2) 
| 1 | 4 | DA (copy of CID 1) 
| 2 | 5 | DA 
| 1 | 6 | DA (copy of CID 1) 
| 2 | 7 | DO 

ИДС УНИКАЛЕН в таблицу экземпляров.

Я хочу удалить все дубликаты DATA GROUP BY ID, которые после друг друга сортируются по CID.

Как вы можете видеть в таблице, CID 2 и 3 являются одинаковыми, и они находятся друг за другом. Я хотел бы удалить CID 3. То же самое с CID 4 и CID 6; они не имеют ID 1 между ними и являются копиями CID 1.

После удаления дубликатов, я хотел бы, чтобы таблица выглядит следующим образом:

copies 
|ID |CID |DATA 
| 1 | 1 | DA 
| 2 | 2 | DO 
| 2 | 5 | DA 
| 2 | 7 | DO 

Есть предложения? :)

Я думаю, что мой вопрос был плохо спросил, потому что ответ все, кажется, думает, это лучшее дает этот результат:

ID | DATA | DATA | DATA | DATA | DATA |  DATA |  CID   | 
                |Expected | Quassnoi | 
1809 | 1 | 0 | 1 | 0 | 0 |  NULL | 252227 | 252227 | 
1809 | 1 | 0 | 1 | 1 | 0 |  NULL | 381530 | 381530 | 
1809 | 1 | 0 | 1 | 0 | 0 |  NULL | 438158 | (missing) | 
1809 | 1 | 0 | 1 | 0 | 1535 | 20090113 | 581418 | 581418 | 
1809 | 1 | 1 | 1 | 0 | 1535 | 20090113 | 581421 | 581421 | 

ИДС 252227 И ИДС 438158 являются дубликатами, но потому, что ИДС 381530 идет между ними; Я хочу сохранить это. Это только дубликаты, которые находятся непосредственно друг за другом при заказе с помощью идентификатора CID и ID.

+0

В вашем окончательном наборе результатов '(2, 2)' и '(2, 7)' не имеют 'id Между ними тоже. Означает ли это, что их также следует удалить? Если нет, что их отличает? – Quassnoi

+0

У них нет ID 1 между ними, но у них (2, 5, DA). ID следует игнорировать как «дублирующиеся» данные, его следует использовать для группировки строк. Возможно, это было отвлекать, чтобы говорить о двух идентификаторах. Если вы посмотрите на последнюю таблицу, вы увидите только один идентификатор и ожидаемый результат только от этого идентификатора. –

+0

'(1, 1)' и '(1, 4)' имеют 'DO' между ними. Почему вы хотите, чтобы '(1, 4)' был удален тогда? – Quassnoi

ответ

5
DELETE c.* 
FROM  copies c 
JOIN  (
     SELECT id, data, MIN(copies) AS minc 
     FROM copies 
     GROUP BY 
       id, data 
     ) q 
ON  c.id = q.id 
     AND c.data = q.data 
     AND c.cid <> q.minc 

Update:

DELETE c.* 
FROM (
     SELECT cid 
     FROM (
       SELECT cid, 
         COALESCE(data1 = @data1 AND data2 = @data2, FALSE) AS dup, 
         @data1 := data1, 
         @data2 := data2 
       FROM (
         SELECT @data1 := NULL, 
           @data2 := NULL 
         ) vars, copies ci 
       ORDER BY 
         id, cid 
       ) qi 
     WHERE dup 
     ) q 
JOIN copies c 
ON  c.cid = q.cid 

Это решение empoys MySQL переменные сессии.

Существует чистое ANSI решения, которое использует бы NOT EXISTS, однако, он будет медленным из-за способом MySQL оптимизатора работы (он не будет использовать range метод доступа в коррелированных подзапросах).

Смотрите эту статью в моем блоге подробности производительности довольно близко задачи:

+0

Благодарим за предложение, но это удаление: | 2 | 7 | DO Не все строки, которые должны быть удалены только теми, которые поступают непосредственно после того, как oneanother сгруппированы по ID. –

+0

Чтобы сделать это правильно, вы также должны иметь критерий, что нет строки с другим значением данных и тем же значением идентификатора и с идентификатором CID между текущей строкой и ранней строкой с тем же значением данных. Это в лучшем случае. –

+0

Привет, Quassnoi, я пробовал это, и он удаляет почти все, что у меня есть в таблице. Как выглядит запрос, если у меня есть более одного столбца DATA? –

1

Вы можете использовать count в подзапрос для этого:

delete from copies 
where 
    (select count(*) from copies s where s.id = copies.id 
            and s.data = copies.data 
            and s.cid > copies.cid) > 0 
+0

Я не думаю, что это работает правильно. Он не учитывает, имеет ли значение DATA между последовательными строками, упорядоченными по ID и CID. –

+0

Это на самом деле. Я просто неверно обозначил 'data' как' name'. Виноват. Исправлена! – Eric

1
// EDITED for @Jonathan Leffler comment 
//$sql = "SELECT ID,CID,DATA FROM copies ORDER BY CID, ID"; 
$sql = "SELECT ID,CID,DATA FROM copies ORDER BY ID, CID"; 
$result = mysql_query($sql, $link); 
$data = ""; 
$id = ""; 
while ($row = mysql_fetch_row($result)){ 
     if (($row[0]!=$id) && ($row[2]!=$data) && ($id!="")){ 
      $sql2 = "DELETE FROM copies WHERE CID=".$row[1]; 
      $res = mysql_query($sql2, $link); 
     } 
     $id=$row[0]; 
     $data=$row[2]; 
} 
+0

Действительно? У вас есть три простых подхода к SQL, и вы предлагаете использовать интерпретируемый язык, начинающий отдельные транзакции «удалить»? Я искренне надеюсь, что это не то, как вы решаете все проблемы с базой данных. Базы данных о мышлении в наборах! – Eric

+0

1º для консультации - это процесс сразу. 2º есть PHP-тег, поэтому можно включить решение с этим языком. что-то другое? –

+0

@ Эрик задача должна быть выполнена один раз для целей очистки, поэтому я думаю, что скорость здесь не является основной проблемой. Это делает правильный ответ, если не лучшим ответом. – quillbreaker

0

удалить из копий с которых c.cid в (выбрать макс (CID) как max_cid, count (*) как num из копий, где num> 1 group по id, data)

+1

Это удалит только последний дубликат, а не все дубликаты. – Quassnoi

+0

Да, вы правы, но вы можете запускать запрос еще раз, пока все дубликаты удалены. Ваше решение более элегантное (я уже проголосовал за ваш ответ)! –

Смежные вопросы