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.