Related
I have a string_split function which looks like below
CREATE FUNCTION [dbo].[String_Split](#String varchar(8000), #Delimiter char(1))
returns #temptable TABLE ([value] 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([value]) values(#slice)
set #String = right(#String,len(#String) - #idx)
if len(#String) = 0 break
end
return
end
I am now successfully able to split the string using the below code
declare #NameList nvarchar(100) = 'Hi,Hi1,Hi2';
SELECT * FROM string_split(#NameList,',')
but now I want to split multiple string using the same function like below
declare #NameList nvarchar(100) = 'Hi,Hi1,Hi2';
declare #DESCLIST nvarchar(100) = 'Hii,Hii1,Hii2';
I want to split these strings in different columns,
my expected output is
col 1 col 2
------------------
Hi |Hii
Hi1 |Hii1
Hi2 |Hii2
how can I achieve this?
You can join them on row-number.
SELECT
s1.value,
s2.value
FROM (
SELECT *,
rn = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM STRING_SPLIT(#NameList, ',') s
) s1
JOIN (
SELECT *,
rn = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM STRING_SPLIT(#DESCLIST, ',') s
) s2 ON s2.rn = s1.rn;
db<>fiddle
I strongly suggest you store multiple items in separate rows, rather than a comma-separated list. Consider using a table variable, temporary table or Table Valued Parameter.
How to get split string with quotation on each split item in SQL Server? I have tried this
declare #departmentNames as varchar(max) = 'Account, hod'
--declare #departmentNames as varchar(max) = 'Account'+', '+'hod'
print #departmentNames
I get result like this => Account,hod
but I want it like this => 'Account', 'hod'
so that I could use it in
select *
from tblDepartment
where name in (select item from splitString(#departmentNames, ','))
I know if I use integers with id column it will work fine i.e => 1, 2, 3, 4 but I want to try it with strings.
So is there anyone who can help me with this?
You can use apply :
select td.*
from tblDepartment td cross apply
<shema>.splitString(#departmentNames, ',') spt(item) -- add schema name
where spt.item = td.name;
If you want string comparison, you can do concatenation.
Note : use Schema name while calling UDF function.
First create this function:
CREATE FUNCTION [dbo].[fn_Split]
(#String varchar(8000),
#Delimiter varchar(50))
RETURNS #temptable TABLE (items varchar(8000))
AS
BEGIN
/*
SELECT * FROM dbo.fn_Split('12345;thome', ';')
*/
DECLARE #idx int
DECLARE #slice varchar(8000)
DECLARE #delimiterLength int
SET #delimiterLength = len(#Delimiter)
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(Items)
VALUES (LTRIM(RTRIM(#slice)))
SET #String = RIGHT(#String, LEN(#String) - #idx - #delimiterLength + 1)
IF LEN (#String) = 0
BREAK
END
RETURN
END
After creating this function then you can test with below query.
It splits words with any delimiter you are passing
select items from dbo.fn_Split('ACCOUNT ,HOD',',')
select items from dbo.fn_Split('ACCOUNT ; HOD',';')
Then pass variable and and use join with this function.
Use table alias for easy understanding
declare #departmentNames as varchar(max) = ('Account, hod')
select t.*
from tblDepartment t
inner join
(Select items
from dbo.fn_Split (#departmentNames, ',')) A on t.name = A.items
I create temptable for testing and this query will return output like below
Suppose I have 2 variables that look like an array:
declare #code nvarchar(200) =
',10501,10203,10491,10490,10091,10253,10008,10020,10570,10499,';
declare #value nvarchar(200) =
'True~~100000006~Digital~0~0~~1388.76~Completed~True';
I need to find if #code contains 10490 (for example) and if it does, I need to find a corresponding value (by its index) in #value variable which would be Digital since 10490 is the 4th element in #code array and 4th element of #value array is Digital (note that the 2nd element of the #value array is NULL.
Disclaimer:
#code array will ALWAYS contain unique values. It's not possible to have more than 1 10490 for example.
#code array will always start and end with ','.
Number of elements in #code and #value will always be the same if you take 1st and last comma off the #code variable.
I cannot use functions or stored procedures, so everything needs to be done as part of 1 query.
I think you know, that this is a very bad design... If you can change this, you really should. But this can be solved:
declare #code nvarchar(200) =
',10501,10203,10491,10490,10091,10253,10008,10020,10570,10499,';
declare #value nvarchar(200) =
'True~~100000006~Digital~0~0~~1388.76~Completed~True';
--The query will cast both strings to a splittable XML
--The query('/x[text()]') will remove empty entries (leading and trailing comma)
--(...assuming there will never be an empty entry in #code)
--Then it will read a derived numbered list from both
--finally it will join both lists on their PartIndex
WITH Casted AS
(
SELECT CAST('<x>' + REPLACE(#code,',','</x><x>') + '</x>' AS XML).query('/x[text()]') AS CodeXml
,CAST('<x>' + REPLACE(#value,'~','</x><x>') + '</x>' AS XML) AS ValueXml
)
,CodeDerived AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS PartIndex
,x.value('text()[1]','nvarchar(max)') AS CodePart
FROM Casted
CROSS APPLY CodeXml.nodes('/x') A(x)
)
,ValueDerived AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS PartIndex
,x.value('text()[1]','nvarchar(max)') AS ValuePart
FROM Casted
CROSS APPLY ValueXml.nodes('/x') A(x)
)
SELECT cd.PartIndex
,CodePart
,ValuePart
FROM CodeDerived cd
INNER JOIN ValueDerived vd ON cd.PartIndex=vd.PartIndex
The result
inx CodePart ValuePart
1 10501 True
2 10203 NULL
3 10491 100000006
4 10490 Digital
5 10091 0
6 10253 0
7 10008 NULL
8 10020 1388.76
9 10570 Completed
10 10499 True
Just add a simple WHERE to reduce this to the one value you need.
Disclaimer: it is not guaranteed, that the numbering with ROW_NUMBER and ORDER BY (SELECT NULL) will ever return the correct sequence, but for a better chance you'd need SQL Server 2016+. For more details: read this link and the other contributions there
Here are two possibilities. In your case I would even try to merge it into one WHILE loop.
SQL Server 2016 and above
(compatibility level 130 and up) you can use built in function STRING_SPLIT
DECLARE #code nvarchar(200) =
',10501,10203,10491,10490,10091,10253,10008,10020,10570,10499,';
DECLARE #value nvarchar(200) =
'True~~100000006~Digital~0~0~~1388.76~Completed~True';
DECLARE #valuetosearch nvarchar(200) = '10490'
SELECT value FROM
(
SELECT value ,ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS 'idx'
FROM STRING_SPLIT ( #value , '~' )
) AS x2
WHERE x2.idx =
(
SELECT idx-1 FROM
(
SELECT value ,ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS 'idx'
FROM STRING_SPLIT ( #code , ',' )
) AS x1
WHERE x1.[value] = #valuetosearch
)
For earlier versions of SQL Server:
DECLARE #code nvarchar(200) =
',10501,10203,10491,10490,10091,10253,10008,10020,10570,10499,';
DECLARE #value nvarchar(200) =
'True~~100000006~Digital~0~0~~1388.76~Completed~True';
DECLARE #valuetosearch nvarchar(200) = '10490'
DECLARE #codetbl AS TABLE (idx int IDENTITY(1,1)
,code nvarchar(200))
DECLARE #valuetbl AS TABLE (idx int IDENTITY(1,1)
,value nvarchar(200))
DECLARE #name nvarchar(200)
DECLARE #pos int
WHILE CHARINDEX(',', #code) > 0
BEGIN
SELECT #pos = CHARINDEX(',', #code)
SELECT #name = SUBSTRING(#code, 1, #pos-1)
INSERT INTO #codetbl
SELECT #name
SELECT #code = SUBSTRING(#code, #pos+1, LEN(#code)-#pos)
END
INSERT INTO #codetbl
SELECT #code
WHILE CHARINDEX('~', #value) > 0
BEGIN
SELECT #pos = CHARINDEX('~', #value)
SELECT #name = SUBSTRING(#value, 1, #pos-1)
INSERT INTO #valuetbl
SELECT #name
SELECT #value = SUBSTRING(#value, #pos+1, LEN(#value)-#pos)
END
INSERT INTO #valuetbl
SELECT #value
SELECT value FROM #valuetbl
WHERE idx = (SELECT idx-1 FROM #codetbl WHERE code = #valuetosearch)
You may need to add some code for when #tofind is not found
declare #code nvarchar(200) =
',10501,10203,10491,10490,10091,10253,10008,10020,10570,10499,';
declare #value nvarchar(200) =
'True~~100000006~Digital~0~0~~1388.76~Completed~True';
declare #tofind nvarchar(200) = '10490';
--select left(#code,CHARINDEX(#tofind,#code))
--select len(left(#code,CHARINDEX(#tofind,#code))) - LEN( REPLACE( left(#code,CHARINDEX(#tofind,#code)) , ',', ''))
declare #nth int;
set #nth = len(left(#code,CHARINDEX(#tofind,#code))) - LEN( REPLACE( left(#code,CHARINDEX(#tofind,#code)) , ',', ''))
declare #SplitOn nvarchar = '~';
declare #RowData nvarchar(200) = #value + '~';
declare #Cnt int = 1
While (Charindex(#SplitOn,#RowData)>0) and #Cnt < #nth
Begin
Set #RowData = Substring(#RowData,Charindex(#SplitOn,#RowData)+1,len(#RowData))
Set #Cnt = #Cnt + 1
End
Select --Data = ltrim(rtrim(#RowData)),
Case when ltrim(rtrim(#RowData)) = '' then null else
LEFT(ltrim(rtrim(#RowData)) , CHARINDEX('~',ltrim(rtrim(#RowData))) -1)
end as Result
This should be quite simple. If performance is important I would suggest splitting the strings using DelimitedSplit8K. Here's a simple, high-performing solution:
DECLARE #searchFor INT = 10490;
SELECT code = s1.item, s2.item
FROM dbo.DelimitedSplit8K(#code,',') s1
JOIN dbo.DelimitedSplit8K(#value,'~') s2 ON s2.ItemNumber = s1.ItemNumber-1
WHERE s1.Item = #searchFor;
Results:
code item
---------- ------------
10490 Digital
I wrote a sql server function which returns substring before the Nth occurence of character.
For example,
SELECT dbo.fn_getFirstNthSentence('.', 'hello world.It.is.raining.today', 3)
returns 'hello world.It.Is.' as a result.
The function I wrote looks dirty and slow so I want to optimize it.
Any advice to make it clean is appreciated.
Thank you.
CREATE FUNCTION fn_getFirstNthSentence
(
#TargetStr VARCHAR(MAX) ,
#SearchedStr VARCHAR(8000) ,
#Occurrence INT
)
RETURNS varchar(MAX)
AS
BEGIN
DECLARE #pos INT ,
#counter INT ,
#ret INT;
SET #pos = CHARINDEX(#TargetStr, #SearchedStr);
IF ( #pos = 0 )
RETURN #SearchedStr
SET #counter = 1;
IF #Occurrence = 1
SET #ret = #pos;
ELSE
BEGIN
WHILE ( #counter < #Occurrence )
BEGIN
IF(LEN(#SearchedStr) < #pos + 1)
RETURN #SearchedStr
SELECT #ret = CHARINDEX(#TargetStr, #SearchedStr,
#pos + 1);
IF(#ret = 0)
RETURN #SearchedStr
SET #counter = #counter + 1;
SET #pos = #ret;
END;
END;
RETURN LEFT(#SearchedStr, #ret)
END;
Here is yet another option using a delimited string splitter. The XML method already posted is a good one but this approach does not require a table variable.
This is created as an inline table valued function which should keep the performance really fast.
create function fn_getFirstNthSentence
(
#SearchedStr varchar(100)
, #Occurrence int
, #Delimiter char(1)
) returns table as return
with ParsedValues as
(
select Item
, ItemNumber
from dbo.DelimitedSplit8K(#SearchedStr, #Delimiter)
where ItemNumber <= #Occurrence
)
select top 1 ResultString = STUFF(
(
select #Delimiter + Item
from ParsedValues
order by ItemNumber
for xml path('')), 1,1, '') + #Delimiter
from ParsedValues
This is also using a splitter created by Jeff Moden. It has one feature that none of the other splitter have...a column to indicate which position the value came from. You can find his article an ensuing discussion here. http://www.sqlservercentral.com/articles/Tally+Table/72993/
Then if you want to execute it you can do this quite simply.
declare #String varchar(100) = 'hello world.It.is.raining.today.'
, #Num int = 3
, #Delimiter char(1) = '.'
;
select *
from fn_getFirstNthSentence(#String, #Num, #Delimiter)
If you don't like Jeff Moden's splitter you can find several other options here. http://sqlperformance.com/2012/07/t-sql-queries/split-strings I don't use Moden's for everything but when you need to keep the parsed values in order it is awesome.
--EDIT--
Here is how you could modify this to become a scalar function instead of an inline table valued function. My preference would be to keep the itvf as they are faster and more flexible.
create function fn_getFirstNthSentenceScalar
(
#SearchedStr varchar(100) = 'hello world.It.is.raining.today.this is after 5'
, #Occurrence int = 5
, #Delimiter char(1) = '.'
) returns varchar(max) as begin
declare #RetVal varchar(max);
with ParsedValues as
(
select Item
, ItemNumber
from dbo.DelimitedSplit8K(#SearchedStr, #Delimiter)
where ItemNumber <= #Occurrence
)
select top 1 #RetVal = STUFF(
(
select #Delimiter + Item
from ParsedValues
order by ItemNumber
for xml path('')), 1,1, '') + #Delimiter
from ParsedValues;
return #RetVal
end
--I find these functions to be a mine-field, and at the risk of stepping on a mine I've tried some simplifications - maybe a microscopic improvement in performance
alter FUNCTION fn_getFirstNthSentence
(
#TargetStr VARCHAR(MAX) ,
#SearchedStr VARCHAR(8000) ,
#Occurrence INT
)
RETURNS varchar(MAX)
AS
BEGIN
DECLARE #pos INT ,
#counter INT ;
IF #Occurrence < 1
RETURN NULL;
SELECT #counter = 0, #POS = 1;
WHILE (#counter < #Occurrence AND #POS > 0)
BEGIN
SELECT #POS = CHARINDEX(#TargetStr, #SearchedStr,
#pos + 1);
IF #POS > 0
SET #counter = #counter + 1;
END;
RETURN CASE WHEN #POS > 0 THEN
LEFT(#SearchedStr, #POS)
ELSE
#SearchedStr
END;
END;
Another option is via XML
I can't see your benchmarks, but it is certainly far less code. An added option could be Find the 3rd through 5th occurrence by adding a parameter and changing the Where Seq<=#FindPos to Where Seq Between range1 and range2.
Declare #FindPos int = 3
Declare #String varchar(max) = 'hello world.It.is.raining.today'
Declare #Delim varchar(10) = '.'
Declare #XML xml,#RetVal varchar(max) = ''
Set #XML = Cast('<x>' + Replace(#String,#Delim,'</x><x>')+'</x>' as XML)
Declare #Table table (Seq int identity(1,1),String varchar(max))
Insert Into #Table Select ltrim(rtrim(String.value('.', 'varchar(max)')))+#Delim as value FROM #XML.nodes('x') as T(String)
Select #RetVal=#RetVal + String from #Table Where Seq<=#FindPos Order By Seq
Select #RetVal
Returns
hello world.It.is.
EDIT: If it helps, below is my generic parsing function which returns a
normalized table...
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#Delimeter varchar(10))
--Usage: Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
-- Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
-- Select * from [dbo].[udf-Str-Parse]('id26,id46|id658,id967','|')
-- Select * from [dbo].[udf-Str-Parse]('hello world. It. is. . raining.today','.')
Returns #ReturnTable Table (Key_PS int IDENTITY(1,1), Key_Value varchar(max))
As
Begin
Declare #XML xml;Set #XML = Cast('<x>' + Replace(#String,#Delimeter,'</x><x>')+'</x>' as XML)
Insert Into #ReturnTable Select Key_Value = ltrim(rtrim(String.value('.', 'varchar(max)'))) FROM #XML.nodes('x') as T(String)
Return
End
So for example:
Select * from [dbo].[udf-Str-Parse]('hello world.It.is.raining.today','.')
Returns
Key_PS Key_Value
1 hello world
2 It
3 is
4 raining
5 today
I have the following three different strings which needs to split into three different columns.
Example:
String 1:
Declare #str1 varchar(max) = 'A1,A2,A3'
String 2:
Declare #str2 varchar(max) = 'B1,B2,B3'
String 3:
Declare #str2 varchar(max) = 'C1,C2,C3'
NoteI want to store the above three strings into three different columns.
Expected Output:
colA colB colC
------------------
A1 B1 C1
A2 B2 C2
A3 B3 C3
Attempt:
SQL Fiddle: http://sqlfiddle.com/#!3/d41d8/41345
I know its a bit heavy but it will work
Declare #str1 varchar(max) = 'A1,A2,A3'
Declare #str2 varchar(max) = 'B1,B2,B3'
Declare #str3 varchar(max) = 'C1,C2,C3'
DECLARE #RowCount TINYINT
DECLARE #i TINYINT = 0
DECLARE #Table AS TABLE
(
colA varchar(MAX)
,ColB varchar(MAX)
,ColC varchar(MAX)
)
SET #RowCount = len(#str1) - len(replace(#str1, ',', ''))
WHILE(#i<=#RowCount)
BEGIN
INSERT INTO #Table
SELECT LEFT(#str1,CHARINDEX(',',#str1+',',0)-1) AS colA
,LEFT(#str2,CHARINDEX(',',#str2+',',0)-1) AS colB
,LEFT(#str3,CHARINDEX(',',#str3+',',0)-1) AS colC
SET #str1 = STUFF(#str1,1,CHARINDEX(',',#str1,0),'')
SET #str2 = STUFF(#str2,1,CHARINDEX(',',#str2,0),'')
SET #str3 = STUFF(#str3,1,CHARINDEX(',',#str3,0),'')
SET #i = #i + 1
END
SELECT * FROM #Table
If you have atmost three values then try this.
DECLARE #str1 VARCHAR(max) = 'A1,A2,A3'
SELECT Parsename(Replace(#str1, ',', '.'), 3) 'FST_COL',
Parsename(Replace(#str1, ',', '.'), 2) 'SCD_COL',
Parsename(Replace(#str1, ',', '.'), 1) 'TRD_COL' into #temp
Or
DECLARE #str1 VARCHAR(max) = 'A1,A2,A3',
#sql NVARCHAR(max),
#loop INT,
#cnt INT=1
SELECT #loop = Len(#str1) - Len(Replace(#str1, ',', '')) + 1
SET #sql=' WITH Split_cols ( xmlcol)
AS (SELECT CONVERT(XML, ''<cols><col>''
+ Replace('''
+ #str1 + ''', '','', ''</col><col>'') + ''</col></cols>'') as xmlcol)
SELECT '
WHILE #cnt <= #loop
BEGIN
SET #sql+=' xmlcol.value(''/cols[1]/col['
+ CONVERT(VARCHAR(30), #cnt)
+ ']'', ''varchar(100)'') AS col'
+ CONVERT(VARCHAR(30), #cnt) + ','
SET #cnt=#cnt + 1
END
SET #sql=LEFT(#sql, Len(#sql) - 1)
SET #sql +=' FROM Split_cols '
--PRINT #sql
EXEC Sp_executesql #sql
THIS WILL WORK WITH ANY NUMBER OF STRINGS AND VALUES NOT HARDCODED
CREATE FUNCTION dbo.splitstring (#stringToSplit VARCHAR(MAX) )
RETURNS
#returnList TABLE ([ID] INT IDENTITY(1,1),[Name] [nvarchar] (500))
AS
BEGIN
DECLARE #name NVARCHAR(255)
DECLARE #pos INT
WHILE CHARINDEX(',', #stringToSplit) > 0
BEGIN
SELECT #pos = CHARINDEX(',', #stringToSplit)
SELECT #name = SUBSTRING(#stringToSplit, 1, #pos-1)
INSERT INTO #returnList
SELECT #name
SELECT #stringToSplit = SUBSTRING(#stringToSplit, #pos+1, LEN(#stringToSplit)-#pos)
END
INSERT INTO #returnList
SELECT #stringToSplit
RETURN
END
-- USE THIS PARAMETER TO PASS VALUE
DECLARE #STRING VARCHAR(MAX)
DECLARE #COUNT INT
DECLARE #I INT
DECLARE #COLUMNNAME VARCHAR(MAX)
DECLARE #CREATETABLE VARCHAR(MAX)
DECLARE #INSERT VARCHAR(MAX)
IF OBJECT_ID('TEMPDB..##TEMPTABLE') IS NOT NULL
DROP TABLE ##TEMPTABLE
IF OBJECT_ID('TEMPDB..##RETURNLIST') IS NOT NULL
DROP TABLE ##RETURNLIST
SELECT * INTO ##RETURNLIST FROM dbo.splitstring(#STRING)
select #COUNT = COUNT(*) from ##RETURNLIST
SET #I=0
SET #CREATETABLE = 'CREATE TABLE ##TEMPTABLE ('
WHILE (#COUNT>0)
BEGIN
SET #COLUMNNAME = 'COLUMN'+ CONVERT(varchar(10), #I) + ' VARCHAR(MAX)'
SET #CREATETABLE = #CREATETABLE + #COLUMNNAME
IF(#COUNT<>1)
SET #CREATETABLE = #CREATETABLE + ', '
SET #I = #I+1
SET #COUNT = #COUNT -1;
END
SET #CREATETABLE = #CREATETABLE + ' )'
EXECUTE(#CREATETABLE)
SET #INSERT = 'INSERT INTO ##TEMPTABLE VALUES( '
WHILE (#I>0)
BEGIN
SET #INSERT = #INSERT +''''+ (SELECT NAME FROM ##RETURNLIST WHERE ID = #COUNT+1) +''''
IF(#I<>1)
SET #INSERT = #INSERT + ', '
SET #I = #I-1
SET #COUNT = #COUNT +1;
ENDenter code here
SET #INSERT = #INSERT + ' )'
EXECUTE(#INSERT)
EXECUTE('SELECT * FROM ##TEMPTABLE')
You haven't given any criteria for joining the values together for each of the rows of columns so I've just joined them in descending order. This solution can handle any number of items in the lists but if the number gets too high you may need to use the MAX RECURSION query hint.
DECLARE #strA NVARCHAR(MAX) = 'A1,A2,A3';
DECLARE #strB NVARCHAR(MAX) = 'B1,B2,B3,B4';
DECLARE #strC NVARCHAR(MAX) = 'C1,C2,C3';
WITH stringData AS (
--each group of comma separate values with a group identifier
SELECT 'a' AS grp, #strA AS strng
UNION ALL
SELECT 'b' AS grp, #strB
UNION ALL
SELECT 'c' AS grp, #strC
),
splitStrings AS (
--a recursive CTE to split the comma separated values
SELECT grp, CAST('' AS NVARCHAR(MAX)) AS item,
strng AS cText
FROM stringData
UNION ALL
SELECT grp,
CASE
WHEN CHARINDEX(N',',cText,0)>0 THEN LEFT(cText,CHARINDEX(N',',cText,0)-1) --SUBSTRING(cText,0,CHARINDEX(N',',cText,0))
ELSE cText
END,
RIGHT(cText,LEN(cText)-CHARINDEX(N',',cText,0))
FROM splitStrings
WHERE cText!=item
)
SELECT grp,
item,
ROW_NUMBER() OVER(PARTITION BY grp ORDER BY item) AS rnum
INTO #stringValues --put the results in a temp table so we don't need to execute the recursive CTE more than once
FROM splitStrings
WHERE len(item)>0;
DECLARE #maxNum INT = (SELECT MAX(rnum) FROM #stringValues);
--join the values together
WITH allNums AS (
SELECT 1 AS num
UNION ALL
SELECT num+1
FROM allNums
WHERE num<#maxNum
)
SELECT sa.item AS colA,
sb.item AS colB,
sc.item AS colC
FROM allNums
LEFT JOIN #stringValues AS sa ON sa.rnum=allNums.num AND sa.grp='A'
LEFT JOIN #stringValues AS sb ON sb.rnum=allNums.num AND sb.grp='B'
LEFT JOIN #stringValues AS sc ON sc.rnum=allNums.num AND sc.grp='C'
DROP TABLE #stringValues;