Форум: Форум PHPФорум ApacheФорум Регулярные ВыраженияФорум MySQLHTML+CSS+JavaScriptФорум FlashРазное
Новые темы: 0000000
Программирование. Ступени успешной карьеры. Авторы: Кузнецов М.В., Симдянов И.В. C++. Мастер-класс в задачах и примерах. Авторы: Кузнецов М.В., Симдянов И.В. MySQL на примерах. Авторы: Кузнецов М.В., Симдянов И.В. PHP Puzzles. Авторы: Кузнецов М.В., Симдянов И.В. Объектно-ориентированное программирование на PHP. Авторы: Кузнецов М.В., Симдянов И.В.
ВСЕ НАШИ КНИГИ
Консультационный центр SoftTime

Форум MySQL

Выбрать другой форум

 

Здравствуйте, Посетитель!

вид форума:
Линейный форум Структурный форум

тема: Размышления на тему: удаление дубликатов из таблицы одним запросом
 
 автор: cheops   (01.03.2011 в 16:45)   письмо автору
 
 

В нормальных базах данных, где вложенные запросы появлись не пару лет назад, а гораздо раньше решить проблему можно на раз, однако в MySQL эта проблема не решается из-за того, что в операторах UPDATE и DELETE по сути невозможно использовать коррелированные запросы.
Пусть имеется таблица
CREATE TABLE cnt (
  id int(11) NOT NULL auto_increment,
  `name` varchar(55) NOT NULL,
  phone varchar(20) NOT NULL,
  PRIMARY KEY  (id),
  KEY namephone (`name`,phone)
) ENGINE=MyISAM  DEFAULT CHARSET=cp1251;

INSERT INTO cnt VALUES(1, 'Александр', '777777');
INSERT INTO cnt VALUES(2, 'Виктор', '555444');
INSERT INTO cnt VALUES(3, 'Евгений', '228833');
INSERT INTO cnt VALUES(4, 'Виктор', '454454');
INSERT INTO cnt VALUES(5, 'Алексей', '223885');
INSERT INTO cnt VALUES(6, 'Михаил', '664488');
INSERT INTO cnt VALUES(7, 'Виктор', '990088');
INSERT INTO cnt VALUES(8, 'Евгений', '228833');

В которой строки с позициями 3 и 8 являются дублирующими, если в каком-нибудь Oracle удалить дублирующе записи можно было бы при помощи запроса вида (ну или не совсем таким, но выкрутиться можно)
DELETE FROM cnt AS fst
WHERE fst.id < (SELECT MAX(snd.id) FROM cnt AS snd
            WHERE fst.name = snd.name AND
                  fst.phone = snd.phone)

то MySQL вернет ошибку
#1093 - You can't specify target table 'cnt' for update in FROM clause

Причем "обмануть" движок не удастся и при помощи SELECT, где вложенные запросы допускаются. Т.е. запрос следующего вида тоже не сработает.
DELETE FROM cnt
WHERE id IN (SELECT id FROM cnt AS fst
WHERE fst.id < (SELECT MAX(snd.id) FROM cnt AS snd
            WHERE fst.name = snd.name AND
                  fst.phone = snd.phone))

Остается только получать список при помощи запроса
SELECT id FROM cnt AS fst
WHERE fst.id < (SELECT MAX(snd.id) FROM cnt AS snd
            WHERE fst.name = snd.name AND
                  fst.phone = snd.phone)

И удалять записи либо при помощи курсора, либо при помощи внешнего кода (например, на PHP).

  Ответить  
 
 автор: Trianon   (01.03.2011 в 19:17)   письмо автору
 
   для: cheops   (01.03.2011 в 16:45)
 

Как стал бы решать такую задачу я.

Определил бы конфликтующие идентификаторы и актуальный первичный ключ
SELECT name, phone, MAX( id ) AS maxid
  FROM `cnt` c1
  GROUP BY name, phone
  HAVING COUNT( * ) >1
as lim


