Splitting a string in sql for all characters - sql

I am trying to split a string in SQL on the space delimiter. The number of spaces changes, so there can be values for 4 or 5 columns. I am trying using CHARINDEX and SUBSTRING, but can't get past the first delimiter.
Example:
input would be '444 Addison Avenue SA 5222'

To split into columns, you could use a little XML in concert with a CROSS APPLY
Example
Declare #YourTable Table ([SomeCol] varchar(50))
Insert Into #YourTable Values
('444 Addison Avenue SA 5222')
,('1 Washington Square')
Select A.*
,B.*
From #YourTable A
Cross Apply (
Select Pos1 = ltrim(rtrim(xDim.value('/x[1]','varchar(max)')))
,Pos2 = ltrim(rtrim(xDim.value('/x[2]','varchar(max)')))
,Pos3 = ltrim(rtrim(xDim.value('/x[3]','varchar(max)')))
,Pos4 = ltrim(rtrim(xDim.value('/x[4]','varchar(max)')))
,Pos5 = ltrim(rtrim(xDim.value('/x[5]','varchar(max)')))
,Pos6 = ltrim(rtrim(xDim.value('/x[6]','varchar(max)')))
From (Select Cast('<x>' + replace((Select replace([SomeCol],' ','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml) as xDim) as A
) B
Returns

You can achieve the same result only with XML-based splitting:
DECLARE #tmp TABLE ([Address] varchar(50))
INSERT INTO #tmp VALUES ('444 Addison Avenue SA 5222'),('1 Washington Square')
SELECT T.[Address]
,AddressElements.value(N'/x[1]','varchar(max)') AS Element_1
,AddressElements.value(N'/x[2]','varchar(max)') AS Element_2
,AddressElements.value(N'/x[3]','varchar(max)') AS Element_3
,AddressElements.value(N'/x[4]','varchar(max)') AS Element_4
,AddressElements.value(N'/x[5]','varchar(max)') AS Element_5
FROM (
SELECT [Address]
,CAST('<x>' + REPLACE([Address],' ','</x><x>') + '</x>' AS XML) AS AddressElements
FROM #tmp
) T
Results:

Related

Splitting a SQL column's value into several fields

I have a bunch of rows that have a field called "name" that have values like this, delimited by a _:
ServerNumber_BrandNumber_BrandName_JobName
Sometimes, the job name will be spread out over two deliminations, like this:
ServerNumber_BrandNumber_BrandName_JobName_JobNamePart2
I want to break out each of those into their own field in a select statement like this:
SELECT
name[0] as ServerNumber,
name[1] as BrandNumber,
name[2] as BrandName,
name[3] as JobName
from table
If I do something like this it will work if job name is only part of one delmiter, but it will return nothing if it's using two:
REVERSE(PARSENAME(REPLACE(REVERSE(name), '_', '.'), 1))
How can I do all of this?
Working example
Declare #YourTable Table ([Name] varchar(150)) Insert Into #YourTable Values
('ServerNumber_BrandNumber_BrandName_JobName')
,('ServerNumber_BrandNumber_BrandName_JobName_JobNamePart2')
Select Pos1 = JSON_VALUE(JS,'$[0]')
,Pos2 = JSON_VALUE(JS,'$[1]')
,Pos3 = JSON_VALUE(JS,'$[2]')
,Pos4 = concat(JSON_VALUE(JS,'$[3]'),'_'+JSON_VALUE(JS,'$[4]'))
From #YourTable A
Cross Apply (values ('["'+replace(replace(string_escape([Name],'json'),' ','_'),'_','","')+'"]') ) B(JS)
Results
Pos1 Pos2 Pos3 Pos4
ServerNumber BrandNumber BrandName JobName
ServerNumber BrandNumber BrandName JobName_JobNamePart2
XML Approach (2012)
Select Pos1 = xDim.value('/x[1]','varchar(150)')
,Pos2 = xDim.value('/x[2]','varchar(150)')
,Pos3 = xDim.value('/x[3]','varchar(150)')
,Pos4 = concat(xDim.value('/x[4]','varchar(150)'),'_'+xDim.value('/x[5]','varchar(150)'))
From #YourTable A
Cross Apply ( values (cast('<x>' + replace((Select replace(NAME,'_','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml))) B(xDim)

Selecting text between 2nd and 3rd occurrence of delimiter

I'm trying to select the text between the second and third occurance of a delimeter (-) in SQL server.
For example, if I have the string aaa-bbbb-cccc-dddd I would like to return cccc, but I can't understand how to make a substring work when I have more than 2 of the delimeters.
Thanks for any help
If you always the same number of elements you could leverage PARSENAME like this.
select parsename(replace('aaa-bbbb-cccc-dddd', '-', '.'), 2)
But if your real data is not that consistent you need to use a real splitter.
If parsename() (+1) is not a valid option, perhaps a little XML.
Here are two illustrations, both return the same results
Example
Declare #YourTable table (SomeCol varchar(500))
Insert Into #YourTable values
('aaa-bbbb-cccc-dddd')
Select SomeCol
,Pos2 = cast('<x>' + replace(A.SomeCol,'-','</x><x>')+'</x>' as xml).value('/x[2]','varchar(50)')
,Pos3 = cast('<x>' + replace(A.SomeCol,'-','</x><x>')+'</x>' as xml).value('/x[3]','varchar(50)')
From #YourTable A
Select SomeCol
,B.*
From #YourTable A
Cross Apply (
Select Pos2 = XMLData.value('/x[2]','varchar(50)')
,Pos3 = XMLData.value('/x[3]','varchar(50)')
From (values (cast('<x>' + replace(A.SomeCol,'-','</x><x>')+'</x>' as xml))) B1(XMLData)
) B
Returns
SomeCol Pos2 Pos3
aaa-bbbb-cccc-dddd bbbb cccc

Is it possible to compare comma delimited string in T-SQL without looping?

Let's say I have 2 tables where both has column called Brand. The value is comma delimited so for example if one of the table has
ACER,ASUS,HP
AMD,NVIDIA,SONY
as value. Then the other table has
HP,GIGABYTE
MICROSOFT
SAMSUNG,PHILIPS
as values.
I want to compare these table to get all matched record, in my example ACER,ASUS,HP and HP,GIGABYTE match because both has HP. Right now I'm using loop to achieve this, I'm wondering if it's possible to do this in a single query syntax.
You are correct in wanting to step away from the loop.
Since you are on 2012, String_Split() is off the table. However, there are any number of split/parse TVF functions in-the-wild.
Example 1 - without a TVF
Declare #T1 table (Brand varchar(50))
Insert Into #T1 values
('ACER,ASUS,HP'),
('AMD,NVIDIA,SONY')
Declare #T2 table (Brand varchar(50))
Insert Into #T2 values
('HP,GIGABYTE'),
('MICROSOFT'),
('SAMSUNG,PHILIPS')
Select Distinct
T1_Brand = A.Brand
,T2_Brand = B.Brand
From (
Select Brand,B.*
From #T1
Cross Apply (
Select RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace(Brand,',','</x><x>')+'</x>' as xml)) as A
Cross Apply x.nodes('x') AS B(i)
) B
) A
Join (
Select Brand,B.*
From #T2
Cross Apply (
Select RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace(Brand,',','</x><x>')+'</x>' as xml)) as A
Cross Apply x.nodes('x') AS B(i)
) B
) B
on A.RetVal=B.RetVal
Example 2 - with a TVF
Select Distinct
T1_Brand = A.Brand
,T2_Brand = B.Brand
From (
Select Brand,B.*
From #T1
Cross Apply [dbo].[tvf-Str-Parse](Brand,',') B
) A
Join (
Select Brand,B.*
From #T2
Cross Apply [dbo].[tvf-Str-Parse](Brand,',') B
) B
on A.RetVal=B.RetVal
Both Would Return
T1_Brand T2_Brand
ACER,ASUS,HP HP,GIGABYTE
The UDF if interested
CREATE FUNCTION [dbo].[tvf-Str-Parse] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(#String,#Delimiter,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
--Thanks Shnugo for making this XML safe
--Select * from [dbo].[tvf-Str-Parse]('Dog,Cat,House,Car',',')
--Select * from [dbo].[tvf-Str-Parse]('John Cappelletti was here',' ')
--Select * from [dbo].[tvf-Str-Parse]('this,is,<test>,for,< & >',',')
Had the same problem with comparing "," delimited strings
you can use "XML" to do that and compare the outputs and return the same/different value:
declare #TestInput nvarchar(255)
, #TestInput2 nvarchar(255)
set #TestInput = 'ACER,ASUS,HP'
set #TestInput2 = 'HP,GIGABYTE'
;WITH FirstStringSplit(S1) AS
(
SELECT CAST('<x>' + REPLACE(#TestInput,',','</x><x>') + '</x>' AS XML)
)
,SecondStringSplit(S2) AS
(
SELECT CAST('<x>' + REPLACE(#TestInput2,',','</x><x>') + '</x>' AS XML)
)
SELECT STUFF(
(
SELECT ',' + part1.value('.','nvarchar(max)')
FROM FirstStringSplit
CROSS APPLY S1.nodes('/x') AS A(part1)
WHERE part1.value('.','nvarchar(max)') IN(SELECT B.part2.value('.','nvarchar(max)')
FROM SecondStringSplit
CROSS APPLY S2.nodes('/x') AS B(part2)
)
FOR XML PATH('')
),1,1,'') as [Same Value]
Edit:
Changed 'Stuff' to 'XML'

SQL Split String on Delimiter and set to new columns

There has been questions related to this asked, but I have not gotten the solution I am need of.
The string(s) I am trying to split up look like this:
/Dev/act/billing
or
/ST/recManage/prod/form
The issue I have is the first '/' giving me problems when I try and do things with LEFT/RIGHT/SUBSTRING/CHARINDEX. It messes up counts and stopping at the delimiter. Also, it is important to note that the number of delimiters changes. So I want to find a way to split it up so I can get every possible substring.
RIGHT(c3.Path,CHARINDEX('/', REVERSE(c3.Path))-1) AS LastPath
This has gotten me the last part of the string. I have messed with other things ilke:
SUBSTRING(c3.Path,CHARINDEX('/',c3.Path,(CHARINDEX('/',c3.Path)+1))+1,len(c3.Path)),
This gets everything after the second '/'
I have also messed with XML and
SET #delimiter='/'
;WITH CTE AS
(SELECT CAST('<M>' + REPLACE([Path], #delimiter , '</M><M>') + '</M>' AS XML)
AS [Type XML]
FROM [Rpts].[dbo].[Cata]
)
,
CTE2 as (Select [Type XML].value('/M[2]', 'varchar(50)') As FirstPath from CTE)
Then doing: CTE2.FirstPath to get the result. But this then gives NULL
I am not on SQL 2016 so I cannot use SPLIT_STRING.
Thank you
Try this:
DECLARE #mockup TABLE(ID INT IDENTITY,YourString VARCHAR(MAX));
INSERT INTO #mockup VALUES('/Dev/act/billing'),('/ST/recManage/prod/form');
SELECT m.ID
,B.part.value(N'text()[1]',N'nvarchar(max)')
FROM #mockup AS m
OUTER APPLY(SELECT CAST('<x>' + REPLACE(m.YourString,'/','</x><x>') + '</x>' AS XML)) AS A(Casted)
OUTER APPLY A.Casted.nodes(N'/x[text()]') AS B(part);
This approach is save as long as you do not have forbidden characters in your string (namely <, > and &). If you need this, just call in, it is possible to solve.
Using .nodes() with the XQuery predicat [text()] will ommit all rows with no value...
The result
ID Part
---------
1 Dev
1 act
1 billing
2 ST
2 recManage
2 prod
2 form
Without going dynamic, and if you have a limited (or max) number of columns, perhaps something like this:
The replace() in the Cross Apply assumes the strings begins with a '/'
(Easy to expand or contract ... the pattern is pretty clear)
Example
Declare #YourTable table (ID int,[Path] varchar(max))
Insert Into #YourTable values
(1,'/Dev/act/billing')
,(2,'/ST/recManage/prod/form')
Select A.ID
,B.*
From #YourTable A
Cross Apply (
Select Pos1 = xDim.value('/x[1]','varchar(max)')
,Pos2 = xDim.value('/x[2]','varchar(max)')
,Pos3 = xDim.value('/x[3]','varchar(max)')
,Pos4 = xDim.value('/x[4]','varchar(max)')
,Pos5 = xDim.value('/x[5]','varchar(max)')
,Pos6 = xDim.value('/x[6]','varchar(max)')
,Pos7 = xDim.value('/x[7]','varchar(max)')
,Pos8 = xDim.value('/x[8]','varchar(max)')
,Pos9 = xDim.value('/x[9]','varchar(max)')
From (Select Cast('<x>' + replace(substring(A.Path,2,len(A.Path)),'/','</x><x>')+'</x>' as xml) as xDim) as A
) B
Returns
EDIT - Slightly Simplified Version
Notice the staggard Pos1 and /x[2]
Select A.ID
,Pos1 = xDim.value('/x[2]','varchar(max)')
,Pos2 = xDim.value('/x[3]','varchar(max)')
,Pos3 = xDim.value('/x[4]','varchar(max)')
,Pos4 = xDim.value('/x[5]','varchar(max)')
,Pos5 = xDim.value('/x[6]','varchar(max)')
From #YourTable A
Cross Apply ( Select Cast('<x>' + replace(A.Path,'/','</x><x>')+'</x>' as xml) ) B (xDim)

SQL string split and value increment

R/005/2016-17
This varchar value i have in a table, i need to add 1 to 005.
i need output R/006/2016-17 for the next entry
How can i split the string and add one.
Your table demonstrates just how bad things can be when you don't properly normalize your data. But you can do this using some string manipulation. I would strongly urge you to split this into the appropriate columns instead of keeping this all crammed together.
This code will produce the desired output based on your sample data.
declare #Something varchar(20) = 'R/005/2016-17'
select parsename(replace(#Something, '/', '.'), 3) + '/' + right('000' + convert(varchar(3), convert(int, parsename(replace(#Something, '/', '.'), 2)) + 1), 3) + '/' + parsename(replace(#Something, '/', '.'), 1)
If you don't have ParseName(), I have a TVF which may help. If you can't use the UDF, the logic is easily ported into the CROSS APPLY
Declare #YourTable table (ID int,String varchar(max))
Insert Into #YourTable values
(1,'R/005/2016-17'),
(2,'A/119/2016-18')
Update #YourTable
Set String = Pos1+'/'+right('000000'+cast(cast(Pos2 as int)+1 as varchar(25)),len(Pos2))+'/'+Pos3
From #YourTable A
Cross Apply [dbo].[udf-Str-Parse-Row](A.String,'/') B
Select * from #YourTable
The Update Table
ID String
1 R/006/2016-17
2 A/120/2016-18
The UDF if Needed
CREATE FUNCTION [dbo].[udf-Str-Parse-Row] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select Pos1 = xDim.value('/x[1]','varchar(max)')
,Pos2 = xDim.value('/x[2]','varchar(max)')
,Pos3 = xDim.value('/x[3]','varchar(max)')
,Pos4 = xDim.value('/x[4]','varchar(max)')
,Pos5 = xDim.value('/x[5]','varchar(max)')
,Pos6 = xDim.value('/x[6]','varchar(max)')
,Pos7 = xDim.value('/x[7]','varchar(max)')
,Pos8 = xDim.value('/x[8]','varchar(max)')
,Pos9 = xDim.value('/x[9]','varchar(max)')
From (Select Cast('<x>' + Replace(#String,#Delimiter,'</x><x>')+'</x>' as XML) as xDim) A
)
--Select * from [dbo].[udf-Str-Parse-Row]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse-Row]('John Cappelletti',' ')
You can use Computed Columns when create table for this type case.
Eg:-
[EmployeeNo] AS ([PreFix]+ RIGHT('0000000' + CAST(Id AS VARCHAR(7)), 7)) PERSISTED,
https://msdn.microsoft.com/en-us/library/ms188300.aspx