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: 0
UPDATE check_dubletten cd1
SET cd1.dublette = ( SELECT COUNT(*) AS anzahl
FROM check_dubletten cd2
WHERE cd2.id = cd1.id
GROUP BY cd2.id );
ERROR 1093 (HY000): You can't specify target table 'cd1' for update in FROM clause
Toll, 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 cd1
SET cd1.dublette = ( SELECT anzahl
FROM ( SELECT cd2.id, COUNT(*) AS anzahl
FROM check_dubletten cd2
GROUP BY cd2.id ) cd3
WHERE cd3.id = cd1.id );
Query OK, 6 rows affected (0.05 sec)
Rows matched: 6 Changed: 6 Warnings: 0
SELECT id, dublette FROM check_dubletten
WHERE 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.
EXPLAIN
SELECT cd1.dublette
FROM check_dubletten cd1
JOIN ( SELECT cd2.id, anzahl
FROM ( SELECT id, COUNT(*) AS anzahl
FROM check_dubletten
GROUP BY id ) cd2
) cd3
ON 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.