Gestern kam mein Kollege Alexander mit einer ganz spannenden SQL-Fragestellung zu mir. weil die Anwendung auch noch den SQL-Server-2000 unterstützt, mussten wir etwas in die Trickkiste greifen. Ich habe die Fragestellung jetzt daheim noch mal unter dem SQL-Server-2005 nachvollzogen und konnte dabei ein paar der schicken, neuen Features nutzen…

Die Frage ist, wie man Strings, die über eine Legacy-Schnittstelle kommen und so in die Datenbank geschrieben wurden, aufteilen kann. Beispielsweise soll die Zeichenfolge "1234#12#bla#blub#132" mit SQL anhand des Trenners "#" in die Einzelbestandteile aufgeteilt und dann weiterverarbeitet werden.

So könnte die Tabelle aussehen:

LfdNr Zeichenkette
1 13242#22#blabla#233242#blubblub#12321#1312#1131##13123
2 13332#12#blibla#233242#blubblub#12321#1312#1131##13123
3 13353#21#blabli#233242#blubblub#12321#1312#1131##13123
4 13242#12#blibli#233242#blubblub#12321#1312#1131##13123

Eine Lösung, die mir persönlich besonders gut gefällt, hat sich der große Itzik Ben Gan ausgedacht: Dabei wird eine Hilfstabelle benötigt, die nur eine Spalte hat, und für jede Zahl einen Datensatz enthält.

n
1
2
3
4

Dann kann man mit einem Cross-Join zwischen den Tabellen in der Where-Klausel diejenigen Sätze rausfischen, die an der n-ten Position ein "#" haben:

...
from LegacyInput
cross join hilfstabelle
where substring('#'+Zeichenkette+'#', n, 1)='#'
and n <= len(Zeichenkette)+2

Damit das erste Teilstück bei 1 anfängt, wird noch ein "#" vor den Anfang angefügt. Außerdem müssen nur die Datensätze untersucht werden, die kleiner/gleich der Länge der Zeichenkette sind. Das ist eigentlich doppelt gemoppelt und kann daher auch entfallen.

Im SELECT werden dann die Einzelbestandteile rausgelöst: von n bis zum nächsten "#", wobei "n" eine Position ist, an der der ein "#" gefunden wurde.

substring('#'+Zeichenkette, n+1, charindex('#',Zeichenkette+'#',n)-n)

Das gesamte Statement sieht dann so aus:

select lfdNr,
n-len(replace(substring('#'+Zeichenkette, 1, n), '#', '')) as "TeilNr",
substring('#'+Zeichenkette, n+1, charindex('#',Zeichenkette+'#',n)-n) as "Teilstück",
Zeichenkette
from LegacyInput
cross join hilfstabelle
where substring('#'+Zeichenkette, n, 1)='#'
and n <= len(Zeichenkette)+1

Und so das Ergebnis:

LfdNr TeilNr Teilstück
1 1 13242
1 2 12
1 3 blabli
1 4 233242

Das ist aus Datenbanksicht, aber nicht optimal. Wünschenswert wäre ein pivotiertes Ergebnis:

LfdNr attr1 attr2 attr3 attr4 attr5 attr6 attr7 attr8 attr9 attr10
1 13242 22 blabla 233242 blubblub 12321 1312 1131 13123
2 13332 12 blibla 233242 blubblub 12321 1312 1131 13123
3 13353 21 blabli 233242 blubblub 12321 1312 1131 13123
4 13242 12 blibli 233242 blubblub 12321 1312 1131 13123

Das kann man dann ganz einfach mit dem PIVOT-Konstrukt erreichen:

SELECT *
FROM (select
LfdNr,
'attr'+convert(varchar,n-len(replace(substring('#'+Zeichenkette, 1, n), '#', ''))) as "Attribute",
substring('#'+Zeichenkette, n+1, charindex('#',Zeichenkette+'#',n)-n) as "Teilstück"
from LegacyInput
cross join hilfstabelle
where substring('#'+Zeichenkette, n, 1)='#'
and n <= len(Zeichenkette)+1 ) AS ATR PIVOT ( MAX("Teilstück") FOR attribute IN([attr1], [attr2], [attr3], [attr4], [attr5], [attr6], [attr7], [attr8], [attr9], [attr10]) ) AS PVT

Das komplette Beispiel steht auch zum Download bereit.