SQL Substring and Charindex - sql

I have 111-1001-0000-0000 this record in one column and 453200-0000-000 in second column
I want output as 111-1001-0000-453200-0000-0000-000
That means 111-1001-0000 from 1st column and 453200 from 2nd column and again 0000 from 1st column and 0000-000
from 2nd column
I tried below query but getting 111-1001-453200-0000-0000-000.
-0000 is missing from 1st column
Declare #1stcolumn nvarchar(30),#2ndcolumn nvarchar(30)
set #1stcolumn='111-1001-0000-0000'
set #2ndcolumn='453200-0000-000'
select substring(#1stcolumn,1,charindex(right(#1stcolumn,charindex('-',reverse(#1stcolumn))),#1stcolumn))
+substring(#2ndcolumn,1,charindex('-',#2ndcolumn))+reverse(substring(reverse(#1stcolumn),0,charindex('-',reverse(#1stcolumn))))
+'-'+substring(#2ndcolumn,charindex('-',#2ndcolumn)+1,len(#2ndcolumn))

find the position where to split column 1 and column2. Use LEFT() and RIGHT() to split the string and then concatenate back in the order that you want
; with tbl as
(
select col1 = #1stcolumn, col2 = #2ndcolumn
)
select *,
c1.s1 + '-' + c2.s1 + '-' +c1.s2 + '-' + c2.s2
from tbl t
cross apply
(
select s1 = left(col1, p - 1),
s2 = right(col1, len(col1) - p)
from (
-- find the position of 3rd '-' by cascading charindex
select p = charindex('-', col1,
charindex('-', col1,
charindex('-', col1) + 1) + 1)
) p
) c1
cross apply
(
select s1 = left(col2, p - 1),
s2 = right(col2, len(col2) - p)
from (
select p = charindex('-', col2)
) p
) c2

A little modification in first substring. To get correct length I used LEN .
select substring(#1stcolumn,1,(Len(#1stcolumn) - charindex('- ',REVERSE(#1stcolumn)) + 1))
+substring(#2ndcolumn,1,charindex('-',#2ndcolumn))
+reverse(substring(reverse(#1stcolumn),0,charindex('-',reverse(#1stcolumn))))
+'-'+substring(#2ndcolumn,charindex('-',#2ndcolumn)+1,len(#2ndcolumn))

I'd probably do with with PARSENAME as it's quite concise then:
WITH YourTable AS(
SELECT '111-1001-0000-0000' AS Column1,
'453200-0000-000' AS Column2)
SELECT CONCAT_WS('-',PN.C1P1,PN.C1P2,PN.C1P3,PN.C2P1,PN.C1P4,PN.C2P2,PN.C2P3) AS NewString
FROM YourTable YT
CROSS APPLY (VALUES(REPLACE(YT.Column1,'-','.'),REPLACE(YT.Column2,'-','.')))R(Column1,Column2)
CROSS APPLY (VALUES(PARSENAME(R.Column1,4),PARSENAME(R.Column1,3),PARSENAME(R.Column1,2),PARSENAME(R.Column1,1),PARSENAME(R.Column2,3),PARSENAME(R.Column2,2),PARSENAME(R.Column2,1)))PN(C1P1,C1P2,C1P3,C1P4,C2P1,C2P2,C2P3);

WITH
test AS
(
select '111-1001-0000-0000' as col1, '453200-0000-000' as col2
)
,cte as
(
select
col1,
col2,
substring
(
col1,
0,
len(col1)-charindex('-',reverse(col1))
) as part1,
substring
(
col2,
0,
len(col2)-charindex('-',col2) - 1
) as part2
from test
),
cte2 as
(
select
part1,
part2,
substring
(
reverse(col1),
0,
charindex('-',reverse(col1))
) as part3,
substring
(
col2,
charindex('-',col2)+1,
len(col2)-charindex('-',col2)+1
) as part4
from cte
)
select part1+'-'+part2+'-'+part3+'-'+part4
from cte2

Related

Extract & Expand Numbers

Using SQL with Microsoft SQL Server. I have a column that has values like this:
5-7(A-C) 15(A-C)
3(A-C)
I am trying to extract the numbers and if there is a dash then I need those numbers plus all the numbers in between. So for this example the output would be 5, 6, 7, 15 for the first row and 3 for the second row. I will use the results to see if they exist in another table.
I have been using this but it does not get the numbers between the dash:
SELECT
CASE
WHEN CHARINDEX('-', SUBSTRING(cc_EXPRESSION, 1, CHARINDEX('(', cc_EXPRESSION) - 1)) > 0
THEN CAST(LEFT(SUBSTRING(cc_EXPRESSION, 1, CHARINDEX('(', cc_EXPRESSION) - 1), CHARINDEX('-', SUBSTRING(cc_EXPRESSION, 1, CHARINDEX('(', cc_EXPRESSION) - 1)) - 1) AS INT)
ELSE CAST(SUBSTRING(cc_EXPRESSION, 1, CHARINDEX('(', cc_EXPRESSION) - 1) AS INT)
END AS extracted_number
Here is an option that illustrates how you can "stack" expressions via a CROSS APPLY and JOIN an ad-hoc tally/numbers table.
You may notice I used TOP 1000 ... feel free to pick a more reasonable number
Example
Select A.cc_Expression
,NewValue = string_agg(N+R1,',')
From YourTable A
Cross Apply string_split(cc_Expression,' ') B
Cross Apply (values ( replace(left(B.Value,charindex('(',B.Value)-1 ),'-','.') ) )C(Rng)
Cross Apply (values (try_convert(int,coalesce(parsename(C.Rng,2),parsename(C.Rng,1) )) ,try_convert(int,parsename(C.Rng,1) )) ) D(R1,R2)
Join ( Select Top 1000 N=-1+Row_Number() Over (Order By (Select NULL)) From master..spt_values n1, master..spt_values n2 ) E on N<=R2-R1
Group By A.cc_Expression
Results
cc_Expression NewValue
3(A-C) 3
5-7(A-C) 15(A-C) 5,6,7,15
With the following table :
CREATE TABLE I_have_a_column_that_has_values_like_this (COL VARCHAR(256));
INSERT INTO I_have_a_column_that_has_values_like_this
VALUES ('5-7(A-C) 15(A-C)'), ('3(A-C)');
You can do it like :
WITH
T0 AS
(
SELECT COL, LEFT(value, CHARINDEX('(', value) -1) AS VAL
FROM I_have_a_column_that_has_values_like_this
CROSS APPLY STRING_SPLIT(COL, ' ')
),
T1 AS
(
SELECT COL, CASE WHEN VAL NOT LIKE '%-%' THEN VAL + '-' + VAL ELSE VAL END AS VAL
FROM T0
)
SELECT COL, value AS VALS
FROM T1
CROSS APPLY GENERATE_SERIES(CAST(LEFT(VAL, CHARINDEX('-', VAL)-1) AS INT),
CAST(RIGHT(VAL, CHARINDEX('-', REVERSE(VAL))-1) AS INT)) AS G
The result will be :
COL VALS
---------------------- -----------
5-7(A-C) 15(A-C) 5
5-7(A-C) 15(A-C) 6
5-7(A-C) 15(A-C) 7
5-7(A-C) 15(A-C) 15
3(A-C) 3

How do I extract data within parentheses from a table with different values?

Im trying to extract data within a column that contains IDs and characters that contain IDs within parentheses. It looks somewhat like this (Btw, there will only be one set of parentheses if they happen to exist within a row):
Col1
Mark(1234)
6789
VZ(X678)
ASD(5677)qwe
Ideal Result
1234
6789
X678
5677
This is what I have so far but its returning an error: 'Invalid length parameter passed to the LEFT or SUBSTRING function.'
SELECT DISTINCT col1,
CASE WHEN col1 like '%(%' then
SUBSTRING (col1,
CHARINDEX('%(%', col1) + 1,
CHARINDEX('%)%', col1) - CHARINDEX('%(%', col1) - 1)
else col1 end
from MyTable B;
If interested in a helper Table-Valued Function which will support multiple observations. If you don't want the function, it would be a small matter to migrate the logic into the CROSS APPLY
Example
Declare #YourTable Table ([Col1] varchar(50)) Insert Into #YourTable Values
('Mark(1234)')
,('6789')
,('VZ(X678)')
,('ASD(5677)qwe')
Select A.*
,NewValue = coalesce(RetVal,Col1)
from #YourTable A
Outer Apply [dbo].[tvf-Str-Extract](Col1,'(',')') B
Results
Col1 NewValue
Mark(1234) 1234
6789 6789
VZ(X678) X678
ASD(5677)qwe 5677
Results
CREATE FUNCTION [dbo].[tvf-Str-Extract-JSON] (#String nvarchar(max),#Delim1 nvarchar(100),#Delim2 nvarchar(100))
Returns Table
As
Return (
Select RetSeq = row_number() over (order by RetSeq)
,RetVal = left(RetVal,charindex(#Delim2,RetVal)-1)
From (
Select RetSeq = [Key]+1
,RetVal = trim(Value)
From OpenJSON( N'["'+replace(string_escape(#String,'json'),#Delim1,'","')+N'"]' )
) C1
Where charindex(#Delim2,RetVal)>1
)
EDIT - Sans TVF
Select A.*
,NewValue = coalesce(RetVal,Col1)
from #YourTable A
Outer Apply (
Select RetSeq = row_number() over (order by RetSeq)
,RetVal = left(RetVal,charindex(')',RetVal)-1)
From (
Select RetSeq = [Key]+1
,RetVal = trim(Value)
From OpenJSON( N'["'+replace(string_escape(Col1,'json'),'(','","')+N'"]' )
) C1
Where charindex(')',RetVal)>1
) B
#martin Smith
Thanks for pointing out the used of the wildcards. I changed my code to this and its doing what I needed it to do! Using the case when expression to look for the parentheses regardless of location so I kept the % wildcard there but took it out in the CHARINDEX as you mentioned:
SELECT DISTINCT col1,
CASE WHEN col1 like '%(%' then
SUBSTRING (col1,
CHARINDEX('(', col1) + 1,
CHARINDEX(')', col1) - CHARINDEX('(', col1) - 1)
else col1 end
from MyTable B;

SQL split string (all possible combination)

I would like to transform this string:
A1+A2+A3.B1+B2.C1
into
A1.B1.C1
A1.B2.C1
A2.B1.C1
A2.B2.C1
A3.B1.C1
A3.B2.C1
How can I do that? (note that each dimension(= a group separate by .), could have x values, I mean it can be A1+A2.B1.C1 or A1+A2.B1+B2+B3+B4+B5.C1+C2)
Thanks
If you have only 3 columns, then just use STRING_SPLIT: number your groups from first split and then do a join 3 times and select each group on corresponding join.
with a as (
select s2.value as v, dense_rank() over(order by s1.value) as rn
from STRING_SPLIT('A1+A2+A3.B1+B2.C1', '.') as s1
cross apply STRING_SPLIT(s1.value, '+') as s2
)
select
a1.v + '.' + a2.v + '.' + a3.v as val
from a as a1
cross join a as a2
cross join a as a3
where a1.rn = 1
and a2.rn = 2
and a3.rn = 3
| val |
----------
|A1.B1.C1|
|A2.B1.C1|
|A3.B1.C1|
|A1.B2.C1|
|A2.B2.C1|
|A3.B2.C1|
If you have indefinite number of groups, then it's better to use recursive CTE instead of dynamic SQL. What you should do:
Start with all the values from the first group.
On recursion step crossjoin all the values of the next group (i.e. step group number is current group number + 1).
Select the last recursion step where you'll have the result.
Code is below:
with a as (
select s2.value as v, dense_rank() over(order by s1.value) as rn
from STRING_SPLIT('A1+A2+A3.B1+B2+B3+B4.C1+C2.D1+D2+D3', '.') as s1
cross apply STRING_SPLIT(s1.value, '+') as s2
)
, b (val, lvl) as (
/*Recursion base*/
select cast(v as nvarchar(1000)) as val, rn as lvl
from a
where rn = 1
union all
/*Increase concatenation on each iteration*/
select cast(concat(b.val, '.', a.v) as nvarchar(1000)) as val, b.lvl + 1 as lvl
from b
join a
on b.lvl + 1 = a.rn /*Recursion step*/
)
select *
from b
where lvl = (select max(rn) from a) /*You need the last step*/
order by val
I won't add a tabular result since it is quite big. But try it by yourself.
Here is SQL server version and fiddle:
with lst(s) as (select * from STRING_SPLIT('A1+A2.B1+B2+B3+B4+B5.C1+C2','.'))
select t1+'.'+t2+'.'+t3 as res from
(select * from STRING_SPLIT((select s from lst where s like 'A%'), '+')) s1(t1) cross join
(select * from STRING_SPLIT((select s from lst where s like 'B%'), '+')) s2(t2) cross join
(select * from STRING_SPLIT((select s from lst where s like 'C%'), '+')) s3(t3);
Of course you can grow it in a regular fashion if the number of dimensions grows.
Here is a Postgresql solution:
with x(s) as (select string_to_array('A1+A2.B1+B2+B3+B4+B5.C1+C2','.'))
select t1||'.'||t2||'.'||t3 as res from
unnest((select string_to_array(s[1],'+') from x)) t1 cross join
unnest((select string_to_array(s[2],'+') from x)) t2 cross join
unnest((select string_to_array(s[3],'+') from x)) t3;
result:
res |
--------|
A1.B1.C1|
A1.B2.C1|
A1.B3.C1|
A1.B4.C1|
A1.B5.C1|
A2.B1.C1|
A2.B2.C1|
A2.B3.C1|
A2.B4.C1|
A2.B5.C1|
A1.B1.C2|
A1.B2.C2|
A1.B3.C2|
A1.B4.C2|
A1.B5.C2|
A2.B1.C2|
A2.B2.C2|
A2.B3.C2|
A2.B4.C2|
A2.B5.C2|
Here my code with your help. I didn't mention, but I can also have more or less than 3 parts, so I'm using a dynamic SQL for this:
declare #FILTER varchar(max)='B+C+D.A+G.T+Y+R.E'
-- Works also with A.B.C
-- Works also with A+B+C.D.E+F
-- Works also with A+B+C.D+E+F+G+H
declare #NB int
declare #SQL varchar(max)=''
select #NB=count(*) from STRING_SPLIT(#FILTER,'.')
set #SQL='
;with T(A,B) as
(select *, row_number() over (order by (select NULL))
from STRING_SPLIT(''' + #FILTER + ''',''.'')
)
select '
;with T(V,N) as (
select *, row_number() over (order by (select NULL))
from STRING_SPLIT(#FILTER,'.')
)
select #SQL=#SQL + 'T' + cast(N as varchar(max)) + ' + ''.'' + ' from T
set #SQL=left(#SQL,len(#SQL)-1) + ' as res from'
;with T(V,N) as (
select *, row_number() over (order by (select NULL))
from STRING_SPLIT(#FILTER,'.')
)
select #SQL=#SQL + '
(select * from STRING_SPLIT((select A from T where B=' + cast(N as varchar(max)) + '), ''+'')) s' + cast(N as varchar(max)) + '(t' + cast(N as varchar(max)) + ') cross join'
from T
set #SQL=left(#SQL,len(#SQL)-len('cross join'))
exec(#SQL)

extract text from the string

I need to extract text from the string KWR/50X50X5/1.4301 between /, or 50x50x5 in T-SQL. I've tried using Substing, however, does not go to me.
Ultimately, I need to add the values (sum values) ​​in between / without character x (for example, 50 + 50 + 5 = 105) I would be grateful for your help.
Try this:
DECLARE #t TABLE (id INT, v VARCHAR(100) )
INSERT INTO #t
VALUES ( 1, 'PWPQ/80X20/1.4301' ) ,
( 2, 'PWO/120/1.4404' ),
( 3, 'PWOI/120X9X90X80/1.4404' )
;WITH cte1 AS(SELECT id, SUBSTRING(v,
CHARINDEX('/', v) + 1,
CHARINDEX('/', v, CHARINDEX('/', v) + 1) - CHARINDEX('/', v) - 1) AS v
FROM #t),
cte2 AS(SELECT id, CAST ('<X>' + REPLACE(v, 'X', '</X><X>') + '</X>' AS XML) AS v FROM cte1)
SELECT id, SUM(Split.a.value('.', 'int')) AS v
FROM cte2 a CROSS APPLY v.nodes ('/X') AS Split(a)
GROUP BY id
Output:
id v
1 100
2 120
3 299
First cte is for extracting value between /.
Second cte for casting those values to xml format.
The last statement is standard trick for transposing string with delimeter to separate rows.
select substring(firstpart,1,CHARINDEX('/',firstpart)-1)
from
(select
substring(pattern,
CHARINDEX('/',pattern)+1,
datalength(pattern)) as firstpart
from tessst
)X;

Find position of delimited character in a String (SQL Server)

I have a string Variable
test=HARIA|123|MALE|STUDENT|HOUSEWIFE
i am using | as delimited character. if i want to extract data from 2nd occurence of pipe till 3rd occurrence. i need to get 'MALE' from above string.
any help appreciated
Untested
SELECT
SUBSTRING (
#test,
CHARINDEX('|', #test, CHARINDEX('|', #test) + 1) + 1,
CHARINDEX('|', #test, CHARINDEX('|', #test, CHARINDEX('|', #test) + 1) + 1) - 1
)
A nicer way would be split the string into a table, and use ROW_NUMBER() to extract the 3rd element. Based on this Arrays and Lists in SQL Server
DECLARE #test varchar(100) = 'HARIA|123|MALE|STUDENT|HOUSEWIFE'
SELECT TOP 8000
Num
INTO
#Number
FROM
(
SELECT
ROW_NUMBER() OVER (ORDER BY c1.object_id) AS Num
FROM
sys.columns c1, sys.columns c2, sys.columns c3
) N
SELECT
ROW_NUMBER() OVER (ORDER BY Num) AS Rank,
LTRIM(RTRIM(SUBSTRING(#test,
Num,
CHARINDEX('|', #test + '|', Num) - Num
))) AS Value
FROM
#Number
WHERE
Num <= LEN (#test)
AND
SUBSTRING('|' + #test, Num, 1) = '|'
DROP TABLE #Number
Try this
Solution 1:(Using a number table)
declare #str varchar(1000)
set #str ='HARIA|123|MALE|STUDENT|HOUSEWIFE'
--Creating a number table
;with numcte as(
select 1 as rn union all select rn+1 from numcte where rn<LEN(#str)),
--Get the position of the "|" charecters
GetDelimitedCharPos as(
select ROW_NUMBER() over(order by getdate()) seq, rn,delimitedcharpos
from numcte
cross apply(select SUBSTRING(#str,rn,1)delimitedcharpos) X where delimitedcharpos = '|')
--Applying the formula SUBSTRING(#str,startseq + 1,endseq-startseq + 1)
-- i.e. SUBSTRING(#str,11,15-11) in this case
select top 1 SUBSTRING(
#str
,(select top 1 rn+1 from GetDelimitedCharPos where seq =2)
,(select top 1 rn from GetDelimitedCharPos where seq =3) -
(select top 1 rn+1 from GetDelimitedCharPos where seq =2)
) DesiredResult
from GetDelimitedCharPos
Solution 2:(Using XQuery)
DECLARE #xml as xml,#str as varchar(100),#delimiter as varchar(10)
SET #str='HARIA|123|MALE|STUDENT|HOUSEWIFE'
SET #xml = cast(('<X>'+replace(#str,'|' ,'</X><X>')+'</X>') as xml)
SELECT ShrededData as DesiredResult FROM(
SELECT
ROW_NUMBER() over(order by getdate()) rn
,N.value('.', 'varchar(10)') as ShrededData FROM #xml.nodes('X') as T(N))X
WHERE X.rn = 3 -- Specifying the destination sequence value(here 3)
Output(in both the cases)
DesiredResult
MALE
I found this. Using a t-Sql for loop. Good reference of syntax, too.