SQL trying to replace middle characters with * - sql

I am trying to replace SQL results with all the middle values with asterix, *. All results are words. I am using SSMS.
The words that are 4-5 letters, it should only show 1 letter in the beginning, one to the end.
6 letters and more, it it should only show 2 letter in the beginning, 2 letters in the end.
1-3 letters, no replacement.
For example:
(I am now using - instead of * so it does not make the text bold).
"Banana" 6 letters should become ba--na
"False" 5 letters should become F---e
"a" stays the same
"Selin is a vegetable and banana is a fruit" becomes "S---n is a ve-----le and ba--na is a f---t."
What I have done so far, is to make this for emails, after the #. But now I want it to happen with every word of the result.
What I've done:
DECLARE #String VARCHAR(100) = 'sample#gmail.com'
SELECT STUFF(STUFF(#STring,
CHARINDEX('#',#String)+2,
(CHARINDEX('.',#String, CHARINDEX('#',#String))-CHARINDEX('#',#String)-3),
REPLICATE('*',CHARINDEX('.',#String, CHARINDEX('#',#String))-CHARINDEX('#',#String)))
,2
,CHARINDEX('#',#String)-3
,REPLICATE('*',CHARINDEX('#',#String)-3))```
With result s----e#g------l.com
instead of -
And I tried the mask method
Select
--select first character from Email and use replicate
SUBSTRING(Sxolia,1,1) + REPLICATE('*',5)+
--function to put asterisks
SUBSTRING(Sxolia,CHARINDEX('#',Sxolia),len(Sxolia)-CHARINDEX('#',Sxolia)+1)
--at this statement i select this part #gmail,com and to first part to become like this A*****#gmail.com
as Emailmask
From [mytable]
With result
B***** Bana is a fruit
And
declare #str nvarchar(max)
select #str = '123456'
select '****' + substring(#str, 5, len(#str) - 3)
Result: ****56
Not what I am looking for.
How should I look into this?

If I had to deal with this in SQL Server I'd operate on each word as a row, however using string_split is not (currently) an option since it does not guarantee ordering.
The following uses json to split the string as an array and provides a key value for ordering, which allows the words to be aggregated in the correct order:
select t.Sentence,
String_Agg( masked, ' ') within group(order by seq) Masked
from t
cross apply (
select seq, [value] word,
case
when l<=3 then [value]
when l<=5 then Stuff([value],2,l-2,Replicate('*',l-2))
else
Stuff([value],3,l-4,Replicate('*',l-4))
end Masked
from (
select j.[value], 1 + Convert(tinyint,j.[key]) Seq
from OpenJson(Concat('["',replace(t.Sentence,' ', '","'),'"]')) j
)w
cross apply (values(Len([value])))x(l)
)w
group by t.Sentence;
See working demo
Result:

I'm not sure how e-mail fits into all this because you're asking for word masks, so I'm going to assume you actually want this. Use divide and conquer to implement this, so first implement an expression that would do this for simplest cases (e.g. single words). Then if you need it for e-mails, just split the e-mails however you see fit and then apply the same expression.
The expression itself is rather simple:
SELECT *
FROM (VALUES
('banana'),
('selin'),
('vegetable')
) words(word)
CROSS
APPLY (SELECT CASE
WHEN ln BETWEEN 4 AND 5
THEN LEFT(word, 1) + REPLICATE('*', ln-2) + RIGHT(word, 1)
WHEN ln >= 6
THEN LEFT(word, 2) + REPLICATE('*', ln-4) + RIGHT(word, 2)
ELSE word
END as result
FROM (VALUES (LEN(words.word))) x(ln)
) calc
This already provides the expected result. You could define a function out of this, if you have the permissions, and use it like so:
SELECT *
FROM (VALUES
('banana'),
('selin'),
('vegetable')
) words(word)
CROSS
APPLY fnMaskWord(word)
Here's a working demo on dbfiddle, it includes the statement to create the function.

Expanding on a few answers:
select case when len(#String) <= 3 then #String
when len(#String) > 3 AND len(#String) <= 5 then
substring(#String, 1, 2) +
REPLICATE('*', Len(#String) - 2) +
substring(#String, Len(#String) - 1, 2)
when len(#String) >= 6 then
substring(#String, 1, 2) +
REPLICATE('*', Len(#String) - 2) +
substring(#String, Len(#String) - 1, 2)
else 'unrecognized length!'
If the length of the string is less than or equal to 3, return the string.
If the length of the string is more than 3 and less than or equal to 5 then create a substring starting at position 1, then replicate * by the length of the string -2 and finally add another substring -1 from the end of the string.
Similar for if the result is over 6 characters.
Else unrecognized length!
Hope this helps understand what's going on!

Maybe this can help
declare #t table (word varchar(50))
insert into #t values ('banana'), ('selin'), ('vegetable')
select case when len(t.word) < 3 then t.word
else left(t.word, 1) + -- take first char from left
replicate('*', Len(t.word) - 2) + -- fill middle with *
right(t.word, 1) -- take last char from right
end
from #t t
this returns
COLUMN1
b****a
s***l
v*******e
If you want to keep 2 chars left and right when the len > 5 then maybe this
select case when len(t.word) < 3 then t.word
when len(t.word) < 6 then
left(t.word, 1) +
replicate('*', len(t.word) - 2) +
right(t.word, 1)
else left(t.word, 2) +
replicate('*', len(t.word) - 4) +
right(t.word, 2)
end
from #t t
The result
COLUMN1
ba**na
s***l
ve*****le
EDIT: What if there is a whole sentence ?
Well then we first split the sentence in words,
and then concat the individual words back together while putting the ** in them
declare #t table (word varchar(50))
insert into #t values ('banana'), ('selin'), ('vegetable'), ('Banana is a fruit')
select t.word,
-- put the words back togheter into the sentence, and ** them while we are at it
( select string_agg(case when len(value) < 3 then value
when len(value) < 6 then
left(value, 1) +
replicate('*', len(value) - 2) +
right(value, 1)
else left(value, 2) +
replicate('*', len(value) - 4) +
right(value, 2)
end,
' ')
)
from #t t
cross apply string_split(t.word, ' ') s -- split the sentence into words
group by t.word
the result is
word COLUMN1
---- -------
banana ba**na
Banana is a fruit Ba**na is a f***t
selin s***n
vegetable ve*****le

Related

SQL Get string between second and third underscore

I need to extract a certain string from a column in a table as part of an SSIS package.
The contents of the column is formatted like this "TST_AB1_ABC123456_TEST".
I need to get the string between the second and 3rd "_", e.g. "ABC123456" without changing too much of the package so would rather do it in 1 SQL command if possible.
I've tried a few different methods using SUBSTRING, REVERSE and CHARINDEX but can't figure out how to get just that string.
Using the base string functions:
SELECT
SUBSTRING(col,
CHARINDEX('_', col, CHARINDEX('_', col) + 1) + 1,
CHARINDEX('_', col, CHARINDEX('_', col, CHARINDEX('_', col) + 1) + 1) -
CHARINDEX('_', col, CHARINDEX('_', col) + 1) - 1)
FROM yourTable;
In notes format, the above call to SUBSTRING is saying:
SELECT
SUBSTRING(<your column>,
<starting at one past the second underscore>,
<for a length of the number of characters in between the 2nd and 3rd
underscore>)
FROM yourTable;
On other databases, such as Postgres and Oracle, there are substring index and regex functions which can handle the above more gracefully. Actually, more recent versions of SQL Server have a STRING_SPLIT function, which could be used here, but it does not maintain the order of the resulting parts.
If your column values always have 4 parts you can use the PARSENAME() function like this.
DECLARE #MyString VARCHAR(100)
SET #MyString = 'TST_AB1_ABC123456_TEST';
SELECT PARSENAME(REPLACE(#MyString, '_', '.'), 2)
You could also do this using Cross Apply. I added in a where clause to make sure you don't get an error resulting from strings without 3 underscores
with your_table as (select 'TST_AB1_ABC123456_TEST' as txt1)
select txt1, txt2
from your_table t1
where txt1 like '%_%_%_%'
cross apply (select charindex( '_', txt1) as i1) t2 -- locate the 1st underscore
cross apply (select charindex( '_', txt1, (i1 + 1)) as i2 ) t3 -- then the 2nd
cross apply (select charindex( '_', txt1, (i2 + 1)) as i3 ) t4 -- then the 3rd
cross apply (select substring( txt1,(i2+1), (i3-i2-1)) as txt2) t5 -- between 2nd & 3rd
Outputs
+------------------------+-----------+
| txt1 | txt2 |
+------------------------+-----------+
| TST_AB1_ABC123456_TEST | ABC123456 |
+------------------------+-----------+
DEMO

Get first n characters from a string write them and continue to get the next n characters and write them

I am trying to deal with some random numbers from a list.
What I am trying to achieve is to paste the numbers on multiple columns of max 35 char per column.
If there is a comma after, let's say char 32 and the next number has 6 char, I want to have the 32 char in column 1 and the next 35 chars from char 32 to in column 2, same condition, if there is a comma I would paste less.
I've only tried the cases until now, and I could get the 1st column, but I can't move to the next one.
declare #string varchar(max)
set #string= '2344,2343,5645465,546456,43645645,654656,5765765,6576467,7576576,35345435'
select
CASE WHEN (LEN(REPLACE(#string, ',', ';')) >= 35 ) THEN REVERSE(SUBSTRING(REVERSE(LEFT(REPLACE(#string, ',', ';'), 35)), CHARINDEX(';', REVERSE(LEFT(REPLACE(#string, ',', ';'), 35)))+1, 35)) ELSE REPLACE(#string, ',', ';') END as fact1,
'' as fact2,
'' as fact3,
'' as fact4
From string '2344,2343,5645465,546456,43645645,654656,5765765,6576467,7576576,35345435'
I would like:
column 1: 2344,2343,5645465,546456,43645645
column 2: 654656,5765765,6576467,7576576
column 3: 35345435
column 4:
Assuming SQL Server here, you could use a recursive CTE to first get the parts of the strings distributed on rows.
You need to split the string into a left and a right part. To get the proper position where to split take the whole 35 characters on the left, reverse it and look for the first comma in that reverse. That gives you the difference you need to correct the 35 with.
You can also have a running number in the CTE. So you can pick any number in a subquery in a SELECT without a FROM to get such a row as a column.
DECLARE #string varchar(max);
SET #string = '2344,2343,5645465,546456,43645645,654656,5765765,6576467,7576576,35345435';
WITH
cte
AS
(
SELECT 1 n,
left(#string, 35 - charindex(',', reverse(left(#string, 35)))) l,
CASE
WHEN len(#string) - 35 + charindex(',', reverse(left(#string, 35))) - 1 >= 0 THEN
right(#string, len(#string) - 35 + charindex(',', reverse(left(#string, 35))) - 1)
ELSE
''
END r
UNION ALL
SELECT n + 1 n,
left(r, 35 - charindex(',', reverse(left(r, 35)))) l,
CASE
WHEN len(r) - 35 + charindex(',', reverse(left(r, 35))) - 1 >= 0 THEN
right(r, len(r) - 35 + charindex(',', reverse(left(r, 35))) - 1)
ELSE
''
END r
FROM cte
WHERE len(r) > 0
)
SELECT (SELECT l
FROM cte
WHERE n = 1) [column 1],
(SELECT l
FROM cte
WHERE n = 2) [column 2],
(SELECT l
FROM cte
WHERE n = 3) [column 3],
(SELECT l
FROM cte
WHERE n = 4) [column 4];
db<>fiddle

Order string alpha numerically A1-1-1, A1-2-1, A1-10-1, A1-2-2, A1-2-3 etc

I have a column with different length strings which has dashes (-) that separates alphanumeric strings.
The string could look like "A1-2-3".
I need to order by first "A1" then "2" then "3"
I want to achieve the following order for the column:
A1
A1-1-1
A1-1-2
A1-1-3
A1-2-1
A1-2-2
A1-2-3
A1-7
A2-1-1
A2-1-2
A2-1-3
A2-2-1
A2-2-2
A2-2-3
A2-10-1
A2-10-2
A2-10-3
A10-1-1
A10-1-2
A10-1-3
A10-2-1
A10-2-2
A10-2-3
I can separate the string with the following code:
declare #string varchar(max) = 'A1-2-3'
declare #first varchar(max) = SUBSTRING(#string,1,charindex('-',#string)-1)
declare #second varchar(max) = substring(#string, charindex('-',#string) + 1, charindex('-',reverse(#string))-1)
declare #third varchar(max) = right(#string,charindex('-',reverse(#string))-1)
select #first, #second, #third
With the above logic I thought that I could use the following:
Note this only regards strings with 2 dashes
select barcode from tabelWithBarcodes
order by
case when len(barcode) - len(replace(barcode,'-','')) = 2 then
len(SUBSTRING(barcode,1,charindex('-',barcode)-1))
end
, case when len(barcode) - len(replace(barcode,'-','')) = 2 then
SUBSTRING(barcode,1,(charindex('-',barcode)-1))
end
, case when len(barcode) - len(replace(barcode,'-','')) = 2 then
len(substring(barcode, charindex('-',barcode) + 1, charindex('-',reverse(barcode))-1))
end
, case when len(barcode) - len(replace(barcode,'-','')) = 2 then
substring(barcode, charindex('-',barcode) + 1, charindex('-',reverse(barcode))-1)
end
, case when len(barcode) - len(replace(barcode,'-','')) = 2 then
len(right(barcode,charindex('-',reverse(barcode))-1))
end
, case when len(barcode) - len(replace(barcode,'-','')) = 2 then
right(barcode,charindex('-',reverse(barcode))-1)
end
But the sorting is not working for the second and third section of the string.
(I haven't added the code for checking if the string has only 1 or no dash in it for simplicity)
Not sure if I'm on the right path here.
Is anybody able to solve this?
This is not pretty, however...
USE Sandbox;
GO
WITH VTE AS(
SELECT V.SomeString
--Randomised order
FROM (VALUES ('A1-1-1'),
('A10-1-3'),
('A10-2-2'),
('A1-1-3'),
('A10-2-1'),
('A2-2-2'),
('A1-2-1'),
('A1-2-2'),
('A2-1-1'),
('A10-1-2'),
('B2-1-2'),
('A1'),
('A2-2-1'),
('A2-10-3'),
('A10-2-3'),
('A2-1-2'),
('B1-4'),
('A2-10-2'),
('A2-2-3'),
('A10-1-1'),
('A1-A1-3'),
('A1-7'),
('A2-10-1'),
('A2-1-3'),
('A1-1-2'),
('A1-2-3')) V(SomeString)),
Splits AS(
SELECT V.SomeString,
DS.Item,
DS.ItemNumber,
CONVERT(int,STUFF((SELECT '' + NG.token
FROM dbo.NGrams8k(DS.item,1) NG
WHERE TRY_CONVERT(int, NG.Token) IS NOT NULL
ORDER BY NG.position
FOR XML PATH('')),1,0,'')) AS NumericPortion
FROM VTE V
CROSS APPLY dbo.DelimitedSplit8K(V.SomeString,'-') DS),
Pivoted AS(
SELECT S.SomeString,
MIN(CASE V.P1 WHEN S.Itemnumber THEN REPLACE(S.Item, S.NumericPortion,'') END) AS P1Alpha,
MIN(CASE V.P1 WHEN S.Itemnumber THEN S.NumericPortion END) AS P1Numeric,
MIN(CASE V.P2 WHEN S.Itemnumber THEN REPLACE(S.Item, S.NumericPortion,'') END) AS P2Alpha,
MIN(CASE V.P2 WHEN S.Itemnumber THEN S.NumericPortion END) AS P2Numeric,
MIN(CASE V.P3 WHEN S.Itemnumber THEN REPLACE(S.Item, S.NumericPortion,'') END) AS P3Alpha,
MIN(CASE V.P3 WHEN S.Itemnumber THEN S.NumericPortion END) AS P3Numeric
FROM Splits S
CROSS APPLY (VALUES(1,2,3)) AS V(P1,P2,P3)
GROUP BY S.SomeString)
SELECT P.SomeString
FROM Pivoted P
ORDER BY P.P1Alpha,
P.P1Numeric,
P.P2Alpha,
P.P2Numeric,
P.P3Alpha,
P.P3Numeric;
This outputs:
A1
A1-1-1
A1-1-2
A1-1-3
A1-2-1
A1-2-2
A1-2-3
A1-7
A1-A1-3
A2-1-1
A2-1-2
A2-1-3
A2-2-1
A2-2-2
A2-2-3
A2-10-1
A2-10-2
A2-10-3
A10-1-1
A10-1-2
A10-1-3
A10-2-1
A10-2-2
A10-2-3
B1-4
B2-1-2
This makes use of 2 user defined functions. Firstly or DelimitedSplit8k_Lead (I used DelimitedSplit8k as I don't have the other on my sandbox at the moment). Then you also have NGrams8k.
I really should explain how this works, but yuck... (edit coming).
OK... (/sigh) What it does. Firstly, we split the data into its relevant parts using delimitedsplit8k(_lead). Then, within the SELECT we use FOR XML PATH to get (only) the nuemrical part of that string (For example, for 'A10' we get '10') and we convert it to a numerical value (an int).
Then we pivot that data out into respective parts. The alphanumerical part, and the numerical part. So, for the value 'A10-A1-12' we end up with the row:
'A', 10, 'A', 1, 12
Then, now that we've pivoted the data, we sort it by each column individually. And voila.
This will fall over if you have a value like 'A1A' or '1B1', and honestly, I'm not changing it to catter for that. This was messy, and really isn't what the RDBMS should be doing.
Up to 3 dashes can be covered by fiddling with replace & parsename & patindex:
declare #TabelWithBarcodes table (id int primary key identity(1,1), barcode varchar(20) not null, unique (barcode));
insert into #TabelWithBarcodes (barcode) values
('2-2-3'),('A2-2-2'),('A2-2-1'),('A2-10-3'),('A2-10-2'),('A2-10-1'),('A2-1-3'),('A2-1-2'),('A2-1-1'),
('A10-2-3'),('A10-2-2'),('A10-2-10'),('A10-1-3'),('AA10-A111-2'),('A10-1-1'),
('A1-7'),('A1-2-3'),('A1-2-12'),('A1-2-1'),('A1-1-3'),('B1-1-2'),('A1-1-1'),('A1'),('A10-10-1'),('A12-10-1'), ('AB1-2-E1') ;
with cte as
(
select barcode,
replace(BarCode, '-', '.')
+ replicate('.0', 3 - (len(BarCode)-len(replace(BarCode, '-', '')))) as x
from #TabelWithBarcodes
)
select *
, substring(parsename(x,4), 1, patindex('%[0-9]%',parsename(x,4))-1)
,cast(substring(parsename(x,4), patindex('%[0-9]%',parsename(x,4)), 10) as int)
,substring(parsename(x,3), 1, patindex('%[0-9]%',parsename(x,3))-1)
,cast(substring(parsename(x,3), patindex('%[0-9]%',parsename(x,3)), 10) as int)
,substring(parsename(x,2), 1, patindex('%[0-9]%',parsename(x,2))-1)
,cast(substring(parsename(x,2), patindex('%[0-9]%',parsename(x,2)), 10) as int)
,substring(parsename(x,1), 1, patindex('%[0-9]%',parsename(x,1))-1)
,cast(substring(parsename(x,1), patindex('%[0-9]%',parsename(x,1)), 10) as int)
from cte
order by
substring(parsename(x,4), 1, patindex('%[0-9]%',parsename(x,4))-1)
,cast(substring(parsename(x,4), patindex('%[0-9]%',parsename(x,4)), 10) as int)
,substring(parsename(x,3), 1, patindex('%[0-9]%',parsename(x,3))-1)
,cast(substring(parsename(x,3), patindex('%[0-9]%',parsename(x,3)), 10) as int)
,substring(parsename(x,2), 1, patindex('%[0-9]%',parsename(x,2))-1)
,cast(substring(parsename(x,2), patindex('%[0-9]%',parsename(x,2)), 10) as int)
,substring(parsename(x,1), 1, patindex('%[0-9]%',parsename(x,1))-1)
,cast(substring(parsename(x,1), patindex('%[0-9]%',parsename(x,1)), 10) as int)
extend each barcode to 4 groups by adding trailing .0 if missing
split each barcode in 4 groups
split each group in leading characters and trailing digits
sort by the leading character first
then by casting the digits as numeric
See db<>fiddle
An alterative approach would be to use your technique to split the string into its 3 component parts, then left pad those strings with leading zeros (or characters of your choice). That avoids any issues where the string may contain alphanumerics rather than just numerics. However, it does mean that strings containing different length alphabetic characters may not be sorted as you may expect... Here's the code to play with (using the definitions from #dnoeth's excellent answer):
;with cte as
(
select barcode
, case
when barcode like '%-%' then
substring(barcode,1,charindex('-',barcode)-1)
else
barcode
end part1
, case
when barcode like '%-%' then
substring(barcode, charindex('-',barcode) + 1, case
when barcode like '%-%-%' then
(charindex('-',barcode,charindex('-',barcode) + 1)) - 1
else
len(barcode)
end
- charindex('-',barcode))
else
''
end part2
, case
when barcode like '%-%-%' then
right(barcode,charindex('-',reverse(barcode))-1) --note: assumes you don't have %-%-%-%
else
''
end part3
from #TabelWithBarcodes
)
select barcode
, part1, part2, part3
, right('0000000000' + coalesce(part1,''), 10) lpad1
, right('0000000000' + coalesce(part2,''), 10) lpad2
, right('0000000000' + coalesce(part3,''), 10) lpad3
from cte
order by lpad1, lpad2, lpad3
DBFiddle Example

How to get a specific part from a string in SQL

I need to get a specific part from string.
In the following example the field POSITION contains the A- block, the M-0000000359 block and finally the block to the right of /.
What I need now is the full number to the right of / and if there is a , only the full number up to the comma.
So if the next output of POSITION would be A-M-0000000359/10 or A-M-0000000359/10,10 then the result I need now is 10 in both cases.
SQL
SELECT POSITION
,SUBSTRING((REPLACE(POSITION, SUBSTRING((POSITION), 1, CHARINDEX('/', (POSITION), 1)), '')), 1, CHARINDEX('/', (POSITION), 0)) AS TRIM_A
,SUBSTRING((REPLACE(POSITION, SUBSTRING((POSITION), 1, CHARINDEX('/', (POSITION), 1)), '')), 0, CHARINDEX(',', ((REPLACE(POSITION, SUBSTRING((POSITION), 1, CHARINDEX('/', (POSITION), 1)), ''))), 1)) AS TRIM_B
,*
FROM ORDER
Output
POSITION |TRIM_A|TRIM_B
---------------------|------|------|
A-M-0000000359/1 |1
---------------------|------|------|
A-M-0000000359/1,10 |1,10 1
You can accomplish this with a CASE statement then. Change the #position variable to test it out.
declare #position varchar(64)= 'A-M-0000000359/1111,10'
select
case
when patindex('%,%',#position) > 0
then substring(substring(#position,CHARINDEX('/',#position) + 1,len(#position) - CHARINDEX('/',#position)),1,patindex('%,%',substring(#position,CHARINDEX('/',#position) + 1,len(#position) - CHARINDEX('/',#position))) - 1)
else substring(#position,CHARINDEX('/',#position) + 1,len(#position) - CHARINDEX('/',#position))
end
Perhaps a lighter alternative
Declare #YourTable table (Position varchar(50))
Insert Into #YourTable values
('A-M-0000000359/1,10'),
('A-M-0000000359/1'),
('A-M-0000000359')
Select A.*
,Trim_A = case when charindex('/',Position)=0 then '' else substring(Position,charindex('/',Position)+1,50) end
,Trim_B = case when charindex(',',Position)=0 then ''
else substring(Position,charindex('/',Position)+1,charindex(',',Position)-charindex('/',Position)-1)
end
From #YourTable A
Returns
Position Trim_A Trim_B
A-M-0000000359/1,10 1,10 1
A-M-0000000359/1 1
A-M-0000000359
Can you please try this, I found it very simple and easy to understand that we can simply do it using CASE
create table #test(block varchar(50))
insert into #test values
('A-M-0000000359/10,11'), ('A-M-0000000359/10')
select substring(block, charindex('/', block)+1,
case when charindex(',', block) = 0 then
len(block)
else
(charindex(',', block)-1)-charindex('/', block)
end) finalValue
from #test
OUTPUT
----------
finalValue
10
10

How can I substring "val2" from a string formatted like "val1A-val2A~val1B-val2B~val1C-val2C", etc, based on the condition of matching it with "val1"?

In a certain SQL table I'm working on, there's a column that contains data formatted like:
"year-text~year-text~year-text~year-text~year-text~year-text~year-text~" and so on and so forth.
(year is in 'yyyy' format)
(for example):
"2012-10000~2013-5000~2014-500~2015-50000~2016-100~"
How, using SQL might I extract, say, the value "50000" based on having the year, "2015"
Things to note/clarify:
The "-" and "~" characters can be trusted as delimiters. That is, they do not exist within any of the values or, of course, the years.
No year exists without a value. In other words, if the value becomes blank, the year is stripped out, as well (In other words, the stored string will never have an "-" and a "~" right next to each other, such as 2016 in the string "2015-200~2016-~2014-1000", for example).
The years in the string may not be in chronological order from left to right.
There could be virtually any number of years (each with a value) in the string or, indeed, none, at all. If no year/value pair exists for the column, the value becomes NULL
Please note that after each value for each year the character "~" is applied even if it is the last year/value pair. Any string value that is not NULL will therefore always end with a "~".
Perhaps this can help
With the aid of a parser and cross apply
Declare #String varchar(max) = '012-10000~2013-5000~2014-500~2015-50000~2016-100~'
Select A.*
,B.*
From [dbo].[udf-Str-Parse](#String,'~') A
Cross Apply (Select Val1=max(IIF(Key_PS=1,Key_Value,NULL))
,Val2=max(IIF(Key_PS=2,Key_Value,NULL))
From [dbo].[udf-Str-Parse](A.Key_Value,'-')) B
Where A.Key_Value<>''
Returns
Key_PS Key_Value Val1 Val2
1 012-10000 012 10000
2 2013-5000 2013 5000
3 2014-500 2014 500
4 2015-50000 2015 50000
5 2016-100 2016 100
My Parser if needed
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#Delimeter varchar(10))
--Usage: Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
-- Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
Returns #ReturnTable Table (Key_PS int IDENTITY(1,1), Key_Value varchar(max))
As
Begin
Declare #XML xml;Set #XML = Cast('<x>' + Replace(#String,#Delimeter,'</x><x>')+'</x>' as XML)
Insert Into #ReturnTable Select ltrim(rtrim(String.value('.', 'varchar(max)'))) FROM #XML.nodes('x') as T(String)
Return
End
Here is an option which uses SUBSTRING and CHARINDEX to get the job done:
SUBSTRING(col,
CHARINDEX('2015', col) + 5,
CHARINDEX('~', col, CHARINDEX('2015', col) + 5) - (CHARINDEX('2015', col) + 5))
In the sample input
2012-10000~2013-5000~2014-500~2015-50000~2016-100~
CHARINDEX('2015', col) + 5 would start at the 5 in the number 50000 after the occurrence of 2015.
The term
CHARINDEX('~', col, CHARINDEX('2015', col) + 5) - (CHARINDEX('2015', col) + 5)
yields the length of the number 50000, which in this case would be 5.
you can use substr and charindex
select substr( charindex( 'your_val', your_column) + length('your__val') +1, 4);
in your case
select substr( charindex( '2016', your_column) + length('2016') +1, 4);