а затем бы построил запрос DELETE по сложному табличному выражению
DELETE cnt 
  FROM cnt 
   JOIN  lim
      ON (cnt.name, cnt.phone) = (lim.name,lim2.phone) AND c2.id < maxid


Подставив одно в другое, выполнил бы

DELETE c2 
  FROM cnt c2 
   JOIN 
   (SELECT name,phone, MAX(id) AS maxid 
     FROM `cnt` c1
     GROUP BY name, phone
     HAVING  COUNT(*) > 1
    ) AS lim
      ON (c2.name, c2.phone) = (lim.name,lim.phone) AND c2.id < maxid


и получил

Удалено строк: 1 (запрос занял 0.0158 сек.)



Конечно, я не стал бы так делать.

Если пара name,phone по модели уникальна, я бы просто создал на ней уикальный индекс, и такая ситуация просто не возникла бы.

  Ответить  
 
 автор: cheops   (01.03.2011 в 19:29)   письмо автору
 
   для: Trianon   (01.03.2011 в 19:17)
 

Здорово, будет теперь тема, на которую можно ссылаться.

PS Все ж такая проблема возникает, например, в приведенном выше дампе - это старые телефонные базы данных и вообще давно существующие базы данных (которые изначально возникли в таких СУБД, в которых возможно и понятия уникального столбца не существовало).

  Ответить  
 
 автор: Trianon   (01.03.2011 в 22:12)   письмо автору
 
   для: cheops   (01.03.2011 в 19:29)
 

Я и не сомневался, собственно, что проблема у Вас не на пустом месте возникла.
Просто для остальных дал понять, что если СУБД и модель позволяет, то безобразиев, когда вынужден искать и удалять дубликаты, лучше стараться избечь заранее.

  Ответить  
 
 автор: Trianon   (01.03.2011 в 22:50)   письмо автору
 
   для: cheops   (01.03.2011 в 19:29)
 

Кстати, надо полагать, база немаленькая.
Перед тем, как выполнять эти запросы, всё ж стоило бы создать составной индекс (не уникальный, обычный, конечно). А то все эти ON и GROUP BY над полями (name,phone) боком выйдут.

  Ответить  
 
 автор: cheops   (01.03.2011 в 22:57)   письмо автору
 
   для: Trianon   (01.03.2011 в 22:50)
 

На самом деле такая задача действительно стояла, но достаточно давно (у нас уже и call-центра то нет, для которого она решалась), к ней я вернулся в связи с тем, что решил почитать древние книги по SQL и порадоваться как мы сейчас хорошо живем с современными технологиями :))). Вместо этого натыкаюсь на элегантное удаление дублирующих записей, которое мне никогда в голову не приходило, и которое, ко всему прочему, не работает в MySQL. От дасады собственно и родилась эта тему. Вопрос-то все-равно регулярно возникает и уже не первый год, пусть будет тема, на которую можно будет сослаться при случае, тем более тут и мораль и решение в одном месте :))).

  Ответить  
 
 автор: Zilog   (03.03.2011 в 00:33)   письмо автору
 
   для: cheops   (01.03.2011 в 22:57)
 

подобные темы надо выделять красным и складировать на видное место.
спасибо.

  Ответить  
 
 автор: nistelroi   (26.04.2014 в 22:16)   письмо автору
 
   для: cheops   (01.03.2011 в 16:45)
 

Кстати, всё-таки движок можно обмануть заставив для внутреннего подзапроса использовать временную таблицу.
Для этого предпоследний запрос запишем в таком виде

DELETE FROM cnt 
WHERE id IN (
              SELECT id FROM (SELECT id FROM cnt AS fst WHERE fst.id < 
                (
                  SELECT MAX(snd.id) FROM cnt AS snd WHERE fst.name = snd.name AND fst.phone = snd.phone
                )) as x
             )


Ессно не стоит такие фокусы делать на сильно больших объемах данных

  Ответить  
Rambler's Top100
вверх

Rambler's Top100 Яндекс.Метрика Яндекс цитирования