Split a VARCHAR in DB2 to retrieve a value inside - sql

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

Related

Year number range formatting

I have these year number ranges
1993-1997
1923-1935
1998-2015
I'm trying to produce this shortened version of these year ranges.
1993-7
1923-35
1998-2015
So far my query looks like this. But its not working on the 2nd and 3rd samples, 1923-1935, and 1998-2015.
declare #bookyear varchar(50)
declare #year1 char(4)
declare #year2 char(4)
set #bookyear = '1993-1997'
set #year1 = substring(#bookyear, 1, charindex('-', #bookyear)-1)
set #year2 = substring(#bookyear, charindex('-', #bookyear) + 1, len(#bookyear))
select cast(#year1 as varchar(50)) + '-'+ substring(#year2, 4, 1)
Note: Year is always in 4 digits.
I am assuming you want a single digit year if its within the same decade, and double digit year when its a different decade. In which case use a case statement to compare the decade component and then display the appropriate number of digits e.g.
declare #bookyear varchar(50), #year1 char(4), #year2 char(4);
set #bookyear = '1993-1997';
set #year1 = substring(#bookyear, 1, charindex('-', #bookyear)-1);
set #year2 = substring(#bookyear, charindex('-', #bookyear) + 1, len(#bookyear));
select cast(#year1 as varchar(50)) + '-'
+ case when substring(#Year1,1,3) = substring(#Year2,1,3) then substring(#year2, 4, 1)
when substring(#Year1,1,2) = substring(#Year2,1,2) then substring(#year2, 3, 2)
else substring(#year2, 1, 4) end;
Assuming your input strings will always have same length i.e. 9 characters
drop table if exists t
create table t (d varchar(9))
insert into t values
('1993-1997')
,('1923-1935')
,('1998-2015')
,('2095-2115')
SQLFIDDLE
select d
, case
when LEFT(d, 3) = LEFT(RIGHT(d , 4), 3) then LEFT(d, 5) + RIGHT(d, 1)
when LEFT(d, 2) = LEFT(RIGHT(d , 4), 2) then LEFT(d, 5) + RIGHT(d, 2)
when LEFT(d, 1) = LEFT(RIGHT(d , 4), 1) then LEFT(d, 5) + RIGHT(d, 3)
ELSE d
end
FROM t
There are actually 2 ways to address this.
Looking for the same from the right.
Or looking for differences from the left.
Example snippet:
declare #bookyear varchar(9);
set #bookyear = '1993-1997';
--
-- looking for different digits from left to right
--
set #bookyear = left(#bookyear,5) +
case
when left(#bookyear,1) != substring(#bookyear,6,1) then right(#bookyear,4)
when left(#bookyear,2) != substring(#bookyear,6,2) then right(#bookyear,3)
when left(#bookyear,3) != substring(#bookyear,6,3) then right(#bookyear,2)
else right(#bookyear,1)
end;
select #bookyear as bookyear1;
-- reset variable
set #bookyear = '1993-1997';
--
-- looking for same digits from right to left
--
set #bookyear = left(#bookyear,5) +
case
when left(#bookyear,3) = substring(#bookyear,6,3) then substring(#bookyear,9,1)
when left(#bookyear,2) = substring(#bookyear,6,2) then substring(#bookyear,8,2)
when left(#bookyear,1) = substring(#bookyear,6,1) then substring(#bookyear,7,3)
else substring(#bookyear,6,4)
end;
select #bookyear as bookyear2;
A test snippet using a table variable:
declare #Test table (bookyear varchar(9));
insert into #Test (bookyear) values
('1993-1997'),
('1923-1935'),
('1998-2015'),
('2095-2115');
select
bookyear,
left(bookyear,5) +
case
when left(bookyear,1) != substring(bookyear,6,1) then right(bookyear,4)
when left(bookyear,2) != substring(bookyear,6,2) then right(bookyear,3)
when left(bookyear,3) != substring(bookyear,6,3) then right(bookyear,2)
else right(bookyear,1)
end as bookyear1,
left(bookyear,5) +
case
when left(bookyear,3) = substring(bookyear,6,3) then substring(bookyear,9,1)
when left(bookyear,2) = substring(bookyear,6,2) then substring(bookyear,8,2)
when left(bookyear,1) = substring(bookyear,6,1) then substring(bookyear,7,3)
else substring(bookyear,6,4)
end as bookyear2
from #Test;
Returns:
bookyear bookyear1 bookyear2
1993-1997 1993-7 1993-7
1923-1935 1923-35 1923-35
1998-2015 1998-2015 1998-2015
2095-2115 2095-115 2095-115
A test on rextester here

How can I get a list of substrings from a column in SQL?

I have a table of products, and each product has a Description field. Some of these Descriptions contain a pdf name, such as "productmanual.pdf". I need to find out all of the pdf names that exist in my product descriptions. I was able to get all of the Descriptions that contain a pdf as such:
SELECT * FROM [Product] WHERE [Description] LIKE '%.pdf%'
but the descriptions are very long and I have a over a thousand products returned. I need to parse out just the PDF titles and return a list of those. I know that each title begins after a "<" and ends with ".pdf", in C# I would parse them like this:
var pdfIndex = description.IndexOf(".pdf");
if ((pdfIndex) > -1){
var pdfEnd = pdfIndex + 3;
var stringBefore = description.Substring(0, pdfIndex);
var pdfStart = stringBefore.LastIndexOf(">");
var pdfLength = pdfEnd - pdfStart;
var pdfString = description.Substring(pdfStart + 1, pdfLength);
}
How can I accomplish this in SQL so that I can return a list all the (distinct) PDF titles?
This would work assuming all the descriptions have only one . followed by pdf.
SELECT substring([Description],
charindex('<', [Description]) + 1,
charindex('.', [Description]) - charindex('<', [Description]) - 1) as filename
FROM [Product]
WHERE [Description] LIKE '%.pdf%'
and charindex('.', [Description]) > 0 and charindex('>', [Description]) > 0
Edit:
SQL Fiddle
SELECT reverse(substring(reverse([Description]),
charindex('.', reverse([Description])) + 1,
charindex('>', reverse([Description]))
- charindex('.', reverse([Description])) - 1)
) as filename
FROM [Product]
WHERE [Description] LIKE '%.pdf%'
and charindex('.', [Description]) > 0 and charindex('>', [Description]) > 0
Perhaps a recursive CTE will work for you.
DECLARE #f VARCHAR(255)
SET #f = '>1.pdf test >2.pdf bob >3.pdf foo >4.pdf'
; WITH cte AS (
SELECT
CONVERT(VARCHAR(255), SUBSTRING(
#f,
PATINDEX('%>%.pdf%', #f) + 1,
(PATINDEX('%.pdf%', #f) + 4) - (PATINDEX('%>%.pdf%', #f) + 1)
)) AS PDFName,
CONVERT(VARCHAR(255), STUFF(
#f, PATINDEX('%>%.pdf%', #f),
(PATINDEX('%.pdf%', #f) + 4) - (PATINDEX('%>%.pdf%', #f)),
''
)) AS strWhatsLeft
UNION ALL
SELECT
CONVERT(VARCHAR(255), SUBSTRING(
strWhatsLeft,
PATINDEX('%>%.pdf%', strWhatsLeft) + 1,
(PATINDEX('%.pdf%', strWhatsLeft) + 4) - (PATINDEX('%>%.pdf%', strWhatsLeft) + 1)
)) AS PDFName,
CONVERT(VARCHAR(255), STUFF(
strWhatsLeft, PATINDEX('%>%.pdf%', strWhatsLeft),
(PATINDEX('%.pdf%', strWhatsLeft) + 4) - (PATINDEX('%>%.pdf%', strWhatsLeft)),
''
)) AS strWhatsLeft
FROM cte
WHERE strWhatsLeft LIKE '%.pdf%'
)
SELECT * FROM cte
With the given test string, it will return
PDFName strWhatsLeft
1.pdf test >2.pdf bob >3.pdf foo >4.pdf
2.pdf test bob >3.pdf foo >4.pdf
3.pdf test bob foo >4.pdf
4.pdf test bob foo

String modification/filtering in MS SQL SERVER 2014

Below is the input string:
Declare #inputvalue varchar(max)
Set #inputvalue = 'Brussels sprout is one type of green veggie you should simply steam away without a doubt. Steaming your sprout will only enable a build up on your immunity.
Raw Sproutbrussels or Brussels still has the cholesterol-lowering ability but as much as steamed brussels sprout. Likewise, here are some of the other health benefits of steaming brusselssprout ".
I like sprout, not Brussels.
Requirement:
Need to remove words sprout and Brussels if they are not adjacent to each other i.e.
1) Need to remove word "sprout" if "Brussels" is not present adjacent to it (left or right,with or without space)
2) Need to remove word "Brussels" if "sprout" is not present adjacent to it (left or right,with or without space)
3) The words are to be considered adjacent even if there are multiple spaces in between them.
Brussels sprout - This should not be removed from the string.
Brusselssprout - This should not be removed from the string. Sproutbrussels - This should not be removed from the string.
The words highlighted in Bold Italics are to be removed and the words highlighted in Bold are not to be removed.
Note: The whole input string can be different/may contain no space at all between words.Thus it can't be done by splitting with space or any other delimiter.
Expected output string:
"Brussels sprout is one type of green veggie you should simply steam away without a doubt. Steaming your will only enable a build up on your immunity. Raw Sproutbrussels or still has the cholesterol-lowering ability but as much as steamed brussels sprout. Likewise, here are some of the other health benefits of steaming brusselssprout".
I like , not"
This is probably not very efficeint but it works:
Declare #inputvalue varchar(max)
Set #inputvalue = 'Brussels sprout is one type of green veggie you should simply steam away without a doubt. Steaming your sprout will only enable a build up on your immunity. Raw Sproutbrussels or Brussels still has the cholesterol-lowering ability but as much as steamed brussels sprout. Likewise, here are some of the other health benefits of steaming brusselssprout ".
I like sprout, not Brussels.';
declare #word1 nvarchar(100) = 'brussels';
declare #word2 nvarchar(100) = 'sprout ';
with W1(id, pos, val) as (
Select 1, CHARINDEX(#word1, #inputvalue, 1)+1,
Case When
right(rtrim(left(#inputvalue, CHARINDEX(#word1, #inputvalue, 1)-1)), len(#word2)) = #word2
or left(ltrim(right(#inputvalue, len(#inputvalue) - CHARINDEX(#word1, #inputvalue, 1) - len(#word1) +1)), len(#word2)) = #word2
then #inputvalue else
left(#inputvalue, CHARINDEX(#word1, #inputvalue, 1) - 1)
end
Union All
Select id+1, CHARINDEX(#word1, val, pos)+1,
Case When
right(rtrim(left(val, CHARINDEX(#word1, val, pos)-1)), len(#word2)) = #word2
or left(ltrim(right(val, len(val) - CHARINDEX(#word1, val, pos) - len(#word1) +1)), len(#word2)) = #word2
then val else
left(val, CHARINDEX(#word1, val, pos) - 1)
+ right(val, len(val) - len(#word1) - CHARINDEX(#word1, val, pos) + 1)
end
From w1
Where CHARINDEX(#word1, val, pos) > 0
), W2(id, pos, val) as (
Select 1, CHARINDEX(#word2, val, 1)+1,
Case When
right(rtrim(left(val, CHARINDEX(#word2, val, 1)-1)), len(#word1)) = #word1
or left(ltrim(right(val, len(val) - CHARINDEX(#word2, val, 1) - len(#word2) +1)), len(#word1)) = #word1
then val else
left(val, CHARINDEX(#word2, val, 1) - 1)
end
From (
Select val From w1 Where id in (Select max(id) From w1)
) as w
Union All
Select id+1, CHARINDEX(#word2, val, pos)+1,
Case When
right(rtrim(left(val, CHARINDEX(#word2, val, pos)-1)), len(#word1)) = #word1
or left(ltrim(right(val, len(val) - CHARINDEX(#word2, val, pos) - len(#word2) +1)), len(#word1)) = #word1
then val else
left(val, CHARINDEX(#word2, val, pos) - 1)
+ right(val, len(val) - len(#word2) - CHARINDEX(#word2, val, pos) + 1)
end
From w2
Where CHARINDEX(#word2, val, pos) > 0
)
Select val From w2 Where id in (Select max(id) From w2)
Option (maxrecursion 0)
CTE W1 could also be put in a function:
with W1(id, pos, val) as (
Select 1, CHARINDEX(#word1, #inputvalue, 1)+1,
Case When
right(rtrim(left(#inputvalue, CHARINDEX(#word1, #inputvalue, 1)-1)), len(#word2)) = #word2
or left(ltrim(right(#inputvalue, len(#inputvalue) - CHARINDEX(#word1, #inputvalue, 1) - len(#word1) +1)), len(#word2)) = #word2
then #inputvalue else
left(#inputvalue, CHARINDEX(#word1, #inputvalue, 1) - 1)
end
Union All
Select id+1, CHARINDEX(#word1, val, pos)+1,
Case When
right(rtrim(left(val, CHARINDEX(#word1, val, pos)-1)), len(#word2)) = #word2
or left(ltrim(right(val, len(val) - CHARINDEX(#word1, val, pos) - len(#word1) +1)), len(#word2)) = #word2
then val else
left(val, CHARINDEX(#word1, val, pos) - 1)
+ right(val, len(val) - len(#word1) - CHARINDEX(#word1, val, pos) + 1)
end
From w1
Where CHARINDEX(#word1, val, pos) > 0
)
Select val From w1 Where id in (Select max(id) From w1)
And then you only have call it twice:
Set #newvalue = replaceWords(replaceWords(#inputvalue, 'brussels', 'sprout'), 'sprout', 'brussels')
Note that I love Brussel sprouts!!! :)

How to reduce code redundancy in this query?

I've got these two calculated fields in my query:
CASE
WHEN TRN.TOTAL_VALUE_T = 0 THEN '00000000000'
ELSE
CASE
WHEN CC.PORT_CURRENCY1 = 'CAD' THEN RIGHT('00000000000' + REPLACE(LTRIM(STR(TRN.TOTAL_VALUE_T + TRN.TAXES, 11, 2)), '.', ''), 11)
WHEN CC.PORT_CURRENCY1 = 'USD' THEN RIGHT('00000000000' + REPLACE(LTRIM(STR((TRN.TOTAL_VALUE_T + TRN.TAXES) * DBO.fnGetExchangeRate(TRN.TRADE_DATE, 'USD', 'CAD'), 11, 2)), '.', ''), 11)
END
END AS TX_PRIX_CAD,
CASE
WHEN T.TRANS_TYPE in ('ADD','DELIV') THEN
CASE
WHEN (SELECT currencyTitle FROM DBO.GetPrice(TRN.SEC_NO, TRN.TRADE_DATE)) = '' THEN TX_PRIX_CAD
END
END AS TX_MNT_BRUT
You see, the second field (TX_MNT_BRUT) must equal the first one (TX_PRIX_CAD) under certain conditions.
Problem when doing this is SQL is telling me that TX_PRIX_CAD is not a valid column name, I guess because the column name does not exist physically in the table.
How could I achieve this without repeating the code in TX_PRIX_CAD in TX_MNT_BRUT ?
Thanks.
You probably need to nest the select statement e.g.
SELECT <other_columns>, TX_PRIX_CAD,
CASE
WHEN T.TRANS_TYPE in ('ADD','DELIV') THEN
CASE
WHEN (SELECT currencyTitle FROM DBO.GetPrice(TRN.SEC_NO, TRN.TRADE_DATE)) = '' THEN TX_PRIX_CAD
END
END AS TX_MNT_BRUT
FROM (
SELECT
<other columns>,
CASE
WHEN TRN.TOTAL_VALUE_T = 0 THEN '00000000000'
ELSE
CASE
WHEN CC.PORT_CURRENCY1 = 'CAD' THEN RIGHT('00000000000' + REPLACE(LTRIM(STR(TRN.TOTAL_VALUE_T + TRN.TAXES, 11, 2)), '.', ''), 11)
WHEN CC.PORT_CURRENCY1 = 'USD' THEN RIGHT('00000000000' + REPLACE(LTRIM(STR((TRN.TOTAL_VALUE_T + TRN.TAXES) * DBO.fnGetExchangeRate(TRN.TRADE_DATE, 'USD', 'CAD'), 11, 2)), '.', ''), 11)
END
END
END AS TX_PRIX_CAD,
T.TRANS_TYPE
FROM <table_name>
)

Find an unmatched curly brackets in a string in 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.