T-SQL Stringhandling (Extraktion von Token)
Heute möchte ich einmal ein kleines Script bzw. eine kleine Funktion vorstellen die zum Zerlegen von Strings sehr nützlich sein kann.
Wenn man strukturierte Texte vorliegen hat und daraus bestimmte Teile extrahieren soll, so kann man dies sicher mit den Stringfunktionen von T-SQL erledigen. Gerade die Verarbeitung von Listen (Kommasepariert oder sonst irgendwie) bedingt aber die wiederholte Extraktion von Textblöcken (Token) die durch verschiedenste Begrenzungszeichen voneinander getrennt sind.
Als Token verstehe ich in diesem Zusammenhang Teile eines Textes die durch Begrenzer (Delimiter) voneinander logisch getrennt werden. In einem Satz wären das die Worte, in einer Liste die durch z.B. Komma getrennten Artikelnummern oder einfach Bestandteile eines Tabellennamens usw.
Sehen wir uns hierzu mal ein paar Beispiele an:
- String: ‘Hello World, how are you?’
- String: ‘123,2345,231234,3456′
- String: ‘[AdventureWorks].[Person]. [Contact]‘
Extrahieren wir mal das dritte Wort aus Beispiel 1, den zweiten Parameterwert aus Beispiel 2 und den Tabellennamen aus Beispiel 3!
Mit Standardmitteln sicherlich ebenfalls zu realisieren, jedoch mit einer Funktion viel eleganter und Übersichtlicher zu gestalten:
| T-SQL | | copy code | | ? |
| 01 | DECLARE @String1 VARCHAR(1000) |
| 02 | DECLARE @String2 VARCHAR(1000) |
| 03 | DECLARE @String3 VARCHAR(1000) |
| 04 | |
| 05 | -- -------------------------------------------------------------------- |
| 06 | -- Single Delimiter ' ' Leerzeichen |
| 07 | -- -------------------------------------------------------------------- |
| 08 | |
| 09 | SET @String1 = 'Hello World, how are you?' |
| 10 | |
| 11 | PRINT @String1 + ' ==> ' + dbo.fn_GetToken(' ',3,@String) |
| 12 | |
| 13 | -- -------------------------------------------------------------------- |
| 14 | -- Single Delimiter ',' Komma |
| 15 | -- -------------------------------------------------------------------- |
| 16 | |
| 17 | SET @String2 = '123,2345,231234,3456' |
| 18 | |
| 19 | PRINT @String2 + ' ==> ' + dbo.fn_GetToken(',',2,@String) |
| 20 | |
| 21 | -- -------------------------------------------------------------------- |
| 22 | -- Paariger Delimiter [] |
| 23 | -- -------------------------------------------------------------------- |
| 24 | |
| 25 | SET @String3 = '[AdventureWorks].[Person].<leerzeichen>[Contact]' |
| 26 | |
| 27 | PRINT @String3 + ' ==> ' + dbo.fn_GetToken('[<-*->]',3,@String) |
Die Syntax für den Fuktionsaufruf sieht dann wie folgt aus:
dbo.fn_GetToken(single_delimiter|delimiter_pair, position, source_string)
- single_delimiter: Beliebige Zeichenkette die Token voneinander trennt.
- delimiter_pair: Beliebige Zeichenkette, danach ein Trennblock <-*-> und dann wieder eine Zeichenkette.
- position: Das n-te Token beginnend mit 1.
Wenn position = 0 dann wird der String unverändert returniert. - source_string: Der Quellstring.
- Als Rückgabewert wird das Token ohne die Delimiter geliefert oder NULL im Fehlerfall.
… und hier wie immer das Script dazu:
| T-SQL | | copy code | | ? |
| 001 | CREATE FUNCTION dbo.[fn_GetToken] |
| 002 | ( |
| 003 | @Delim NVARCHAR(100), |
| 004 | @TokenNr INT, |
| 005 | @Zeile NVARCHAR(MAX) |
| 006 | ) |
| 007 | RETURNS NVARCHAR(MAX) |
| 008 | AS |
| 009 | BEGIN |
| 010 | |
| 011 | DECLARE @RetWert NVARCHAR(MAX) |
| 012 | DECLARE @Line NVARCHAR(MAX) |
| 013 | |
| 014 | DECLARE @End_Delim NVARCHAR(100) |
| 015 | DECLARE @End_Delim_Len INT |
| 016 | |
| 017 | DECLARE @Delim_Pos_S INT |
| 018 | DECLARE @Delim_Pos_E INT |
| 019 | |
| 020 | DECLARE @Delim_S NVARCHAR(100) |
| 021 | DECLARE @Delim_E NVARCHAR(100) |
| 022 | |
| 023 | DECLARE @Pos INT |
| 024 | DECLARE @LEN INT |
| 025 | DECLARE @Delim_Len INT |
| 026 | DECLARE @TokenCnt INT |
| 027 | |
| 028 | -- Initialisierung der Parameter auf Defaults |
| 029 | |
| 030 | IF @Zeile IS NULL SET @Zeile = N'' |
| 031 | IF @Delim IS NULL SET @Delim = N';' |
| 032 | IF @TokenNr IS NULL SET @TokenNr = 1 |
| 033 | |
| 034 | SET @Line = @Zeile |
| 035 | |
| 036 | -- Anfangs- und Ende-Delimiter bestimmen wenn die Zeichenfolge |
| 037 | -- '<-*->' im Delimiter vorhanden ist. |
| 038 | -- Im anderen Fall wird die Zeichenfolge als Einzeldelimiter verwendet. |
| 039 | |
| 040 | IF CHARINDEX(N'<-*->',@Delim) > 0 |
| 041 | BEGIN |
| 042 | SET @End_Delim = SUBSTRING(@Delim, |
| 043 | CHARINDEX(N'<-*->',@Delim) |
| 044 | +5,datalength(@Delim)/2) |
| 045 | |
| 046 | SET @Delim = SUBSTRING(@Delim,1, |
| 047 | CHARINDEX(N'<-*->',@Delim)-1) |
| 048 | END |
| 049 | ELSE |
| 050 | BEGIN |
| 051 | SET @End_Delim = @Delim |
| 052 | END |
| 053 | |
| 054 | -- Die Länge des Begrenzers muss halbiert werden (Unicode) |
| 055 | |
| 056 | SET @Delim_Len = datalength(@Delim)/2 |
| 057 | SET @End_Delim_Len = datalength(@End_Delim)/2 |
| 058 | |
| 059 | -- Start und Ende setzen. Wenn nur ein Begrenzer definiert ist, |
| 060 | -- dann sind Start und Ende gleich. |
| 061 | |
| 062 | SET @Delim_S = @Delim |
| 063 | SET @Delim_E = @End_Delim |
| 064 | |
| 065 | -- Die eckige Klammer muss als regulärer Ausdruck gecastet werden. |
| 066 | -- Aus [ wird [[] |
| 067 | |
| 068 | SET @Delim = REPLACE(@Delim ,N'[','[[]') |
| 069 | SET @End_Delim = REPLACE(@End_Delim,N'[','[[]') |
| 070 | |
| 071 | -- Wenn es nur einen Delimiter gibt, muss die ganze Zeile auf eine |
| 072 | -- Zwei-Delimiter-Logik erweitert werden |
| 073 | -- Dies bedeutet, dass die Zeile von einem Start und Enddelimiter |
| 074 | -- umschlossen sein muss. Diese Werte werden ggf. hinzugefügt. |
| 075 | |
| 076 | IF @Delim_S = @Delim_E |
| 077 | BEGIN |
| 078 | IF SUBSTRING(@Zeile,1,@Delim_Len ) <> @Delim_S |
| 079 | SET @Zeile = @Delim_S + @Zeile |
| 080 | |
| 081 | IF RIGHT (@Zeile ,@End_Delim_Len) <> @Delim_E |
| 082 | SET @Zeile = @Zeile + @Delim_E |
| 083 | END |
| 084 | |
| 085 | -- Delimiter erweitern |
| 086 | |
| 087 | IF SUBSTRING(@Delim,1,1) <> N'%' SET @Delim = N'%'+ @Delim |
| 088 | IF RIGHT (@Delim,1) <> N'%' SET @Delim = @Delim + N'%' |
| 089 | |
| 090 | IF SUBSTRING(@End_Delim,1,1) <> N'%' SET @End_Delim = N'%'+ @End_Delim |
| 091 | IF RIGHT (@End_Delim,1) <> N'%' SET @End_Delim = @End_Delim + N'%' |
| 092 | |
| 093 | -- Initial-Position setzen |
| 094 | |
| 095 | SET @Pos = 1 |
| 096 | SET @TokenCnt = 1 |
| 097 | |
| 098 | SET @Delim_Pos_S = PATINDEX(@Delim,@Zeile) |
| 099 | SET @Delim_Pos_E = PATINDEX(@End_Delim, |
| 100 | SUBSTRING(@Zeile,@Delim_Pos_S+@Delim_Len , |
| 101 | datalength(@Zeile)/2)) |
| 102 | + @Delim_Pos_S |
| 103 | + @Delim_Len-1 |
| 104 | |
| 105 | -- Delimiter und Token suchen |
| 106 | |
| 107 | IF @TokenNr > 0 |
| 108 | BEGIN |
| 109 | WHILE (@Delim_Pos_S > 0 and @Delim_Pos_E > 0) |
| 110 | BEGIN |
| 111 | |
| 112 | -- Ermittlung des Token ... |
| 113 | |
| 114 | SET @LEN = @Delim_Pos_E - @Delim_Pos_S - @Delim_Len |
| 115 | SET @RetWert = SUBSTRING(@Zeile,@Delim_Pos_S + @Delim_Len,@LEN) |
| 116 | |
| 117 | -- Vorbereitung auf den nächsten Token ... |
| 118 | |
| 119 | IF @TokenCnt = @TokenNr BREAK ELSE SET @TokenCnt = @TokenCnt + 1; |
| 120 | |
| 121 | SET @Zeile = SUBSTRING(@Zeile,@Delim_Pos_E + @End_Delim_Len, |
| 122 | datalength(@Zeile)/2 - @Delim_Pos_E) |
| 123 | |
| 124 | IF @Delim_S = @Delim_E and datalength(@Zeile)/2 > 0 |
| 125 | BEGIN |
| 126 | IF SUBSTRING(@Zeile,1,@Delim_Len ) <> @Delim_S |
| 127 | SET @Zeile = @Delim_S + @Zeile |
| 128 | |
| 129 | IF RIGHT (@Zeile ,@End_Delim_Len) <> @Delim_E |
| 130 | SET @Zeile = @Zeile + @Delim_E |
| 131 | END |
| 132 | |
| 133 | SET @Delim_Pos_S = PATINDEX(@Delim,@Zeile) |
| 134 | |
| 135 | SET @Delim_Pos_E = PATINDEX(@End_Delim, |
| 136 | SUBSTRING(@Zeile,@Delim_Pos_S+@Delim_Len, |
| 137 | datalength(@Zeile)/2)) |
| 138 | + @Delim_Pos_S |
| 139 | + @Delim_Len-1 |
| 140 | |
| 141 | IF @Delim_Pos_E <= @Delim_Len SET @RetWert = NULL |
| 142 | END |
| 143 | END |
| 144 | |
| 145 | -- Returncodes setzen |
| 146 | |
| 147 | SET @RetWert = CASE WHEN @TokenCnt < @TokenNr THEN NULL |
| 148 | WHEN @TokenNr < 0 THEN NULL |
| 149 | WHEN @TokenNr = 0 THEN @Line |
| 150 | ELSE @RetWert |
| 151 | END |
| 152 | |
| 153 | RETURN @RetWert |
| 154 | END |
| 155 |
Tip: Man kann die Funktionsaufrufe natürlich auch ineinander verschachteln!
have fun
Facebook
XING
META-SQL