MySQL-Erkenntnisse die Erste

Herzlich Willkommen zur einzigen zu einer neuen Ausgabe des Creations MySQL-Newsletters,

schon mehrfach trat mein geschätzter Kollege Sebastian mit einem code-kosmetischen Problem an mich heran. Er fragte, ob es nicht möglich sei, beim Holen der Sprachdaten aus einer Datenbank direkt einen Fallback-Spracheintrag mitzuholen, für den Fall, daß die gewünschte Sprache nicht verfügbar wäre. Stets mußte ich verneinen. Doch heute – beim Lösen eines ganz anderen Problems – kam mir die Erleuchtung.


Hier die Tabellendaten zum Nachmachen:

CREATE TABLE language_test ( id int(10) unsigned NOT NULL auto_increment, inhalt varchar(255) NOT NULL, schluessel int(10) unsigned NOT NULL, sprache enum('deutsch','englisch') NOT NULL, PRIMARY KEY (id) ) ENGINE=MyISAM DEFAULT CHARSET=latin1; INSERT INTO language_test VALUES (1, 'The quick brown fox', 42, 'englisch'); INSERT INTO language_test VALUES (2, 'Der schnelle braune Fuxx', 42, 'deutsch'); INSERT INTO language_test VALUES (3, 'Toller Text', 815, 'deutsch'); INSERT INTO language_test VALUES (4, 'awesome text', 815, 'englisch'); INSERT INTO language_test VALUES (5, 'Verschwindibus', 666, 'deutsch');

Und hier das entscheidende Query:

SELECT COALESCE(normal.inhalt,fallback.inhalt) FROM language_test AS fallback LEFT JOIN language_test AS normal ON normal.schluessel = fallback.schluessel AND normal.sprache="englisch" WHERE fallback.sprache="deutsch" AND fallback.schluessel IN (666,42,815)

Zu beachten ist, daß es einen Spracheintrag in der Fallback-Sprache auf jeden Fall geben muß, sonst wird gar keiner zurückgegeben, auch wenn es in der eigentlich gewählten Sprache einen geben sollte. Um das zu erreichen, wäre ein FULL OUTER JOIN nötig, den, wie ich eben mit Entsetzen feststellen mußte, MySQL nicht kann.

Wenn man nun ein Query pro Sprachkonstante macht, halbiert sich durch dieses Verfahren die Anzahl der Queries – sicherlich eine sinnvolle Angelegenheit. Wenn man aber alle Sprachkonstanten auf einmal lädt, stellt sich die Frage, was schneller ist: Die oben beschriebene Technik, die immerhin erfordert, daß Zeilen geladen werden, die letztlich vom COALESCE sowieso wieder rausgeschmissen werden, oder doch das Herausfinden der fehlenden Einträge im PHP mit anschließendem Holen der Fallback-Einträge in einem zweiten Query. Dies herauszufinden sei dem geneigten Leser überlassen.

Kommen wir nun zum zweiten Thema des heutigen Newsletters. Auch hier dreht es sich wieder um COALESCE.

Vor einiger Zeit führte ich mit Sven ein Gespräch über das Summieren von Traffic. Ich habe immer noch vergessen, worum es genau ging, aber möglicherweise ging es ja um das Problem des Gruppierens in UNIONs. Hierzu möchte ich nämlich zwei Techniken vorzustellen, die eventuell nicht offensichtlich sind. Sie bietet sich auch an, wenn es gilt, Daten aus verschiedenen Tabellen zu gruppieren, etwa um Statistiken aus einer aktuellen und weiteren Archiv-Tabellen zu erstellen.

Nehmen wir an, wir möchten Traffic aus zwei Tabellen nach Tagen summieren.

Hierzu schreiben wir:

SELECT tag, SUM(menge) FROM ( (SELECT tag,menge FROM in_traffic) UNION (SELECT tag,menge FROM out_traffic) ) AS sub GROUP BY tag

Wer – aus welchen Gründen auch immer – das Subselect vermeiden möchte, kann zu einem Trick greifen: Eine sogenannte “mutex table”.

Hierzu legen wir eine weitere Tabelle an, die nur ein Feld mit beliebigen, aber unterschiedlichen Werten enthält:

CREATE TABLE mutex( i INT NOT NULL PRIMARY KEY ); INSERT INTO mutex(i) VALUES (0), (1);

Anschließend können wir – ausgehend von dieser Mutex-Tabelle – durch JOINs das UNION simulieren:

SELECT COALESCE(it.tag,ot.tag) AS ctag, SUM(COALESCE(it.menge,ot.menge)) AS cmenge FROM mutex LEFT JOIN in_traffic it ON mutex.i=0 LEFT JOIN out_traffic ot ON mutex.i=1 GROUP BY ctag