Bietet Rezepte, Lösungen und ausführliche Beispiele rund um gesundes SQL und zufriedene Datenbanken.
SQL ist lustig und manchmal fällt man richtig rein. Sei es nun bei MySQL oder anderen Datenbanken. Eben habe ich in einem Forum eine Frage gelesen, wie man am Besten den "letzten" Datensatz in einer Tabelle findet. Lösungen? Jede Menge, manche einfach und genial, andere wiederum perfekt geschaffen für einen unfreiwilligen "internen" DoS (Denial of Service) Angriff.
Um was geht es? Der letzte Datensatz (die höchste ID) soll gezielt gefunden und dieser Datensatz gelesen werden.
* mysql-essential-5.1.55-winx64
* mysql-5.5.9-winx64
* mysql-5.6.2-m5-winx64
CREATE TABLE test_last_row (
id INT(11) NOT NULL AUTO_INCREMENT,
wert1 INT(10) UNSIGNED NOT NULL,
wert2 INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (id)
) ENGINE = INNODB;
INSERT INTO test_last_row
SELECT NULL, wert1, wert2 FROM geodaten;
=> 1000000 rows inserted [5,291s]
Zuerst zwei Lösungen, um schnell ans Ziel zu kommen. Schnell, da MySQL unmittelbar über den Index zugreifen kann.
/* Standard SQL */
SELECT t1.*
FROM test_last_row t1
JOIN ( SELECT MAX(t2.id) AS max_id FROM test_last_row t2 ) t2
ON t1.id = t2.max_id;
Ausführdauer laut Profile => 0,000441 Sekunden
/* MySQL Lösung mit LIMIT */
SELECT * FROM test_last_row
ORDER BY id DESC
LIMIT 1;
Ausführdauer laut Profile => 0,000241 Sekunden
Die Lösungen aus Listing 2: und 3: sind praktisch und perfekt. Und nun eine Variante, die im ersten Moment richtig nett aussieht, bei kleinen Tabellen auch funktioniert, aber sich bei grossen Datenmengen zum selbstgemachten DoS Angriff auf die eigene Datenbank auswächst. Warum? Alle Datensätze der Tabelle T1 werden solange mit allen Datensätze der Tabelle T2 verglichen, bis das Tabellen-Ende erreicht ist. Ein klassisches kartesisches Produkt eben.
/* ACHTUNG! */
SELECT t1.*
FROM test_last_row t1
LEFT JOIN test_last_row t2
ON (t1.id < t2.id)
WHERE t2.id IS NULL;
Ausführdauer ... sehr sehr lang!
Unmittelbar nach dem Einfügen eines neuen Datensatzes kann man mit MySQL auch folgendes machen. Einfach die letzte automatisch vergebene Auto_Increment_Id ermitteln und damit den "letzten" Datensatz lesen. Beispielsweise geht dies mit der bekannten LAST_INSERT_ID() Funktion.
mysql> INSERT INTO test_last_row (wert1, wert2 ) VALUES (4711, 4712);
mysql> select * from test_last_row where id = last_insert_id();
+----------+-------+-------+
| id | wert1 | wert2 |
+----------+-------+-------+
| 1000001 | 4711 | 4712 |
+----------+-------+-------+
1 row in set (0.00 sec)
Oder man nutzt den weniger bekannten MySQL ODBC Workaround. Jedenfalls funktioniert dieser mit MySQL 5.1.55 oder vorher. Bei den neueren MySQL-Versionen ( 5.5.9 und 5.6.2-m5) scheint es - eigentlich zum Glück - nicht mehr zu funktionieren. Zum Glück? Ja, weil diese Methode meines Erachtens vollkommen irreführend ist.
mysql> select version();
+------------------+
| version() |
+------------------+
| 5.1.55-community |
+------------------+
mysql> INSERT INTO test_last_row (wert1, wert2 ) VALUES (4711, 4712);
Query OK, 1 row affected (0.00 sec)
mysql> select last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
| 1000001 |
+------------------+
mysql> SELECT * FROM test_last_row WHERE id IS NULL;
+----------+-------+-------+
| id | wert1 | wert2 |
+----------+-------+-------+
| 1000001 | 4711 | 4712 |
+----------+-------+-------+
Mit dem sogenannten ODBC-Workaround läßt sich die letzte vergeben Auto_increment_id mit WHERE id IS NULL ermitteln. Obwohl die Spalte ID eine NOT NULL Spalte ist, liefert MySQL ein Ergebnis. Bei den neueren Versionen 5.5.9 und 5.6.2-m5 wird stattdessen ein leeres Resultset ausgegeben.
/* identisch bei 5.5.9 und 5.6.2-m5 */
mysql> INSERT INTO test_last_row (wert1, wert2 ) VALUES (4711, 4712);
Query OK, 1 row affected (0.00 sec)
mysql> select last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
| 1000001 |
+------------------+
mysql> SELECT * FROM test_last_row WHERE id IS NULL;
Empty set (0.00 sec)
SQL macht Spaß und wie bereits eingangs erwähnt, gibt es für manches Problem diverse Lösungen, aber auch Fallstricke, die man verstehen und kennen sollte. Sei es nun das Problem des kartesischen Produkts oder das Migrationsproblem von Datenbankversionen, wenn verwendete Techniken nicht mehr wie gewohnt funktionieren.