У меня есть таблица со многими повторяющимися записями:
shop ID tax_id 1 10 1 10 1 11 2 10 2 12 2 10 2 10
Я хочу удалить все повторяющиеся записи, не создавая временную таблицу. После запроса на обновление таблица должна выглядеть так:
shop ID tax_id 1 10 1 11 2 10 2 12
Вот решение на месте (но не однострочное)
Узнать max id:
select max(id) as maxid from shop;
Помните это значение. Предположим, что он равен 1000;
Повторно вставьте уникальные значения со смещением:
insert into shop (id, tax_id) select distinct id + 1000, tax_id from shop;
Отбросить старые значения:
delete from shop where id <= 1000;
Восстановить нормальные идентификаторы:
update shop set id = id - 1000;
PROFIT!
Рабочее решение.
//Sql query to find duplicates SELECT id, tax_id, count(*) - 1 AS cnt FROM shop GROUP BY id HAVING cnt > 1 --- res +------+--------+-----+ | id | tax_id | cnt | +------+--------+-----+ | 1 | 10 | 2 | | 2 | 10 | 3 | +------+--------+-----+ //Iterate through results with your language of choice DELETE FROM shop WHERE id=<res id> AND tax_id=<res tax_id> LIMIT <cnt - 1> ---res (iterated) +------+--------+ | id | tax_id | +------+--------+ | 1 | 10 | | 1 | 11 | | 2 | 12 | | 2 | 10 | +------+--------+
Для двух запросов потребуется небольшая часть php для выполнения удалений
$res = mysql_query("SELECT id, tax_id, count(*) - 1 AS cnt FROM shop GROUP BY id HAVING cnt > 1") while($row = mysql_fetch_assoc($res)){ mysql_query("DELETE FROM shop WHERE id=".$row['id']." AND tax_id=". $row['tax_id']." LIMIT ".$row['cnt'] -1 . "); }
Изменить: в последнее время это повторилось, поскольку это стоит того, вот альтернативное решение с использованием временного столбца, устраняющее необходимость в языке сценариев.
ALTER TABLE shop ADD COLUMN place INT; SET @i = 1 UPDATE shop SET place = @i:= @i + 1; DELETE FROM shop WHERE place NOT IN (SELECT place FROM items GROUP BY id, tax_id); ALTER TABLE shop DROP COLUMN place;
Во-первых, вы можете предотвратить это, создав уникальный индекс для этих двух полей для дальнейшего использования.
Что касается решения, создайте новую таблицу shopnew
с той же структурой в mysql или просто удалите каждую запись из таблицы при создании списка записей (убедитесь, что у вас есть резервная копия!):
//Get every record from mysql $sSQL = "Select ID, tax_id from shop"; $oRes = mysql_query($sSQL); $aRecordList = array(); while($aRow = mysql_fetch_assoc($oRes)){ //If record is a duplicate, it will be 'overwritten' $aRecordList[$aRow['id'].".".$aRow['tax_id']] =1; } //You could delete every record from shop here, if you dont want an additional table //recordList now only contains unique records foreach($aRecordList as $sRecord=>$bSet){ $aExpRecord = explode(".",$sRecord); mysql_query("INSERT INTO shopnew set id=".$aExpRecord[0].", tax_id = ".$aExpRecord[1] }
Может быть, это может помочь:
$query="SELECT * FROM shop ORDER BY id"; $rez=$dbh->query($query); $multi=$rez->fetchAll(PDO::FETCH_ASSOC); foreach ($multi as $key=>$row){ $rest=array_slice($multi,$key+1); foreach ($rest as $rest){ if(($row['id']==$rest['id']) && ($row['tax_id']==$rest['tax_id'])){ $dbh->query("DELETE FROM shop WHERE id={$rest['id']} and tax_id= {$rest['tax_id']}"); } }
}
Первый foreach
выполняет итерацию каждой строки, а второй делает сравнение. Я использую PDO, но, конечно, вы можете сделать это процедурным способом.
На самом деле вопрос с его текущими ограничениями – довольно сложная задача. Я думал о решении весь вечер (понимая, что решение никогда не будет полезно). Я бы не использовал решение в дикой природе, я просто попытался выяснить, можно ли это делать только с использованием MySQL.
Вопрос в моей формулировке: можно ли написать серию операторов DELETE, которые будут удалять повторяющиеся строки из таблицы с двумя столбцами без особых ограничений?
Проблемы:
DELETE
которая поддерживает ORDER BY
может иметь только WHERE
и не поддерживает HAVING
. Это порядок применяется после выполнения условия. Предположим, что у нас есть таблица:
CREATE TABLE `tablename` ( `a_id` int(10) unsigned NOT NULL, `b_id` int(10) unsigned NOT NULL, KEY `Index_1` (`a_id`,`b_id`) ) ENGINE=InnoDB COLLATE utf8_bin;
Я добавил ключ (не UNIQUE или PRIMARY), чтобы ускорить поиск и надеяться использовать его в группах.
Вы можете подать таблицу с некоторыми значениями:
INSERT INTO tablename (a_id, b_id) VALUES (2, 3), (1, 1), (2, 2), (1,4); INSERT INTO tablename (a_id, b_id) VALUES (2, 3), (1, 1), (2, 2), (1,4); INSERT INTO tablename (a_id, b_id) VALUES (2, 3), (1, 1), (2, 2), (1,4);
В качестве побочного эффекта ключ стал индексом покрытия, и когда мы делаем SELECTs из таблицы, отображаемые значения сортируются, но когда мы делаем удаления, значения считываются в том порядке, в который мы их ввели.
Теперь давайте рассмотрим следующий запрос:
SELECT @c, @a_id as a, @b_id as b, a_id, b_id FROM tablename, (SELECT @a_id:=0, @b_id:=0, @c:=0) as init WHERE (@c:=IF(LEAST(@a_id=(@a_id:=a_id), @b_id=(@b_id:=b_id)), @c+1, 1)) >= 1 ;
И его результат:
@c, a, b, a_id, b_id 1, 1, 1, 1, 1 2, 1, 1, 1, 1 3, 1, 1, 1, 1 1, 1, 4, 1, 4 2, 1, 4, 1, 4 3, 1, 4, 1, 4 1, 2, 2, 2, 2 2, 2, 2, 2, 2 3, 2, 2, 2, 2 1, 2, 3, 2, 3 2, 2, 3, 2, 3 3, 2, 3, 2, 3
Результаты автоматически сортируются с использованием Index_1
, а дублированные пары (a_id, b_id)
перечисляются в столбце @c
. Теперь наша задача – удалить все строки, где @c > 1
. Единственная проблема, с которой мы сталкиваемся, – заставить MySQL использовать Index_1
при удалении, что довольно сложно, не применяя дополнительные условия. Но мы можем сделать это, используя проверку равенства или несколько проверок равенства на a_id
:
DELETE FROM t USING tablename t FORCE INDEX (Index_1) JOIN (SELECT @a_id:=0, @b_id:=0, @c:=0) as init WHERE a_id IN (1) AND (@c:=IF(LEAST(@a_id=(@a_id:=a_id), @b_id=(@b_id:=b_id)), @c+1, 1)) > 1; DELETE FROM t USING tablename t FORCE INDEX (Index_1) JOIN (SELECT @a_id:=0, @b_id:=0, @c:=0) as init WHERE a_id IN (2) AND (@c:=IF(LEAST(@a_id=(@a_id:=a_id), @b_id=(@b_id:=b_id)), @c+1, 1)) > 1; SELECT * FROM tablename t; a_id, b_id 1, 1 1, 4 2, 2 2, 3
Я не могу поместить все возможные a_id
в IN()
потому что MySQL поймет, что индекс бесполезен в этом случае, и запрос не удалит все дубликаты (только смежные), но, скажем, 10 разных a_id
я могу удалить дубликаты в двух операторах DELETE , каждый IN будет иметь 5 явных идентификаторов.
Надеюсь, это может быть полезно кому-то =)