How to sort numeric values in string in SQL Server - sql

Input string -
'0|33|6|2|30|12|4|1|8|29|9|34|7|13|11|5|3|31|10|36|37|37|37|38|39|40|44|65|66'
Output string -
'0|1|2|3|4|5|6|7|8|9|10|11|12|13|29|30|31|33|34|36|37|37|37|38|39|40|44|65|66'
Need this in SQL Server 2008.
I have splitted the value on | and then inserted the values into temp table and then concatenated value. Is there any better approarch?

Without a Parse/Split function
Declare #String varchar(max) = '0|33|6|2|30|12|4|1|8|29|9|34|7|13|11|5|3|31|10|36|37|37|37|38|39|40|44|65|66'
Select Sorted = Stuff((Select '|' +RetVal
From (
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 #String as [*] For XML Path('')),'|','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) A
Order By cast(RetVal as int)
For XML Path ('')),1,1,'')
With a Parse/Split function'
Declare #String varchar(max) = '0|33|6|2|30|12|4|1|8|29|9|34|7|13|11|5|3|31|10|36|37|37|37|38|39|40|44|65|66'
Select Sorted = Stuff((Select '|' +RetVal
From [dbo].[udf-Str-Parse-8K](#String,'|')
Order By cast(RetVal as int)
For XML Path ('')),1,1,'')
Both Return
0|1|2|3|4|5|6|7|8|9|10|11|12|13|29|30|31|33|34|36|37|37|37|38|39|40|44|65|66
The UDF if needed
CREATE FUNCTION [dbo].[udf-Str-Parse-8K] (#String varchar(max),#Delimiter varchar(25))
Returns Table
As
Return (
with cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (IsNull(DataLength(#String),0)) Row_Number() over (Order By (Select NULL)) From (Select N=1 From cte1 a,cte1 b,cte1 c,cte1 d) A ),
cte3(N) As (Select 1 Union All Select t.N+DataLength(#Delimiter) From cte2 t Where Substring(#String,t.N,DataLength(#Delimiter)) = #Delimiter),
cte4(N,L) As (Select S.N,IsNull(NullIf(CharIndex(#Delimiter,#String,s.N),0)-S.N,8000) From cte3 S)
Select RetSeq = Row_Number() over (Order By A.N)
,RetVal = LTrim(RTrim(Substring(#String, A.N, A.L)))
From cte4 A
);
--Orginal Source http://www.sqlservercentral.com/articles/Tally+Table/72993/
--Much faster than str-Parse, but limited to 8K
--Select * from [dbo].[udf-Str-Parse-8K]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse-8K]('John||Cappelletti||was||here','||')

declare #str varchar(100) =
'0|33|6|2|30|12|4|1|8|29|9|34|7|13|11|5|3|31|10|36|37|37|37|38|39|40|44|65|66';
declare #s varchar(100) = '|' + #str + '|';
declare #result varchar(100);
with D(d) as
(
select v from (values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) V(v)
),
N(n) as
(
select top (len(#s) - 1)
row_number() over (order by (select 1)) from D d1, D d2
),
S(s) as
(
select substring(#s, n + 1, charindex('|', #s, n + 1) - (n + 1))
from N where substring(#s, n, 1) = '|'
)
select top (100) #result = isnull(#result + '|', '') + s
from S order by cast(s as int);
print #result;

Related

Remove style from database (SQL Server)

I am working on old database table where there are values that have CSS attached to it.
For example:
<font size="25">Select your gender?</font>
<font size="25">Select your country?</font>
Is there any way to remove all the styling at once besides removing it one at a time?
I want to remove <font size="25"></font> and just keep Select your gender
Thank you in advance!
With the aid of a Helper Function
Example
Declare #YourTable table (SomeCol varchar(max) )
Insert Into #YourTable values
('1.<font size="25">Select your <b>gender</b>?</font>') -- Notice nested tags <b> ..</b>
,('2.<font size="25">Select your <span style="color:blue;">country</span>?</font>')
Select B.*
From #YourTable A
Cross Apply (
Select NewStr = Stuff((Select '' +RetVal
From [dbo].[tvf-Str-Extract](SomeCol,'>','<')
Order By RetSeq
For XML Path ('')),1,0,'')
) B
Returns
NewStr
1.Select your gender?
2.Select your country?
The function if interested
CREATE FUNCTION [dbo].[tvf-Str-Extract] (#String varchar(max),#Delim1 varchar(100),#Delim2 varchar(100))
Returns Table
As
Return (
Select RetSeq = row_number() over (order by RetSeq)
,RetVal = left(RetVal,charindex(#Delim2,RetVal)-1)
From (
Select RetSeq = row_number() over (order by 1/0)
,RetVal = ltrim(rtrim(B.i.value('(./text())[1]', 'varchar(max)')))
From ( values (convert(xml,'<x>' + replace((Select replace(#String,#Delim1,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>').query('.'))) as A(XMLData)
Cross Apply XMLData.nodes('x') AS B(i)
) C1
Where charindex(#Delim2,RetVal)>1
)
/*
Declare #String varchar(max) = 'Dear [[FirstName]] [[LastName]], ...'
Select * From [dbo].[tvf-Str-Extract] (#String,'[[',']]')
*/
A combination of charindex, substring and left can help
The problem is you have be sure that the data you need for every row is in the form:
xxxxx>DATA<*****, that you don,t a row in a form like xxxxx>DATA1<*****xxxxx>DATA2<*****
variable example:
declare #string as varchar(1000)
declare #NoLeft as varchar(1000)
declare #NoRight as varchar(1000)
set #string = '1.<font size="25">Select your gender?</font>';
select #string
SELECT #NoLeft = substring(#string, CHARINDEX( '>', #string) + 1 , len(#string)-CHARINDEX( '>', #string))
select #NoLeft
SELECT #NoRight = left(#NoLeft, CHARINDEX( '<', #NoLeft) - 1)
select #NoRight
For a table it becomes
select left(substring(ColumnName, CHARINDEX( '>', ColumnName) + 1 , len(ColumnName)-CHARINDEX( '>', ColumnName)), CHARINDEX( '<', substring(ColumnName, CHARINDEX( '>', ColumnName) + 1 , len(ColumnName)-CHARINDEX( '>', ColumnName))) - 1)
From TableName

How to get first character beside underscore for a given string

input:
string
'abc_def_ghk_lmn'
output:
dgl
You can try this (or even create a function):
DECLARE #str varchar(250) = 'abc_def_ghk_lmn'
DECLARE #result varchar(250)='';
WHILE(charindex('_',#str)!=0)
BEGIN
DECLARE #position int = charindex('_',#str)
SET #result += substring(#str,#position+1,1)
SET #str = substring(#str,#position+1,len(#str))
END
SELECT #result
You can use a recursive CTE:
IF OBJECT_ID('tempdb..#t') is not null drop table #t
SELECT * into #t from (values (N'abc_def_ghk_lmn'), (N'a_f_k_n'), (null), ('____'), ('asasas'), ('a_sasas'), ('asas_')) T(val);
;WITH CTE AS
(
SELECT
Cast(SUBSTRING(T.val, CHARINDEX('_',T.val,1) + 1, 1) as nvarchar(4000)) FC
,CHARINDEX('_', T.val, 1) CI
,val
,0 [level]
from #t T
where CHARINDEX('_', T.val, 1) > 0
union all
SELECT
Cast(T.FC + SUBSTRING(T.val, CHARINDEX('_',T.val,T.CI+1) + 1, 1) as nvarchar(4000)) FC
,CHARINDEX('_', T.val, T.CI+1) CI
,val
,t.[level] + 1
from CTE T
where CHARINDEX('_',T.val,T.CI+1) > 0
)
, Res AS
(
SELECT
*
,ROW_NUMBER() OVER (Partition by val order by [level] desc) RN
from CTE
)
SELECT * from Res where RN = 1
This uses Jeff Moden's DelimitedSplit8K Function. Firstly because i don't know what version of SQL Server you are using, and secondly, the inbuilt function STRING_SPLIT (available in SQL Server 2016 onwards) doesn't include an Item Number value (thus how does one exclude the first result?):
SELECT (SELECT LEFT(Item, 1)
FROM DelimitedSplit8K ('abc_def_ghk_lmn','_') DS
WHERE DS.ItemNumber > 1
FOR XML PATH(''));
Edit:
Example with a dataset:
WITH VTE AS(
SELECT *
FROM (VALUES ('asdgsad_sdfh_sadfh'),('_ashdf+ashd'),('jsda_sdkhfsdjf_654_asdfkhasd_567465413_kasbgdjkasdj')) V(S))
SELECT (SELECT LEFT(Item, 1)
FROM DelimitedSplit8K (S,'_') DS
WHERE DS.ItemNumber > 1
FOR XML PATH('')) AS FirstCharacters
FROM VTE;
Try this:
DECLARE #String VARCHAR(50)= 'abc_def_ghk_lmn',#Result VARCHAR(10)=''
WHILE CHARINDEX('_',#String)>0
BEGIN
SELECT #Result=#Result + SUBSTRING(#String,CHARINDEX('_',#String)+1,1)
SELECT #String=RIGHT(#String,LEN(#String)- CHARINDEX('_',#String))
END
SELECT #Result FinalResult
OUTPUT:
FinalResult
dgl
Please try this -...Always use SET BASED Approach
SOLUTION
DECLARE #x AS XML=''
DECLARE # AS VARCHAR(1000) = 'abc_def_ghk_lmn_'
SET #x = CAST('<A>'+ REPLACE(#,'_','</A><A>')+ '</A>' AS XML)
;WITH CTE AS
(
SELECT t.value('.', 'VARCHAR(10)') Value FROM #x.nodes('/A') AS x(t)
)
,CTE1 AS
(
SELECT * , ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) rnk FROM CTE
)
,CTE2 AS
(
SELECT SUBSTRING(Value,1,1) v , rnk FROM CTE1
)
SELECT CASE WHEN LEFT(#,1) <> '_' THEN MAX(SUBSTRING(u,2,LEN(u))) ELSE MAX(u) END finalstr from (
SELECT
(
SELECT '' + v
FROM CTE2 a
FOR XML PATH('')
)u
FROM CTE2 )x
OUTPUT
finalstr
---------------
dgl
(1 row affected)
Try this below
DECLARE #str TAble(String varchar(250))
INSERT INTO #str
SELECT 'abc_def_ghk_lmn_opq_rst_uvw_xyz'
SELECT STUFF((SELECT ''+LEFT(String,1) FROM
(
SELECT Split.a.value('.','Varchar(1000)') As String,
ROW_NUMBER()OVER(ORDER BY (SELECT 1)) AS Id FROM
(
SELECT CASt('<S>'+REPLACE(String,'_','</S><S>')+'</S>' AS XML )AS String
FROM #str
)as A
CROSS APPLY String.nodes ('S') AS Split(a)
)dt
WHERE dt.Id <>1
FOR XML PATH ('')),1,0,'') AS ExpectedColumn
Result
ExpectedColumn
--------------
dglorux
Use LEFT to get the left part and INSTR to find underscore and finally get your string:
SELECT LEFT(FIELD_1, CHARINDEX('_', FIELD_1) - 2) AS [ANY_ALIAS]
FROM TABLE_1;
EDIT: Now is MSSQL... ;D

Reading repeating tags using open xml or xml functions SQL

<ActiveDig>
<Dig IsPrim="True">
<Code>3342</Code>
<Name>abc</Name>
</Dig>
<Dig IsPrim="False">
<Code>5342</Code>
<Name>xyz</Name>
</Dig>
</ActiveDig>
I'm trying to load it into table as follows:
| Dig_Isprim | Code | Name |
|-------------|--------|--------|
| True | 3342 | abc |
|-------------|--------|--------|
| False | 5342 | xyz |
Example
Set #XML ='
<ActiveDig>
<Dig IsPrim="True">
<Code>3342</Code>
<Name>abc</Name>
</Dig>
<Dig IsPrim="False">
<Code>5342</Code>
<Name>xyz</Name>
</Dig>
</ActiveDig>
'
Select Dig_Isprim = lvl1.n.value('#IsPrim','varchar(50)')
,Code = lvl1.n.value('./Code[1]','varchar(50)')
,Name = lvl1.n.value('./Name[1]','varchar(50)')
From #XML.nodes('ActiveDig/*') lvl1(n)
Returns
Dig_Isprim Code Name
True 3342 abc
False 5342 xyz
EDIT -FULLY DYNAMIC SQL
The following uses a Table-Valued Function to parse virtually any XML into a dataset. Then we have some Dynamic XML within some Dynamic XML.
It may seem a little complicated, but when you look at the individual parts, it is not so bad.
Example
Declare #XML xml ='
<ActiveDig>
<Dig IsPrim="True">
<Code>3342</Code>
<Name>abc</Name>
</Dig>
<Dig IsPrim="False">
<Code>5342</Code>
<Name>xyz</Name>
</Dig>
</ActiveDig>
'
Declare #SQL varchar(max) = '
Select * Into #Temp
From (
Select *
,Item = concat(Element,IIF(Attribute='''','''',''_''),Attribute )
,RowN = sum(case when Lvl=2 then 1 else 0 end) over (Order by R1)
From [dbo].[tvf-XML-Hier](cast('''+cast(#XML as varchar(max))+''' as xml))
) A
Where Value is not null
Declare #SQL varchar(max) = ''
Select *
From ( Select RowN,Item,Value From #Temp ) A
Pivot (max([Value]) For [Item] in ('' + Stuff((Select Top 1000 '',''+QuoteName(Item) From (Select Distinct Item,Seq=min(R1) over (Partition By Item) from #Temp ) A Order By Seq For XML Path('''')),1,1,'''') + '') ) p''
Exec(#SQL);
'
Exec(#SQL)
Returns
You may notice the only thing specified is Lvl 2... No attribute names or Node Names.
The UDF if Interested
CREATE FUNCTION [dbo].[tvf-XML-Hier](#XML xml)
Returns Table
As Return
with cte0 as (
Select Lvl = 1
,ID = Cast(1 as int)
,Pt = Cast(NULL as int)
,Element = x.value('local-name(.)','varchar(150)')
,Attribute = cast('' as varchar(150))
,Value = x.value('text()[1]','varchar(max)')
,XPath = cast(concat(x.value('local-name(.)','varchar(max)'),'[' ,cast(Row_Number() Over(Order By (Select 1)) as int),']') as varchar(max))
,Seq = cast(1000000+Row_Number() over(Order By (Select 1)) as varchar(max))
,AttData = x.query('.')
,XMLData = x.query('*')
From #XML.nodes('/*') a(x)
Union All
Select Lvl = p.Lvl + 1
,ID = Cast( (Lvl + 1) * 1024 + (Row_Number() Over(Order By (Select 1)) * 2) as int ) * 10
,Pt = p.ID
,Element = c.value('local-name(.)','varchar(150)')
,Attribute = cast('' as varchar(150))
,Value = cast( c.value('text()[1]','varchar(max)') as varchar(max) )
,XPath = cast(concat(p.XPath,'/',c.value('local-name(.)','varchar(max)'),'[',cast(Row_Number() Over(PARTITION BY c.value('local-name(.)','varchar(max)') Order By (Select 1)) as int),']') as varchar(max) )
,Seq = cast(concat(p.Seq,' ',10000000+Cast( (Lvl + 1) * 1024 + (Row_Number() Over(Order By (Select 1)) * 2) as int ) * 10) as varchar(max))
,AttData = c.query('.')
,XMLData = c.query('*')
From cte0 p
Cross Apply p.XMLData.nodes('*') b(c)
)
, cte1 as (
Select R1 = Row_Number() over (Order By Seq),A.*
From (
Select Lvl,ID,Pt,Element,Attribute,Value,XPath,Seq From cte0
Union All
Select Lvl = p.Lvl+1
,ID = p.ID + Row_Number() over (Order By (Select NULL))
,Pt = p.ID
,Element = p.Element
,Attribute = x.value('local-name(.)','varchar(150)')
,Value = x.value('.','varchar(max)')
,XPath = p.XPath + '/#' + x.value('local-name(.)','varchar(max)')
,Seq = cast(concat(p.Seq,' ',10000000+p.ID + Row_Number() over (Order By (Select NULL)) ) as varchar(max))
From cte0 p
Cross Apply AttData.nodes('/*/#*') a(x)
) A
)
Select A.R1
,R2 = IsNull((Select max(R1) From cte1 Where Seq Like A.Seq+'%'),A.R1)
,A.Lvl
,A.ID
,A.Pt
,A.Element
,A.Attribute
,A.XPath
,Title = Replicate('|---',Lvl-1)+Element+IIF(Attribute='','','#'+Attribute)
,A.Value
From cte1 A
/*
Source: http://beyondrelational.com/modules/2/blogs/28/posts/10495/xquery-lab-58-select-from-xml.aspx
Declare #XML xml='<person><firstname preferred="Annie" nickname="BeBe">Annabelle</firstname><lastname>Smith</lastname></person>'
Select * from [dbo].[tvf-XML-Hier](#XML) Order by R1
*/
EDIT 2- Just to help with the Visualization
If you were to simply run the following:
Select * From [dbo].[tvf-XML-Hier](#XML) Order By R1
The Results would Be

i want to remove string after colon in sql server

-- i need data like this F11.20,F13.20,F14.10 in sql server
declare #S varchar(200) = ',F11.20:,F13.20:Sedative, hypnotic o,F14.10:Cocaine abuse, uncom';
select left(#S, charindex(':', #S, charindex(':', #S)+2)-2);
Any Split/Parse Function would do the trick, but you would have to perform secondary logic to clean the parsed string. That said, I modified a parse function to accept any two non-like delimiters (start/end). In this case a , and :
Also, being a Table-Valued-Function, it is easy to incorporate into a CROSS APPLY or as a stand-alone as illustrated below.
Example
Select NewString = Stuff((Select ',' +RetVal
From [dbo].[udf-Str-Extract](#S,',',':')
For XML Path ('')),1,1,'')
Returns
F11.20,F13.20,F14.10
The UDF if Interested
CREATE FUNCTION [dbo].[udf-Str-Extract] (#String varchar(max),#Delimiter1 varchar(100),#Delimiter2 varchar(100))
Returns Table
As
Return (
with cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (IsNull(DataLength(#String),0)) Row_Number() over (Order By (Select NULL)) From (Select N=1 From cte1 N1,cte1 N2,cte1 N3,cte1 N4,cte1 N5,cte1 N6) A ),
cte3(N) As (Select 1 Union All Select t.N+DataLength(#Delimiter1) From cte2 t Where Substring(#String,t.N,DataLength(#Delimiter1)) = #Delimiter1),
cte4(N,L) As (Select S.N,IsNull(NullIf(CharIndex(#Delimiter1,#String,s.N),0)-S.N,8000) From cte3 S)
Select RetSeq = Row_Number() over (Order By N)
,RetPos = N
,RetVal = left(RetVal,charindex(#Delimiter2,RetVal)-1)
From (Select *,RetVal = Substring(#String, N, L) From cte4) A
Where charindex(#Delimiter2,RetVal)>1
/*
Max Length of String 1MM characters
Declare #String varchar(max) = 'Dear [[FirstName]] [[LastName]], ...'
Select * From [dbo].[udf-Str-Extract] (#String,'[[',']]')
*/
EDIT Just to Help with the Visualization
If you executed the TVF alone:
declare #S varchar(200) = ',F11.20:,F13.20:Sedative, hypnotic o,F14.10:Cocaine abuse, uncom';
Select * From [dbo].[udf-Str-Extract](#S,',',':')
Returns
RetSeq RetPos RetVal
1 2 F11.20
2 10 F13.20
3 38 F14.10
EDIT 2 - Execute via Cross Apply
Declare #YourTable table (ID int,SomeString varchar(200))
Insert Into #YourTable values
(1,',F11.20:,F13.20:Sedative, hypnotic o,F14.10:Cocaine abuse, uncom'),
(2,',Z99.55:,Z25.10:Someother text')
Select A.ID
,B.*
From #YourTable A
Cross Apply (
Select NewString = Stuff((Select ',' +RetVal
From [dbo].[udf-Str-Extract](A.SomeString,',',':')
For XML Path ('')),1,1,'')
) B
Returns
ID NewString
1 F11.20,F13.20,F14.10
2 Z99.55,Z25.10
If the text you're extracting is always in the form of (letter, number, number, ., number, number) and there's always 3 instances of that text, then you could do this:
WITH
s1(string, ci) AS (SELECT #S, CHARINDEX(':', #S)),
s2(ci) AS (SELECT CHARINDEX(':', #S, ci+1) FROM s1),
s3(ci) AS (SELECT CHARINDEX(':', #S, ci+1) FROM s2)
SELECT
SUBSTRING(string, s1.ci-6, 6)+','+
SUBSTRING(string, s2.ci-6, 6)+','+
SUBSTRING(string, s3.ci-6, 6)
FROM s1, s2, s3;
Execution Plan:
It doesn't get any more efficient then that.
If its always the 6 characters before any instance of ":" you can grab a copy of NGrams8K and do this:
declare #S varchar(200) = ',F11.20:,F13.20:Sedative, hypnotic o,F14.10:Cocaine abuse, uncom';
SELECT NewString = STUFF
((SELECT ','+SUBSTRING(#S, position-6, 6)
FROM dbo.NGrams8k(#S, 1)
WHERE token = ':'
FOR XML PATH('')),1,1,'');
Another way using NGrams8K and a variable:
declare #S varchar(200) = ',F11.20:,F13.20:Sedative, hypnotic o,F14.10:Cocaine abuse, uncom';
declare #newstring varchar(100)='';
declare #S varchar(200) = ',F11.20:,F13.20:Sedative, hypnotic o,F14.10:Cocaine abuse, uncom';
declare #newstring varchar(100)='';
SELECT
#newstring +=
CASE #newstring WHEN '' THEN '' ELSE ',' END +SUBSTRING(#S, position-6, 6)
FROM dbo.NGrams8k(#S, 1)
WHERE token = ':';
SELECT #newstring;

split semicolon delimiter SQL to rows [duplicate]

This question already has answers here:
T-SQL split string
(27 answers)
Closed 6 years ago.
Just want to ask for help.
I'm trying to split delimited values with a semicolon as a delimiter.
Comma cannot be replaced to the semicolon since there are values that have comma.
ID Value
1 | A&B;C;D;E, F
Transform to:
ID Value
1 A&B
1 C
1 D
1 E, F
I tried tweaking the SQL scripts that i got online but to no success
SELECT F1.ID,
O.splitdata
FROM
(
SELECT OldID,
cast('<X>'+replace((SELECT ColumnName + '' FOR XML PATH('')),';','</X><X>')+'</X>' as XML) as xmlfilter from TableName F
)F1
CROSS APPLY
(
SELECT fdata.D.value('.','varchar(max)') as splitdata
FROM f1.xmlfilter.nodes('X') as fdata(D)) O
It works for some of my columns but if the columns have special or Illegal characters it outputs this error:
Msg 9411, Level 16, State 1, Line 2
XML parsing: line 1, character 16, semicolon expected
Thanks!
If you do not like a function, or if you do not have the rights to create a new function, you can use the quite fast XML approach. In your case it needs some extra effort to get this XML-safe (due to special characters and the ; as delimiter):
Declare #Dummy table (ID int, SomeTextToSplit varchar(max))
Insert Into #Dummy values
(1,'A&B;C;D;E, F')
,(2,'"C" & "D";<C>;D;E, F');
DECLARE #Delimiter VARCHAR(10)=';';
WITH Casted AS
(
SELECT *
,CAST('<x>' + REPLACE((SELECT REPLACE(SomeTextToSplit,#Delimiter,'§§Split$me$here§§') AS [*] FOR XML PATH('')),'§§Split$me$here§§','</x><x>') + '</x>' AS XML) AS SplitMe
FROM #Dummy
)
SELECT Casted.*
,x.value('.','nvarchar(max)') AS Part
FROM Casted
CROSS APPLY SplitMe.nodes('/x') AS A(x)
The result
1 A&B
1 C
1 D
1 E, F
2 "C" & "D"
2 <C>
2 D
2 E, F
Option 1 with a UDF
Declare #YourTable table (ID int, Value varchar(max))
Insert Into #YourTable values
(1,'A&B;C;D;E, F')
Select A.ID
,B.*
From #YourTable A
Cross Apply [dbo].[udf-Str-Parse-8K](A.Value,';') B
Option 2 without a UDF
Select A.ID
,B.*
From #YourTable A
Cross Apply (
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(A.Value,';','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) B
Both Return
ID RetSeq RetVal
1 1 A&B
1 2 C
1 3 D
1 4 E, F
This UDF is XML Safe and VERY fast
CREATE FUNCTION [dbo].[udf-Str-Parse-8K] (#String varchar(max),#Delimiter varchar(25))
Returns Table
As
Return (
with cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (IsNull(DataLength(#String),0)) Row_Number() over (Order By (Select NULL)) From (Select N=1 From cte1 a,cte1 b,cte1 c,cte1 d) A ),
cte3(N) As (Select 1 Union All Select t.N+DataLength(#Delimiter) From cte2 t Where Substring(#String,t.N,DataLength(#Delimiter)) = #Delimiter),
cte4(N,L) As (Select S.N,IsNull(NullIf(CharIndex(#Delimiter,#String,s.N),0)-S.N,8000) From cte3 S)
Select RetSeq = Row_Number() over (Order By A.N)
,RetVal = LTrim(RTrim(Substring(#String, A.N, A.L)))
From cte4 A
);
--Orginal Source http://www.sqlservercentral.com/articles/Tally+Table/72993/
--Much faster than str-Parse, but limited to 8K
--Select * from [dbo].[udf-Str-Parse-8K]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse-8K]('John||Cappelletti||was||here','||')
Please use the function below to split a string by a specific delimiter:
CREATE FUNCTION [dbo].[Split](#String varchar(8000), #Delimiter char(1))
returns #temptable TABLE (SplitValue varchar(8000))
as
begin
declare #idx int
declare #slice varchar(8000)
select #idx = 1
if len(#String)<1 or #String is null return
while #idx!= 0
begin
set #idx = charindex(#Delimiter,#String)
if #idx!=0
set #slice = left(#String,#idx - 1)
else
set #slice = #String
if(len(#slice)>0)
insert into #temptable(SplitValue) values(#slice)
set #String = right(#String,len(#String) - #idx)
if len(#String) = 0 break
end
return
end
Let me know if you have any queries.
Thanks .