Split Varchar max field into multiple columns based on a Semicolon - sql

I am writing up a report where I have a varchar(max) column that has data in it separated by a ";" semicolon. The end users are asking for the report to break apart this one column and return it as a series of columns.
I am not sure how to do this.
The data has variable lengths based on the status of the customer. Some of the columns only have 60 characters in it, some have 400+ characters.
The data looks somewhat like this:
Result 1 = aaaaaaaa; bbbbbbbbbbbb; ccccccccccccccccccccccccc; ddddddddddddd;
Result 2 = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; bbbbbbbbb;
Result 3 = aaaaaaaaaaaaaa; bbbbbbbbbbbbbbb; ccccccccccccccccccccccccc;
The flip side of this is that if I were to break each semicolon section out, I could end up with 40 - 50 columns worth of data, while others might end up being just 3. (if that makes sense)
Everything else in the report is a piece of cake, it is just the splitting of this one column that has me questioning the whole endeavor.
I guess my question here is:
How would I break apart this one column into multiple based off the Semicolon delimiter?

With the helps of a CROSS APPLY and an little XML. As you can see, the XML portion is easy to expand or contract as necessary.
Example
Declare #YourTable table (ID int, SomeCol varchar(max))
Insert into #YourTable values
(1,'aaaaaaaa; bbbbbbbbbbbb; ccccccccccccccccccccccccc; ddddddddddddd;'),
(2,'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; bbbbbbbbb;'),
(3,'aaaaaaaaaaaaaa; bbbbbbbbbbbbbbb; ccccccccccccccccccccccccc;')
Select A.ID
,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)')))
,Pos7 = ltrim(rtrim(xDim.value('/x[7]','varchar(max)')))
,Pos8 = ltrim(rtrim(xDim.value('/x[8]','varchar(max)')))
,Pos9 = ltrim(rtrim(xDim.value('/x[9]','varchar(max)')))
From (Select Cast('<x>' + replace((Select replace(A.SomeCol,';','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml) as xDim) as A
) B
Returns

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)

Get data between two second period and comma

I have an AWS server name and I am trying to just get the domain name.
Here is an example of the server name:
SERVER1.SERVER2.US-WEST-5.RDS.AMAZONAWS.COM,59642
I just want to return:
US-WEST-5.RDS.AMAZONAWS.COM
I have tried using something similar to this:
But that is only for semi-colons. I know it is nested substrings and charindexes,
You can try this using SUBSTRING (Transact-SQL)
.
Returns part of a character, binary, text, or image expression in SQL Server.
Syntax
SUBSTRING ( expression ,start , length )
Here is the implementation.
SELECT SUBSTRING('SERVER1.SERVER2.US-WEST-5.RDS.AMAZONAWS.COM,59642',
1, CHARINDEX(',', 'SERVER1.SERVER2.US-WEST-5.RDS.AMAZONAWS.COM,59642') - 1)
AS FirstPart
Here is the live db<>fiddle demo.
Just another option. Perhaps a little overkill, but it demonstrates how you can parse a delimited string into columns.
Example
Declare #YourTable table (ID int,SomeCol varchar(500))
Insert Into #YourTable values
(1,'SERVER1.SERVER2.US-WEST-5.RDS.AMAZONAWS.COM,59642')
,(2,'SERVER1.SERVER2.US-WEST-6.RDS.AMAZONAWS.COM') -- note no trailing ,
,(2,'SERVER1.SERVER2.US-WEST-6.RDS.DEV.AMAZONAWS.COM') -- note extra DEV ,
Select A.ID
,NewStr = concat(pos3,'.'+pos4,'.'+pos5,'.'+pos6,'.'+pos7,'.'+pos8,'.'+pos9)
From #YourTable A
Cross Apply ( values ( left(SomeCol,charindex(',',SomeCol+',')-1 ))) B(CleanString) -- Removes Trailing ,####
Cross Apply ( -- Parses CleanString on .
Select Pos1 = xDim.value('/x[1]','varchar(max)') -- Can be removed
,Pos2 = xDim.value('/x[2]','varchar(max)') -- Can be removed
,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 ( values (cast('<x>' + replace(CleanString,'.','</x><x>')+'</x>' as xml))) as A(xDim)
) C
Returns
ID NewStr
1 US-WEST-5.RDS.AMAZONAWS.COM
2 US-WEST-6.RDS.AMAZONAWS.COM
2 US-WEST-6.RDS.DEV.AMAZONAWS.COM

aggregation of comma-separated values in-position

I have one table where there is two column loan no and counter_value.
Against each loan no there is the list of comma separated values are stored.
declare #tbl table (loanno varchar(100) , counter_value varchar(200) )
insert into #tbl
values(‘pr0021’,‘1000,200,300,100,800,230’),
(‘pr0021’,‘500,300,300,100,600,200’),
(‘pr0021’,‘500,100,200,190,400,100’)
I need to do grouping according to loan no and in-position aggregation (summation) on counter values.
I need the output like below.
loanno counter_value
pr0021 2000,600,800,390,1800,530
Since you have denormalized data you will first have to split this into columns, do the aggregation and then recreate the delimited column. There are plenty of splitters out there but here is my favorite for this type of thing. http://www.sqlservercentral.com/articles/Tally+Table/72993/ The main advantage of this splitter is that it returns the position of each value which most other splitter do not.
Utilizing that splitter you can do this like this.
with AggregateData as
(
select t.loanno
, s.ItemNumber
, TotalValue = sum(convert(int, s.Item))
from #tbl t
cross apply dbo.DelimitedSplit8K(t.counter_value, ',') s
group by t.loanno
, s.ItemNumber
)
select ad.loanno
, STUFF((select ',' + convert(varchar(10), ad2.TotalValue)
from AggregateData ad2
where ad2.loanno = ad.loanno
order by ad2.ItemNumber
FOR XML PATH('')), 1, 1, '')
from AggregateData ad
group by ad.loanno
Sean's would be my first choice (+1).
However, if you have a known (or fixed) number of positions, consider the following:
Example
Select A.loanno
,NewAggr = concat(sum(Pos1),',',sum(Pos2),',',sum(Pos3),',',sum(Pos4),',',sum(Pos5),',',sum(Pos6))
From #tbl A
Cross Apply (
Select Pos1 = n.value('/x[1]','int')
,Pos2 = n.value('/x[2]','int')
,Pos3 = n.value('/x[3]','int')
,Pos4 = n.value('/x[4]','int')
,Pos5 = n.value('/x[5]','int')
,Pos6 = n.value('/x[6]','int')
From (Select cast('<x>' + replace(A.counter_value,',','</x><x>')+'</x>' as xml) as n) X
) B
Group By A.loanno
Returns
loanno NewAggr
pr0021 2000,600,800,390,1800,530
If it Helps with the Visualization, the CROSS APPLY Generates

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