How to get a specific part from a string in SQL - 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

Related

SQL trying to replace middle characters with *

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

Pull the particular string from nvarchar type column in SQL Server

I have a string like &hprop=anprop_p&asofmonth=01/2017&OutputType=PDF&IsGrid=-&ReportCode=AllCol1&Attach=NO&IsRequestQue=true and want to pull the values partitioned by & from string.
As we see above each string is separated with & and both the values have a name i.e. Outputtype= and ReportCode=
In SQL query it should return only values in different columns. AllCol1 Aand PDF
I have tried the below query but it is pulling string ReportCode=AllCol1
declare #Str varchar(500)
select SUBSTRING(SUBSTRING(#Str, CHARINDEX('&ReportCode=', #Str) + 1, LEN(#Str)), 0, CHARINDEX('&', SUBSTRING(#Str, CHARINDEX('&', #Str) +1, LEN(#Str))))
As you are using SQL Server 2016, you can take advantage of STRING_SPLIT() to split your url into the component query parameters, e.g.
SELECT *
FROM STRING_SPLIT(N'&hprop=anprop_p&asofmonth=01/2017&OutputType=PDF&IsGrid=-&ReportCode=AllCol1&Attach=NO&IsRequestQue=true', '&');
Will return:
value
-----------------
hprop=anprop_p
asofmonth=01/2017
OutputType=PDF
IsGrid=-
ReportCode=AllCol1
Attach=NO
IsRequestQue=true
You would then need to split each result on = to separate it into the parameter name and the argument. e.g.
SELECT s.value,
Parameter = CASE WHEN CHARINDEX('=', s.value) = 0 THEN s.value ELSE LEFT(s.value, CHARINDEX('=', s.value) - 1) END,
Value = CASE WHEN CHARINDEX('=', s.value) = 0 THEN NULL ELSE SUBSTRING(s.value, CHARINDEX('=', s.value) + 1, LEN(s.value)) END
FROM STRING_SPLIT(N'&hprop=anprop_p&asofmonth=01/2017&OutputType=PDF&IsGrid=-&ReportCode=AllCol1&Attach=NO&IsRequestQue=true', '&') s;
Returns:
value Parameter Value
-------------------------------------------------
NULL
hprop=anprop_p hprop anprop_p
asofmonth=01/2017 asofmonth 01/2017
OutputType=PDF OutputType PDF
IsGrid=- IsGrid -
ReportCode=AllCol1 ReportCode AllCol1
Attach=NO Attach NO
IsRequestQue=true IsRequestQue true
Finally, you would just need to extract the terms you are actually interested in, and PIVOT them to bring back one row. Bringing it all together, you get:
DECLARE #T TABLE (ID INT IDENTITY, Col NVARCHAR(MAX));
INSERT #T (Col)
VALUES
(N'&hprop=anprop_p&asofmonth=01/2017&OutputType=PDF&IsGrid=-&ReportCode=AllCol1&Attach=NO&IsRequestQue=true'),
(N'&hprop=anprop_p&asofmonth=01/2017&OutputType=XLS&IsGrid=-&ReportCode=AllCol3&Attach=NO&IsRequestQue=false');
SELECT pvt.ID, pvt.OutputType, pvt.ReportCode
FROM ( SELECT T.ID,
t.Col,
Parameter = CASE WHEN CHARINDEX('=', s.value) = 0 THEN s.value ELSE LEFT(s.value, CHARINDEX('=', s.value) - 1) END,
Value = CASE WHEN CHARINDEX('=', s.value) = 0 THEN NULL ELSE SUBSTRING(s.value, CHARINDEX('=', s.value) + 1, LEN(s.value)) END
FROM #T AS t
CROSS APPLY STRING_SPLIT(T.Col, '&') AS s
WHERE s.value <> ''
) AS t
PIVOT (MAX(Value) FOR Parameter IN ([ReportCode], [OutputType])) AS pvt;
Which returns:
ID OutputType ReportCode
----------------------------------
1 PDF AllCol1
2 XLS AllCol3
Example on DB<>Fiddle
Use string_split():
select max(case when s.value like 'Outputtype=%'
then stuff(s.value, 1, 11, '')
end) as Outputtype,
max(case when s.value like 'ReportCode=%'
then stuff(s.value, 1, 11, '')
end) as ReportCode
from string_split(#str, '&') s;
Here is a db<>fiddle.

Extract substring from a string in SQL Server

I need to extract a part of substring from string which follows as per below.
YY_12.Yellow
ABC_WSA.Thisone_A
SS_4MON.DHHE_A_A
I need to extract the string as per below
Yellow
Thisone
DHHE
You could use something like this:
declare #tbl table (col nvarchar(100));
insert #tbl values ('YY_12.Yellow'), ('ABC_WSA.Thisone_A'), ('SS_4MON.DHHE_A_A')
select *
, charindex('_', col2, 0)
, left(col2,
case
when charindex('_', col2, 0) - 1 > 0
then charindex('_', col2, 0) - 1
else len(col2)
end) [result]
from (
select col
, substring(col, charindex('.', col, 0) + 1, len(col)) [col2]
from #tbl ) rs
I'm going to leave the full code so as you can hopefully understand what I did.
First identify and remove everything up to the dot "." (in the [col2] column in the nested SELECT)
Then I nest that SELECT so I can apply a new logic much easier on the result column from the first SELECT from which I only keep everything up to the underscore "_"
The final result is stored in the [result] column
Try this:
CREATE TABLE app (info varchar(20))
INSERT INTO app VALUES
('YY_12.Yellow'),
('ABC_WSA.Thisone_A'),
('SS_4MON.DHHE_A_A'),
('testnopoint')
SELECT
CASE
WHEN CHARINDEX('.', info) > 0 THEN
CASE
WHEN CHARINDEX('_', info, CHARINDEX('.', info) + 1) > 0 THEN
SUBSTRING(info, CHARINDEX('.', info) + 1, CHARINDEX('_', info, CHARINDEX('.', info) + 1) - CHARINDEX('.', info) - 1)
ELSE
SUBSTRING(info, CHARINDEX('.', info) + 1, LEN(info))
END
END
FROM app
My query, if . is not present returns NULL, if you want returns all string remove the CASE statement
Go on SqlFiddle
You could also try with parsename() function available from SQL Server 2012
select Name, left(parsename(Name,1),
case when charindex('_', parsename(Name,1)) > 0
then charindex('_', parsename(Name,1))-1
else len(parsename(Name,1))
end) [ExtrectedName] from table
This assumes you have always . in your string to read the name after .
Result :
Name ExtrectedName
YY_12.Yellow Yellow
ABC_WSA.Thisone_A Thisone
SS_4MON.DHHE_A_A DHHE
Try this, used STUFF here
SELECT LEFT(STUFF(col,1,CHARINDEX('.',col),''),
CHARINDEX('_',STUFF(col,1,CHARINDEX('.',col),'')+'_')-1
)
FROM #table
Output:-
Yellow
Thisone
DHHE

How to use substring conditionally before and after two different symbols in SQL SERVER

I have a table A with ID col. Here is sample data -
ID
NT-QR-1499-1(2015)
NT-XYZ-1503-1
NT-RET-546-1(2014)
I need to select everything after first '-' from left and before '(' from the right. However, some records do not have '(', in which case, the second condition would not apply.
Here is what I need -
QR-1499-1
XYZ-1503-1
RET-546-1
You could get it done in a CASE statement, although I'd definitely take any advice from Aaron;
CREATE TABLE #TestData (ID nvarchar(50))
INSERT INTO #TestData (ID)
VALUES
('NT-QR-1499-1(2015)')
,('NT-XYZ-1503-1')
,('NT-RET-546-1(2014)')
SELECT
ID
,CASE
WHEN CHARINDEX('(',ID) = 0
THEN RIGHT(ID, LEN(ID)-CHARINDEX('-',ID))
ELSE LEFT(RIGHT(ID, LEN(ID)-CHARINDEX('-',ID)),CHARINDEX('(',RIGHT(ID, LEN(ID)-CHARINDEX('-',ID)))-1)
END Result
FROM #TestData
Try this:
SELECT y.i, SUBSTRING(ID, x.i + 1, IIF(y.i = 0, LEN(ID), y.i - x.i - 1))
FROM mytable
CROSS APPLY (SELECT CHARINDEX('-', ID)) AS x(i)
CROSS APPLY (SELECT CHARINDEX('(', ID)) AS y(i)
It looks like your column is not actually a single data element, but multiple data elements that have been concatenated together. A bad idea for database design, which is causing the problem that you're having now.
This should give you what you need, but strongly consider separating the column into the required pieces.
SELECT
SUBSTRING(id, CHARINDEX('-', id) + 1, LEN(id) - CHARINDEX('(', REVERSE(id)) - CHARINDEX('-', id))
FROM
My_Table
DECLARE #str varchar(64);
DECLARE #start int;
DECLARE #length int;
SELECT #str = 'NT-QR-1499-1(2015)';
/*SELECT #str = 'NT-XYZ-1503-1';*/
SELECT #start = CHARINDEX('-', #str) + 1;
SELECT #length = CHARINDEX('(', #str) - #start;
IF (#length > 0)
SELECT SUBSTRING(#str, #start, #length)
ELSE
SELECT SUBSTRING(#str, #start, LEN(#str))
GO
SELECT CASE
WHEN CHARINDEX('(',ID) > 0
THEN
SUBSTRING(ID,CHARINDEX('-',ID)+1,(CHARINDEX('(',ID)-CHARINDEX('-',ID)-1))
ELSE
SUBSTRING(ID,CHARINDEX('-',ID)+1)
END AS New_Column_Name
FROM Table_Name
First it will check whether "(" present or not .
If present then it will fetch the data from next position of "-" to before the position of "(".
otherwise it will fetch the data from next position of "-" to till end.

Convert fractional string to decimal

I've got a few columns that have values either in fractional strings (i.e. 6 11/32) or as decimals (1.5). Is there a CAST or CONVERT call that can convert these to consistently be decimals?
The error:
Msg 8114, Level 16, State 5, Line 1
Error converting data type varchar to numeric.
Can I avoid doing any kind of parsing?
Thanks!
P.S. I'm working in SQL Server Management Studio 2012.
CREATE FUNCTION ufn_ConvertToNumber(#STR VARCHAR(50))
RETURNS decimal(18,10)
AS
BEGIN
DECLARE #L VARCHAR(50) = ''
DECLARE #A DECIMAL(18,10) = 0
SET #STR = LTRIM(RTRIM(#STR)); -- Remove extra spaces
IF ISNUMERIC(#STR) > 0 SET #A = CONVERT(DECIMAL(18,10), #STR) -- Check to see if already real number
IF CHARINDEX(' ',#STR,0) > 0
BEGIN
SET #L = SUBSTRING(#STR,1,CHARINDEX(' ',#STR,0) - 1 )
SET #STR = SUBSTRING(#STR,CHARINDEX(' ',#STR,0) + 1 ,50 )
SET #A = CONVERT(DECIMAL(18,10), #L)
END
IF CHARINDEX('/',#STR,0) > 0
BEGIN
SET #L = SUBSTRING(#STR,1,CHARINDEX('/',#STR,0) - 1 )
SET #STR = SUBSTRING(#STR,CHARINDEX('/',#STR,0) + 1 ,50 )
SET #A = #A + ( CONVERT(DECIMAL(18,10), #L) / CONVERT(DECIMAL(18,10), #STR) )
END
RETURN #A
END
GO
Then access it via select dbo.ufn_ConvertToNumber ('5 9/5')
You'll need to parse. As Niels says, it's not really a good idea; but it can be done fairly simply with a T-SQL scalar function.
CREATE FUNCTION dbo.FracToDec ( #frac VARCHAR(100) )
RETURNS DECIMAL(14, 6)
AS
BEGIN
RETURN CASE
WHEN #frac LIKE '% %/%'
THEN CAST(LEFT(#frac, CHARINDEX(' ', #frac, 1) -1) AS DECIMAL(14,6)) +
( CAST(SUBSTRING(#frac, CHARINDEX(' ', #frac, 1) + 1, CHARINDEX('/', #frac, 1)-CHARINDEX(' ',#frac,1)-1) AS DECIMAL(14,6))
/ CAST(RIGHT(#frac, LEN(#frac) - CHARINDEX('/', #frac, 1)) AS DECIMAL(14,6)) )
WHEN #frac LIKE '%/%'
THEN CAST(LEFT(#frac, CHARINDEX('/', #frac, 1) - 1) AS DECIMAL(14,6)) / CAST(RIGHT(#frac, LEN(#frac) - CHARINDEX('/', #frac, 1)) AS DECIMAL(14,6))
ELSE
CAST(#frac AS DECIMAL(14,6))
END
END
GO
-- Test cases
SELECT dbo.FracToDec('22/7'), dbo.fracToDec('3.117'), dbo.fracToDec('7 3/4')
-- Output
-- 3.142857 3.117000 7.750000
Note that this will fail if the contents passed does not actually match the forms "mm/nn", "xx mm/nn" or a real decimal.
And here is the solution without functions and stored procedures - just for the fun of it. First you have to create new column (I call it decimal) and then fill it with the values converted from the original mixed-format column (called inconsistent) using the following query:
UPDATE "my_table"
SET "decimals" = CASE WHEN CHARINDEX('/', "inconsistent") > 0
THEN CAST(CASE WHEN CHARINDEX(' ',
RTRIM(LTRIM("inconsistent"))) > 0
THEN LEFT(RTRIM(LTRIM("inconsistent")),
CHARINDEX(' ',
RTRIM(LTRIM("inconsistent")))
- 1)
ELSE '0'
END AS FLOAT)
+ CAST(SUBSTRING(RTRIM(LTRIM("inconsistent")),
CHARINDEX(' ',
RTRIM(LTRIM("inconsistent")))
+ 1,
CHARINDEX('/',
RTRIM(LTRIM("inconsistent")))
- 1 - CHARINDEX(' ',
RTRIM(LTRIM("inconsistent")))) AS FLOAT)
/ CAST(RIGHT(RTRIM(LTRIM("inconsistent")),
LEN(RTRIM(LTRIM("inconsistent")))
- CHARINDEX('/',
RTRIM(LTRIM("inconsistent")))) AS FLOAT)
ELSE CAST(RTRIM(LTRIM("inconsistent")) AS FLOAT)
END
I am not aware of any database system, or code framework for that matter, supporting strings like 6 11/32 natively. Your best bet is to add a column to the relevant table and denormalize the actual value in there with a script, or creating a view on top of it that does that automatically. It'll take some complex code though, and it's probably not a good idea to do it in SQL at all.