Das Beste aus zwei Welten

SQL und Regex – zwei Garanten für genialen und im Nachhinein völlig unverständlichen Code. Was liegt also näher, als beides zu kombinieren…

Im konkreten, unglaublich wichtigen Problemfall ging es darum, aus einer Tabelle mit Bowlingergebnissen die Anzahl der unmittelbar nacheinandergeworfenen Strikes (strikes in a row) zu berechnen.

Ja, mit einer PROCEDURE wäre das Ganze trivial, aber es bestand der Wille, das Ganze in Plain-SQL hinzubekommen, also insbesondere ohne Schleifen. Dank regulären Ausdrücken klappt das nicht nur, sondern sieht darüber hinaus auch noch unglaublich ästhetisch aus.

Die zugrundeliegende Tabelle enthält in den Spalten sp_w01_1, sp_w01_2, sp_w02_1 usw. die im jeweiligen Wurf gefallenen Pins (w01_1 == erster Frame, erster Wurf; w01_2 == erster Frame, zweiter Wurf usw.). Für das Aufsummieren der Strikes sind nur die jeweils ersten Würfe interessant, lediglich im zehnten Frame müssen auch der zweite und dritte Wurf berücksichtigt werden.

Die folgende SQL-Abfrage liefert das Gewünschte – eine Erklärung für Denkfaule folgt darunter

select pers_name, sp_id,

regexp_replace(
regexp_replace(
regexp_replace(
regexp_replace(
regexp_replace(
regexp_replace(
regexp_replace(
regexp_replace(
regexp_replace(
regexp_replace(
regexp_replace(
regexp_replace(
regexp_replace(
regexp_replace(

CASE WHEN spiel.sp_w01_1 = 10 THEN 'X'::text ELSE '-'::text END ||
CASE WHEN spiel.sp_w02_1 = 10 THEN 'X'::text ELSE '-'::text END ||
CASE WHEN spiel.sp_w03_1 = 10 THEN 'X'::text ELSE '-'::text END ||
CASE WHEN spiel.sp_w04_1 = 10 THEN 'X'::text ELSE '-'::text END ||
CASE WHEN spiel.sp_w05_1 = 10 THEN 'X'::text ELSE '-'::text END ||
CASE WHEN spiel.sp_w06_1 = 10 THEN 'X'::text ELSE '-'::text END ||
CASE WHEN spiel.sp_w07_1 = 10 THEN 'X'::text ELSE '-'::text END ||
CASE WHEN spiel.sp_w08_1 = 10 THEN 'X'::text ELSE '-'::text END ||
CASE WHEN spiel.sp_w09_1 = 10 THEN 'X'::text ELSE '-'::text END ||
CASE WHEN spiel.sp_w10_1 = 10 THEN 'X'::text ELSE '-'::text END ||
CASE WHEN spiel.sp_w10_2 = 10 THEN 'X'::text ELSE '-'::text END ||
CASE WHEN spiel.sp_w10_3 = 10 THEN 'X'::text ELSE '-'::text END,

'.*XXXXXXXXXXXXX.*'::text, '13'::text, 'g'::text),
'.*XXXXXXXXXXXX.*'::text, '12'::text, 'g'::text),
'.*XXXXXXXXXXX.*'::text, '11'::text, 'g'::text),
'.*XXXXXXXXXX.*'::text, '10'::text, 'g'::text),
'.*XXXXXXXXX.*'::text, '9'::text, 'g'::text),
'.*XXXXXXXX.*'::text, '8'::text, 'g'::text),
'.*XXXXXXX.*'::text, '7'::text, 'g'::text),
'.*XXXXXX.*'::text, '6'::text, 'g'::text),
'.*XXXXX.*'::text, '5'::text, 'g'::text),
'.*XXXX.*'::text, '4'::text, 'g'::text),
'.*XXX.*'::text, '3'::text, 'g'::text),
'.*XX.*'::text, '2'::text, 'g'::text),
'.*X.*'::text, '1'::text, 'g'::text),
'.*-.*'::text, '0'::text, 'g'::text)

::integer AS strikesinarow

from spiel natural join person
order by pers_name, strikesinarow desc;

Zuerst wird ein String zusammengesetzt, in dem die gesamte Abfolge Strikes/Nichtstrikes des gesamten Spiels codiert ist ('X' == Strike, '-' == kein Strike).

Dann geht die Reg(h)exerei los: Sollte der String 13 mal hintereinander 'X' enthalten, so wird er (als Ganzes) durch den String "13" ersetzt. Sollte der String 12 mal hintereinander 'X' enthalten, so wird er (wieder als Ganzes) durch den String "12" ersetzt, usw. Nur bei einem Strike-losen Spiel wird der String am Ende durch den String "0" ersetzt.

Nunja, und anschließend muss der String (der ja nun die gesuchte Zahl enthält) nur noch in einen Integer umgewandelt werden…

One thought on “Das Beste aus zwei Welten”

Comments are closed.