Bietet Rezepte, Lösungen und ausführliche Beispiele rund um gesundes SQL und zufriedene Datenbanken.
Kaum hat man sich vertippt, muss man schon einen UPDATE machen. Kein Problem, wozu hat man denn eine Datenbank, die einen darin unterstützt, Daten korrekt zu verwalten. Der notwendige Befehl hierfür heisst UPDATE und wird den meisten bekannt sein. Spannend wird das Ganze, wenn der UPDATE auf einem komplexen Sub-Select basiert.
Bei Datenimporten aus Fremdsystemen muss häufig auf Dubletten geprüft werden. Wir bauen also eine kleine Tabelle und validieren die ID auf doppelte. Um keine unnötige Zeit zu verlieren, kommt in die selbe Tabelle noch eine Spalte "dublette", in der die Trefferanzahl der Dublettenprüfung abgelegt werden soll.
* IBM DB2 9.1
* MySQL 5.5.9 (InnoDB)
* MySQL 5.1.30 (MyISAM)
* ORACLE 10g 10.2 EE
CREATE TABLE check_dubletten (id INT NOT NULL,dublette INT);INSERT INTO check_dubletten ( id ) VALUES( 1 ),( 2 ),( 2 ),( 3 ),( 3 ),( 3 );Query OK, 6 rows affected (0.02 sec)Records: 6 Duplicates: 0 Warnings: 0UPDATE check_dubletten cd1SET cd1.dublette = ( SELECT COUNT(*) AS anzahlFROM check_dubletten cd2WHERE cd2.id = cd1.idGROUP BY cd2.id );ERROR 1093 (HY000): You can't specify target table 'cd1' for update in FROM clauseToll, während dieser UPDATE bei DB2 und ORACLE klaglos funktioniert, kann bei MySQL ein selbstreverenzierender Update basierend auf einer Unterabfrage nicht ausgeführt werden. Dies ist MySQL sehr wohl bekannt und demnach findet sich dort auch ein Workaround.
Damit ich nicht gleich einen "Dr." bekomme, hier schnell der Quellenhinweis dazu: http://www.mysqlfaqs.net/mysql-faqs/Errors/1093-You-can-not-specify-target-table-comments-for-update-in-FROM-clause
UPDATE check_dubletten cd1SET cd1.dublette = ( SELECT anzahlFROM ( SELECT cd2.id, COUNT(*) AS anzahlFROM check_dubletten cd2GROUP BY cd2.id ) cd3WHERE cd3.id = cd1.id );Query OK, 6 rows affected (0.05 sec)Rows matched: 6 Changed: 6 Warnings: 0SELECT id, dublette FROM check_dublettenWHERE dublette > 1;+----+----------+| id | dublette |+----+----------+| 2 | 2 || 2 | 2 || 3 | 3 || 3 | 3 || 3 | 3 |+----+----------+5 rows in set (0.00 sec)Warum geht das nun? Der Sub-Query cd3 materialisiert während der Abarbeitung in MySQL intern als temporäre Tabelle. Wie läßt sich so etwas beweisen? Einfach in dem man einen EXPLAIN ausführt. Da EXPLAIN leider nur mit SELECT funktioniert, muss man eben den UPDATE so ähnlich wie möglich in einen SELECT umschreiben.
EXPLAINSELECT cd1.dubletteFROM check_dubletten cd1JOIN ( SELECT cd2.id, anzahlFROM ( SELECT id, COUNT(*) AS anzahlFROM check_dublettenGROUP BY id ) cd2) cd3ON cd3.id = cd1.id;+----+-------------+-----------------+------+---------------+------+---------+------+------+---------------------------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+-----------------+------+---------------+------+---------+------+------+---------------------------------+| 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 3 | || 1 | PRIMARY | cd1 | ALL | NULL | NULL | NULL | NULL | 6 | Using where; Using join buffer || 2 | DERIVED | <derived3> | ALL | NULL | NULL | NULL | NULL | 3 | || 3 | DERIVED | check_dubletten | ALL | NULL | NULL | NULL | NULL | 6 | Using temporary; Using filesort |+----+-------------+-----------------+------+---------------+------+---------+------+------+---------------------------------+4 rows in set (0.02 sec)Und siehe da, bei der Tabelle "check_dubletten" erscheint in der Spalte Extra ein "Using temporary; Using filesort".
Der umgeschriebene UPDATE funktioniert jetzt zwar, aber performant kann er nicht sein, da er quasi absichtlich kompliziert gehalten ist, um einen "Bug" in MySQL zu umgehen.