Find an unmatched curly brackets in a string in SQL - sql

How can I find an unmatched curly brackets in a string in SQL?
DECLARE #iVariable varchar(100)
SET iVariable = '{Day}{Month}{Year}'
If any unmatched left bracket found ({Day}{Month{Year}) then should return 'Unmatched {'
else any unmatched right bracket found ({Day}{Month}Year}) then should return 'Unmatched }'
If there is no unmatched brackets it shoud return the value in comma seperated format eg ('Day,Month,Year')
Is there any logic to do this?

What I would do is that I would validate the length of the string after replacing '{' with '' (empty string).
DECLARE #iVariable varchar(100) = '{Day}{Month}{Year}'
select case
when len(#iVariable) - len(replace(#iVariable, '{', '')) < len(#iVariable) - len(replace(#iVariable, '}', ''))
then 'Unmatched }'
when len(#iVariable) - len(replace(#iVariable, '{', '')) > len(#iVariable) - len(replace(#iVariable, '}', ''))
then 'Unmatched {'
else right(replace(replace(#iVariable, '{', ','), '}', ''), len(replace(replace(#iVariable, '{', ','), '}', '')) - 1)
end
What happens here is that I check if there are more '}' than '{', it returns unmatched '}'.
Likewise for '}'.
If the number matches, it returns the original string, with '{' and '}' replaced out, and commas inserted instead.
Edit: As Gordon stated in the comments, this does not work for for example '{}}{'.
Instead, You could use a user defined function. For instance:
create function SomeFunc(#iVariable varchar(2000))
returns varchar(3000)
as
begin
if len(replace(replace(#iVariable, '}', ''), '{', '')) = 0
return 'No data present'
else
begin
-- Declare stuff to be used
declare #result varchar(3000) = ''
declare #AMT_Left int = len(#iVariable) - len(replace(#iVariable, '{', ''))
declare #AMT_Right int = len(#iVariable) - len(replace(#iVariable, '}', ''))
-- First test if no. of brackets match:
if #AMT_Left > #AMT_Right
set #result = 'Unmatched }'
else if #AMT_Left < #AMT_Right
set #result = 'Unmatched {'
else if #AMT_Left = #AMT_Right
begin
-- If matched, define result, and use while loop for error handling
set #result = right(replace(replace(#iVariable, '{', ','), '}', ''), len(replace(replace(#iVariable, '{', ','), '}', '')) - 1)
DECLARE #intFlag INT
SET #intFlag = 1
-- Loop through each set and check if '{' occurs before '}':
WHILE (#intFlag <= #AMT_Left and #result != 'Non matching pair')
BEGIN
if charindex('{', #iVariable) > charindex('}', #iVariable)
set #result = 'Non matching pair'
set #iVariable = right(#iVariable, len(#iVariable) - charindex('}', #iVariable))
SET #intFlag = #intFlag + 1
end
end
end
return #result
end;
go
Testing with these input values:
select dbo.SomeFunc('{Day}{Month}{Year}')
select dbo.SomeFunc('{Day}{Month{Year}')
select dbo.SomeFunc('{Day}{Month}Year}')
select dbo.SomeFunc('{}{}')
select dbo.SomeFunc('{}}{')
select dbo.SomeFunc('{Day}}Month{')
result:
Day,Month,Year
Unmatched }
Unmatched {
No data present
No data present
Non matching pair

There may be a more elegant way to do this, but the following should capture all the cases:
with v as (
select '{Day}{Month}{Year}' as var union all
select '{Day}{Month}{Year}}{' union all
select '{Day}{Month}{Year}}}'
),
cte as (
select left(var, 1) as c, 1 as num, var
from v
union all
select substring(var, num+1, 1), num + 1, var
from cte
where num <= len(var)
)
select var,
(case when min(balance) < 0 then 'Unbalanced }'
when sum(case when c = '{' then 1
when c = '}' then -1
else 0
end) > 0
then 'Unbalanced {'
else 'Balanced'
end)
from (select cte.*,
(select sum(case when c = '{' then 1
when c = '}' then -1
else 0
end)
from cte cte2
where cte2.var = cte.var and cte2.num <= cte.num
) as balance
from cte
) t
group by var;
This explodes the values character by character and then checks for the balance.

Related

Replace the alternate occurances of a substring

My input strings are like:
A or B OR C or D OR E or F
A OR B OR C OR D OR E OR F
Expected Output: 'A or B' OR 'C or D' OR 'E or F'
outputString = '''' + REPLACE(#inputValue COLLATE Latin1_General_CS_AS, ' OR ' COLLATE Latin1_General_CS_AS, ''' OR ''') + ''''
I tried using SQL Replace function and the above statement works properly for the first string and I get the desired output but for the second string since we have all the ORs in the uppercase it fails and returns 'A' OR 'B' OR 'C' OR 'D' OR 'E' OR 'F'
I'm using SSMS 15.0.
How can I solve this problem? Any help will be appreciated.
Here's a solution that uses a UDF.
The function splits a string on a pattern as a resultset.
(similar as the STRING_SPLIT function, but with a pattern)
The FOR XML trick is then used to construct a string from the splitted parts, and to add the quotes.
DECLARE #vchNewValue VARCHAR(100), #result VARCHAR(100);
SET #vchNewValue = 'A OR B or C OR D or E OR F';
SET #result = LTRIM(RTRIM((
SELECT
CASE WHEN match = 1
THEN ' '+quotename(ltrim(rtrim(replace(value,' OR ',' or ') )),'''')+' '
ELSE UPPER(value)
END
FROM dbo.fnPattern_Split(' '+#vchNewValue+' ', ' % OR % ') AS spl
ORDER BY ordinal
FOR XML PATH(''), TYPE).value(N'./text()[1]', N'nvarchar(max)')
));
SELECT #result AS result;
result
'A or B' OR 'C or D' OR 'E or F'
Test db<>fiddle here
The UDF
Uses PATINDEX to find each next start position of the given pattern in the string.
Then finds the nearest end position where the pattern is still valid.
So it's kinda like a lazy search in regex.
The positions are then used to insert the parts into the returned table.
CREATE FUNCTION dbo.fnPattern_Split
(
#str VARCHAR(MAX),
#pattern VARCHAR(100)
)
RETURNS #tbl TABLE (
ordinal INT,
value VARCHAR(MAX),
match BIT
)
WITH SCHEMABINDING
AS
BEGIN
DECLARE #value NVARCHAR(MAX)
, #splitvalue NVARCHAR(MAX)
, #startpos INT = 0
, #endpos INT = 0
, #ordinal INT = 0
, #foundend BIT = 0
, #patminlen INT = ISNULL(NULLIF(LEN(REPLACE(#pattern,'%','')),0),1);
WHILE (LEN(#str) > 0)
BEGIN
SET #startpos = ISNULL(NULLIF(PATINDEX('%'+#pattern+'%', #str),0), LEN(#str)+1);
IF #startpos < LEN(#str)
BEGIN
SET #foundend = 0;
SET #endpos = #startpos+#patminlen-1;
WHILE #endpos < LEN(#str) AND #foundend = 0
BEGIN
IF SUBSTRING(#str, #startpos, 1+#endpos-#startpos) LIKE #pattern
SET #foundend = 1;
ELSE
SET #endpos += 1;
END
END
ELSE SET #endpos = LEN(#str);
IF #startpos > 1
BEGIN
SET #ordinal += 1;
SET #value = LEFT(#str, #startpos-1);
INSERT INTO #tbl (ordinal, value, match)
VALUES (#ordinal, #value, 0);
END
IF #endpos >= #startpos
BEGIN
SET #ordinal += 1;
SET #splitvalue = SUBSTRING(#str, #startpos, 1+#endpos-#startpos);
INSERT INTO #tbl (ordinal, value, match)
VALUES (#ordinal, #splitvalue, 1);
END
SET #str = SUBSTRING(#str, #endpos+1, LEN(#str));
END;
RETURN;
END;
A recursive solution that stuffs the quotes.
The recursive CTE loops through the string while finding the start positions of the ' or ' patterns.
Since ' or ' has 4 characters, having a start position means you also have the end position.
TheSTUFF function can insert characters in a string on positions.
So the positions are used to stuff the quotes where needed.
Which is every even occurence (modulus 2 of lvl is 0).
declare #input varchar(100)
, #result varchar(100);
set #input = 'A OR B or C OR D or E OR F';
set #result = #input;
with rcte as (
select 1 as lvl
, charindex(' or ', #input) as pos
, len(#input) as max_pos
union all
select lvl+1
, isnull(nullif(charindex(' or ', #input, pos+4), 0), max_pos)
, max_pos
from rcte
where pos < max_pos
)
select #result = stuff(stuff(#result,pos+4,0,''''),pos,0,'''')
from rcte
where lvl%2 = 0 and pos+4 < max_pos
order by lvl desc;
SET #result = ''''+#result+'''';
SET #result = REPLACE(REPLACE(#result,' OR ',' or '),''' or ''',''' OR ''');
select #result as result;
result
'A or B' OR 'C or D' OR 'E or F'
Test on db<>fiddle here
This solution comes with a high cost on server load since while is used.
declare #input varchar(100)
set #input = 'A or B or C or D or E or F or G or'
declare #inc int = 1, #end int = 1
,#final varchar(100) = '', #part varchar(100)
,#nextposition varchar(100), #or varchar(10)= ''
,#last varchar(10), #ifendsOR varchar(10)
select #nextposition = case when #input like '%or' then substring(#input,1,len(#input)-2) else #input end
select #ifendsOR = case when #input like '%or' then ' or' else '' end
select #last = ltrim(rtrim(right(#nextposition,2)))
while #end <> 0
begin
select #part = substring(#nextposition,1,charindex('or',#nextposition)-2)
select #nextposition = replace(#nextposition,concat(#part,' or '),'')
set #end = charindex('or',#nextposition)
select #or = case when #inc%2 = 0 then ' OR ' else ' or ' end
set #inc = #inc+1
set #final = concat(#final,#part,#or)
end
select #ifendsOR = case when #inc%2 = 0 then upper(#ifendsOR) else #ifendsOR end
select concat(#final,#last,#ifendsOR)

MSSQL - Create table function, return substring

I need to create this table function. The function needs to return single words from passed parameters like: hello, hhuu, value
The table function should return:
hello,
hhuu,
value
But I am always getting some errors, please could you help me?
you can write as:
DECLARE #input_char VARCHAR(255)
SET #input_char = 'hello, hhuu, value'
;WITH cte AS (
SELECT
CAST('<r>' + REPLACE(#input_char, ' ', '</r><r>') + '</r>' AS XML) AS input_char
)
SELECT
rtrim( LTRIM (xTable.xColumn.value('.', 'VARCHAR(MAX)')) ) AS input_char
FROM cte
CROSS APPLY input_char.nodes('//r') AS xTable(xColumn)
Please give a look at this article:
http://www.codeproject.com/Tips/625872/Convert-a-CSV-delimited-string-to-table-column-in
and you could use ' ' (space) as delimiter.
SELECT * FROM dbo.CSVtoTable('hello, hhuu, value', ' ')
I have used the following function many times. It is a bit lengthy forgive me, but it has become a great tool for me.
CREATE Function [dbo].[ParseText2Table]
(
#p_SourceText varchar(MAX)
,#p_Delimeter varchar(100) = ',' --default to comma delimited.
)
RETURNS #retTable TABLE
(
POSITION INT
,Int_Value bigint
,Num_value REAL--Numeric(18,3)
,txt_value varchar(MAX)
)
AS
BEGIN
DECLARE #tmpTable TABLE
(
Position2 INT IDENTITY(1,1) PRIMARY KEY
,Int_Value bigint
,Num_value REAL--Numeric(18,3)
,txt_value varchar(MAX)
)
DECLARE #w_Continue INT
,#w_StartPos INT
,#w_Length INT
,#w_Delimeter_pos INT
,#w_tmp_int bigint
,#w_tmp_num REAL--numeric(18,3)
,#w_tmp_txt varchar(MAX)
,#w_Delimeter_Len INT
IF len(#p_SourceText) = 0
BEGIN
SET #w_Continue = 0 -- force early exit
END
ELSE
BEGIN
-- if delimiter is ' ' change
IF #p_Delimeter = ' '
BEGIN
SET #p_SourceText = replace(#p_SourceText,' ','ÿ')
SET #p_Delimeter = 'ÿ'
END
-- parse the original #p_SourceText array into a temp table
SET #w_Continue = 1
SET #w_StartPos = 1
SET #p_SourceText = RTRIM( LTRIM( #p_SourceText))
SET #w_Length = DATALENGTH( RTRIM( LTRIM( #p_SourceText)))
SET #w_Delimeter_Len = len(#p_Delimeter)
END
WHILE #w_Continue = 1
BEGIN
SET #w_Delimeter_pos = CHARINDEX( #p_Delimeter
,(SUBSTRING( #p_SourceText, #w_StartPos
,((#w_Length - #w_StartPos) + #w_Delimeter_Len)))
)
IF #w_Delimeter_pos > 0 -- delimeter(s) found, get the value
BEGIN
SET #w_tmp_txt = LTRIM(RTRIM( SUBSTRING( #p_SourceText, #w_StartPos
,(#w_Delimeter_pos - 1)) ))
IF dbo.isReallyNumeric(#w_tmp_txt) = 1 --and not #w_tmp_txt in('.', '-', '+', '^')
BEGIN
--set #w_tmp_int = cast( cast(#w_tmp_txt as real) as bigint)--numeric) as bigint)
SET #w_tmp_int = CASE WHEN (CAST(#w_tmp_txt AS REAL) BETWEEN -9223372036854775808 AND 9223372036854775808) THEN CAST( CAST(#w_tmp_txt AS REAL) AS bigint) ELSE NULL END
SET #w_tmp_num = CAST( #w_tmp_txt AS REAL)--numeric(18,3))
END
ELSE
BEGIN
SET #w_tmp_int = NULL
SET #w_tmp_num = NULL
END
SET #w_StartPos = #w_Delimeter_pos + #w_StartPos + (#w_Delimeter_Len- 1)
END
ELSE -- No more delimeters, get last value
BEGIN
SET #w_tmp_txt = LTRIM(RTRIM( SUBSTRING( #p_SourceText, #w_StartPos
,((#w_Length - #w_StartPos) + #w_Delimeter_Len)) ))
IF dbo.isReallyNumeric(#w_tmp_txt) = 1 --and not #w_tmp_txt in('.', '-', '+', '^')
BEGIN
--set #w_tmp_int = cast( cast(#w_tmp_txt as real) as bigint)--as numeric) as bigint)
SET #w_tmp_int = CASE WHEN (CAST(#w_tmp_txt AS REAL) BETWEEN -9223372036854775808 AND 9223372036854775808) THEN CAST( CAST(#w_tmp_txt AS REAL) AS bigint) ELSE NULL end
SET #w_tmp_num = CAST( #w_tmp_txt AS REAL)--numeric(18,3))
END
ELSE
BEGIN
SET #w_tmp_int = NULL
SET #w_tmp_num = NULL
END
SELECT #w_Continue = 0
END
INSERT INTO #tmpTable VALUES( #w_tmp_int, #w_tmp_num, #w_tmp_txt )
END
INSERT INTO #retTable SELECT Position2, Int_Value ,Num_value ,txt_value FROM #tmpTable
RETURN
END
Here are the supporting functions for above as well:
CREATE FUNCTION dbo.isReallyInteger
(
#num VARCHAR(64)
)
RETURNS BIT
BEGIN
IF LEFT(#num, 1) = '-'
SET #num = SUBSTRING(#num, 2, LEN(#num))
RETURN CASE
WHEN PATINDEX('%[^0-9-]%', #num) = 0
AND CHARINDEX('-', #num) <= 1
AND #num NOT IN ('.', '-', '+', '^')
AND LEN(#num)>0
AND #num NOT LIKE '%-%'
THEN
1
ELSE
0
END
END
CREATE FUNCTION dbo.isReallyNumeric
(
#num VARCHAR(64)
)
RETURNS BIT
BEGIN
IF LEFT(#num, 1) = '-'
SET #num = SUBSTRING(#num, 2, LEN(#num))
DECLARE #pos TINYINT
SET #pos = 1 + LEN(#num) - CHARINDEX('.', REVERSE(#num))
RETURN CASE
WHEN PATINDEX('%[^0-9.-]%', #num) = 0
AND #num NOT IN ('.', '-', '+', '^')
AND LEN(#num)>0
AND #num NOT LIKE '%-%'
AND
(
((#pos = LEN(#num)+1)
OR #pos = CHARINDEX('.', #num))
)
THEN
1
ELSE
0
END
END
Usage Examples:
--Single Character Delimiter
--select * from dbo.ParseText2Table('100|120|130.56|Yes|Cobalt|Blue','|')
--select txt_value from dbo.ParseText2Table('100 120 130.56 Yes Cobalt Blue',' ') where Position = 3
--select * from dbo.ParseText2Table('100,120,130.56,Yes,Cobalt,Blue,,',',')
/*
POSITION Int_Value Num_value txt_value
----------- ----------- -------------------- --------------
1 100 100.000 100
2 120 120.000 120
3 131 130.560 130.56
4 NULL NULL Yes
5 NULL NULL Cobalt Blue
*/

Have someone a function for SQL Server that given a pattern and a value could return a normalized value

I'm programming a function in SQL 2008R2 that i could give it some parameters like a value varchar, a pattern varchar, a separator char and a filler also char. Then I would like to give the value '22687' with the patter '000.000.000.000', a separator '.' and the filler would be '0', then i would like to expect the function will return '000.000.022.687', does any one have a function already done that can do this?
Something like this:
DECLARE #valor VARCHAR(30)
DECLARE #formato VARCHAR(30)
DECLARE #separador CHAR(1)
DECLARE #rellenarcon CHAR(1)
SELECT #valor = '22959'
SELECT #formato = '000.000.000.000'
SELECT #separador = '.'
SELECT #rellenarcon = '0'
DECLARE #n INTEGER
DECLARE #m INTEGER
DECLARE #i INTEGER
DECLARE #j INTEGER
SELECT #n = LEN(#formato)
SELECT #m = LEN(#valor)
SELECT #i = 1
SELECT #j = 1
DECLARE #res2 varchar(30)
SELECT #res2 = ''
SELECT #valor = REVERSE(#valor)
WHILE #i<=#n
BEGIN
if SUBSTRING(#formato,#i,1) <> #separador
begin
IF #j<=#m
BEGIN
SELECT #res2 = #res2 + SUBSTRING(#valor,#j,1)
SELECT #i=#i+1
SELECT #j=#j+1
END
ELSE
BEGIN
SELECT #res2 = #res2 + #rellenarcon
SELECT #i=#i+1
END
end
else
BEGIN
SELECT #res2 = #res2 + #separador
SELECT #i=#i+1
END
END
print reverse(#res2)
Is a crossover code from java to tsql, the original code in java is:
public static String formatear(String valor, String formato, char separator,
char fillWith, Map<Integer, String> params) {
int n = formato.length() - 1;
int m = valor.length() - 1;
int i = n;
int j = m;
StringBuilder res = new StringBuilder(formato);
for(; i >= 0; i--) {
if(res.charAt(i) != separator) {
if(j >= 0) {
res.deleteCharAt(i);
res.insert(i, valor.charAt(j--));
} else {
res.deleteCharAt(i);
res.insert(i, fillWith);
}
}
}
if(params != null) {
Set<Integer> keys = params.keySet();
for(Integer key : keys) {
i = key;
res.deleteCharAt(i);
res.insert(i, params.get(key));
}
}
return res.toString();
}
The following assumes well-formed inputs, e.g. the value is not longer than the pattern.
declare #Pattern as VarChar(64) = '000.000.000.000';
declare #Fill as Char = '0';
declare #Value as VarChar(64) = '22687';
declare #False as Bit = 0;
declare #True as Bit = 1;
with Gargoyle as (
select #Pattern as Pattern, #Value as Value, Cast( '' as VarChar(64) ) as Buffer,
case when Right( #Pattern, 1 ) = #Fill then #True else #False end as Fill
union all
select
-- Always consume a character from the pattern.
Left( Pattern, Len( Pattern ) - 1 ),
-- Consume a character from the value if the pattern contains fill at the current position.
case
when Fill = #True and Value != '' then Left( Value, Len( Value ) - 1 )
else Value end,
-- Add the correct character to the buffer.
Cast( case when Fill = #True and Value != '' then Right( Value, 1 ) else Right( Pattern, 1 ) end + Buffer as VarChar(64) ),
-- Check the next pattern character for fill.
case
when Len( Pattern ) = 1 then #False
when Substring( Pattern, Len( Pattern ) - 1, 1 ) = #Fill then #True
else #False end
from Gargoyle
where Pattern != ''
)
select Buffer
from Gargoyle
where Pattern = '';
Or, as a function:
create function dbo.PatternFill( #Pattern as VarChar(64), #Fill as Char, #Value as VarChar(64) )
returns VarChar(64)
as
begin
declare #Buffer as VarChar(64) = ''
declare #PatternChar as Char = Right( #Pattern, 1 )
declare #ValueChar as Char = Right( #Value, 1 )
while #Pattern != ''
begin
if #PatternChar = #Fill and #ValueChar != ''
begin
-- Replace a fill character with a value character.
select #Buffer = #ValueChar + #Buffer
if Len( #Value ) > 1
select #Value = Left( #Value, Len( #Value ) - 1 ), #ValueChar = Right( #Value, 1 )
else
select #ValueChar = '', #Value = ''
end
else
begin
-- Copy the pattern character.
select #Buffer = #PatternChar + #Buffer
end
if Len( #Pattern ) > 1
select #Pattern = Left( #Pattern, Len( #Pattern ) - 1 ), #PatternChar = Right( #Pattern, 1 )
else
select #PatternChar = '', #Pattern = ''
end
return #Buffer
end
go
declare #Result as VarChar(64)
declare #Count as Int = 1000000
declare #Start as DateTime = GetDate()
while #Count > 0
select #Result = dbo.PatternFill( '000.000.000.000', '0', '22687' ), #Count = #Count - 1
select #Result as [Result], DateDiff( ms, #Start, GetDate() ) as [Total ms]
1,000,000 iterations on my notebook took 151,656ms, but it's busy BOINCing. That's simple timing with no correction for the time consumed by an empty loop or calling an empty function.
This query will do it:
;with cteZeroPadded(Num) as
(
Select Right('000000000000' + '22687', 12)
)
,cteSplit as
(
Select SUBSTRING(Num, 1, 3) Col1
,SUBSTRING(Num, 4, 3) Col2
,SUBSTRING(Num, 7, 3) Col3
,SUBSTRING(Num, 10, 3) Col4
From cteZeroPadded
)
Select Col1 + '.' + Col2 + '.' + Col3 + '.' + Col4
From cteSplit
In SQLServer2005+ you can use option with recursive CTE
DECLARE #valor varchar(30) = '22959',
#formato varchar(30) = '000000000000000',
#text varchar(30),
#result varchar(30) = N''
SET #text = REVERSE(RIGHT(#formato + #valor, 15))
;WITH cte AS
(
SELECT 1 AS Number, SUBSTRING(#text, 1, 1) AS Num
UNION ALL
SELECT c.Number + 1,
CASE WHEN c.Number IN(3, 7, 11) THEN '.' ELSE
SUBSTRING(#text, CASE WHEN c.Number > 11 THEN c.Number - 2
WHEN c.Number > 7 THEN c.Number - 1
WHEN c.Number > 3 THEN c.Number
ELSE c.Number + 1
END, 1)
END
FROM cte c
WHERE Number < LEN(#text)
)
SELECT #result += c.Num
FROM cte c
ORDER BY Number DESC
SELECT #result
See demo on SQLFiddle

How to use a TRIM function in SQL Server

I cannot get this TRIM code to work
SELECT
dbo.COL_V_Cost_GEMS_Detail.TNG_SYS_NR AS [EHP Code],
dbo.COL_TBL_VCOURSE.TNG_NA AS [Course Title],
LTRIM(RTRIM(FCT_TYP_CD)& ') AND (' & LTRIM(RTRIM(DEP_TYP_ID) & ')' AS [Course Owner]
You are missing two closing parentheses...and I am not sure an ampersand works as a string concatenation operator. Try '+'
SELECT dbo.COL_V_Cost_GEMS_Detail.TNG_SYS_NR AS [EHP Code],
dbo.COL_TBL_VCOURSE.TNG_NA AS [Course Title],
LTRIM(RTRIM(FCT_TYP_CD)) + ') AND (' + LTRIM(RTRIM(DEP_TYP_ID)) + ')' AS [Course Owner]
TRIM all SPACE's TAB's and ENTER's:
DECLARE #Str VARCHAR(MAX) = '
[ Foo ]
'
DECLARE #NewStr VARCHAR(MAX) = ''
DECLARE #WhiteChars VARCHAR(4) =
CHAR(13) + CHAR(10) -- ENTER
+ CHAR(9) -- TAB
+ ' ' -- SPACE
;WITH Split(Chr, Pos) AS (
SELECT
SUBSTRING(#Str, 1, 1) AS Chr
, 1 AS Pos
UNION ALL
SELECT
SUBSTRING(#Str, Pos, 1) AS Chr
, Pos + 1 AS Pos
FROM Split
WHERE Pos <= LEN(#Str)
)
SELECT #NewStr = #NewStr + Chr
FROM Split
WHERE
Pos >= (
SELECT MIN(Pos)
FROM Split
WHERE CHARINDEX(Chr, #WhiteChars) = 0
)
AND Pos <= (
SELECT MAX(Pos)
FROM Split
WHERE CHARINDEX(Chr, #WhiteChars) = 0
)
SELECT '"' + #NewStr + '"'
As Function
CREATE FUNCTION StrTrim(#Str VARCHAR(MAX)) RETURNS VARCHAR(MAX) BEGIN
DECLARE #NewStr VARCHAR(MAX) = NULL
IF (#Str IS NOT NULL) BEGIN
SET #NewStr = ''
DECLARE #WhiteChars VARCHAR(4) =
CHAR(13) + CHAR(10) -- ENTER
+ CHAR(9) -- TAB
+ ' ' -- SPACE
IF (#Str LIKE ('%[' + #WhiteChars + ']%')) BEGIN
;WITH Split(Chr, Pos) AS (
SELECT
SUBSTRING(#Str, 1, 1) AS Chr
, 1 AS Pos
UNION ALL
SELECT
SUBSTRING(#Str, Pos, 1) AS Chr
, Pos + 1 AS Pos
FROM Split
WHERE Pos <= LEN(#Str)
)
SELECT #NewStr = #NewStr + Chr
FROM Split
WHERE
Pos >= (
SELECT MIN(Pos)
FROM Split
WHERE CHARINDEX(Chr, #WhiteChars) = 0
)
AND Pos <= (
SELECT MAX(Pos)
FROM Split
WHERE CHARINDEX(Chr, #WhiteChars) = 0
)
END
END
RETURN #NewStr
END
Example
-- Test
DECLARE #Str VARCHAR(MAX) = '
[ Foo ]
'
SELECT 'Str', '"' + dbo.StrTrim(#Str) + '"'
UNION SELECT 'EMPTY', '"' + dbo.StrTrim('') + '"'
UNION SELECT 'EMTPY', '"' + dbo.StrTrim(' ') + '"'
UNION SELECT 'NULL', '"' + dbo.StrTrim(NULL) + '"'
Result
+-------+----------------+
| Test | Result |
+-------+----------------+
| EMPTY | "" |
| EMTPY | "" |
| NULL | NULL |
| Str | "[ Foo ]" |
+-------+----------------+
LTRIM(RTRIM(FCT_TYP_CD)) & ') AND (' & LTRIM(RTRIM(DEP_TYP_ID)) & ')'
I think you're missing a ) on both of the trims. Some SQL versions support just TRIM which does both L and R trims...
Example:
DECLARE #Str NVARCHAR(MAX) = N'
foo bar
Foo Bar
'
PRINT '[' + #Str + ']'
DECLARE #StrPrv NVARCHAR(MAX) = N''
WHILE ((#StrPrv <> #Str) AND (#Str IS NOT NULL)) BEGIN
SET #StrPrv = #Str
-- Beginning
IF EXISTS (SELECT 1 WHERE #Str LIKE '[' + CHAR(13) + CHAR(10) + CHAR(9) + ']%')
SET #Str = LTRIM(RIGHT(#Str, LEN(#Str) - 1))
-- Ending
IF EXISTS (SELECT 1 WHERE #Str LIKE '%[' + CHAR(13) + CHAR(10) + CHAR(9) + ']')
SET #Str = RTRIM(LEFT(#Str, LEN(#Str) - 1))
END
PRINT '[' + #Str + ']'
Result
[
foo bar
Foo Bar
]
[foo bar
Foo Bar]
Using fnTrim
Source: https://github.com/reduardo7/fnTrim
SELECT dbo.fnTrim(colName)

Split a VARCHAR in DB2 to retrieve a value inside

I have a VARCHAR column that contains 5 informations (2 CHAR(3) and 3 TIMESTAMP) separated with '$'.
CREATE TABLE MYTABLE (
COL VARCHAR(256) NOT NULL
);
INSERT INTO MYTABLE
VALUES
( 'AAA$000$2009-10-10 10:50:00$null$null$null' ),
( 'AAB$020$2007-04-10 10:50:00$null$null$null' ),
( 'AAC$780$null$2007-04-10 10:50:00$2009-04-10 10:50:00$null' )
;
I would like to extract the 4th field ...
'AAA$000$2009-10-10 10:50:00$null$null$null'
^^^^ this field
... to have something like
SELECT SPLIT(COL, '$', 4) FROM MYTABLE
1
-----
'null'
'null'
'2009-04-10 10:50:00'
I'm searching, in that order :
A DB2 build-in string function
An embeddable statement such as SUBSTR(COL, POSSTR(COL)+1)...
An user defined function that behaves like SPLIT
Precision : Yes, I do know that it's not a good idea to have such columns...
CREATE FUNCTION split(pos INT, delimeter CHAR, string VARCHAR(255))
LANGUAGE SQL
RETURNS VARCHAR(255)
DETERMINISTIC NO EXTERNAL ACTION
BEGIN ATOMIC
DECLARE x INT;
DECLARE s INT;
DECLARE e INT;
SET x = 0;
SET s = 0;
SET e = 0;
WHILE (x < pos) DO
SET s = locate(delimeter, string, s + 1);
IF s = 0 THEN
RETURN NULL;
END IF;
SET x = x + 1;
END WHILE;
SET e = locate(delimeter, string, s + 1);
IF s >= e THEN
SET e = LENGTH(string) + 1;
END IF;
RETURN SUBSTR(string, s + 1, e - s -1);
END!
Usage:
SELECT split(3,'$',col) from mytable; -- or
SELECT split(0,'-', 'first-second-third') from sysibm.sysdummy1;
SELECT split(0,'-', 'returns this') from sysibm.sysdummy1;
SELECT split(1,'-', 'returns null') from sysibm.sysdummy1;
I am sure there is a better way to write this, but here is 1 (SQL) solution for the simple case given. It could be rewritten as a stored procedure to look for any arbitrary string. There may also be some 3rd party tools/extensions to help out w/ the split you want...
select
locate('$', col, (locate('$',col, (locate('$',col) +1))) + 1) as poss3rdDollarSign, -- position of 3rd dollar sign
locate('$', col, (locate('$', col, (locate('$',col, (locate('$',col) +1))) + 1)) + 1) as poss4thDollarSign, -- position of 4th dollar sign
(locate('$', col, (locate('$', col, (locate('$',col, (locate('$',col) +1))) + 1)) + 1)) -
(locate('$', col, (locate('$',col, (locate('$',col) +1))) + 1)) - 1 as stringLength,-- length of string between 3rd and 4th dollar sign
substr(col, locate('$', col, (locate('$',col, (locate('$',col) +1))) + 1) + 1, (locate('$', col, (locate('$', col, (locate('$',col, (locate('$',col) +1))) + 1)) + 1)) -
(locate('$', col, (locate('$',col, (locate('$',col) +1))) + 1)) - 1) as string
from mytable
try this, it works!
CREATE FUNCTION SPLIT( P_1 VARCHAR(3200),
P_2 VARCHAR(200))
RETURNS TABLE(P_LIST VARCHAR(3200))
SPECIFIC SPLIT
LANGUAGE SQL
MODIFIES SQL DATA
NO EXTERNAL ACTION
F1: BEGIN
return
with source(str, del) as
(select p_1, p_2 from sysibm.sysdummy1),
target(str, del) as
(select source.str, source.del from source
where length(source.str) > 0
union all
select
(case when (instr(target.str, target.del) > 0)
then substr(target.str,
instr(target.str, target.del)+1,
length(target.str)-instr(target.str, target.del)) else null end),
(case when (instr(target.str, target.del) > 0)
then target.del else null end)
from target
where length(target.str) > 0
)
select str from target
where str is not null;
END
If your DB2's version can do it, you can use then LOCATE_IN_STRING function for to found position of your separator. The LOCATE_IN_STRING function returns the starting position of a string and enable you to choice the Nth instance. You can found documentation of this function here
For your example, you can use this code :
select
substring(col, LOCATE_IN_STRING(col, '$', 1, 3), LOCATE_IN_STRING(col, '$', 1, 4) - LOCATE_IN_STRING(col, '$', 1, 3))
from MYTABLE
substr(e.data,1,13) as NNSS,
substring(e.data, LOCATE_IN_STRING(e.data, ';', 1, 1, CODEUNITS32)+1, (LOCATE_IN_STRING(e.data, ';', 1, 2, CODEUNITS32) - LOCATE_IN_STRING(e.data, ';', 1, 1, CODEUNITS32)-1) ) as Name,
substring(e.data, LOCATE_IN_STRING(e.data, ';', 1, 2, CODEUNITS32)+1, (LOCATE_IN_STRING(e.data, ';', 1, 3, CODEUNITS32) - LOCATE_IN_STRING(e.data, ';', 1, 2, CODEUNITS32)-1) ) as Vorname,
substring(e.data, LOCATE_IN_STRING(e.data, ';', 1, 3, CODEUNITS32)+1, (LOCATE_IN_STRING(e.data, ';', 1, 4, CODEUNITS32) - LOCATE_IN_STRING(e.data, ';', 1, 3, CODEUNITS32)-1) ) as Grund