Row value (with ,) values to column in SQL - sql

I am trying to do this in SQL: data in column is like this need to separate them into new columns.
Create table #TEST4 ( NAME VARCHAR(25) )
INSERT INTO #TEST4
VALUES ( 'a,b,c,d,e')
,( 'ax,bde,c,ded,es')
select name from #TEST4
Expecting result like this, any suggestions will be appreciated.
enter image description here

Using a bit of JSON in concert with a CROSS APPLY
Select Name1 = JSON_VALUE(JS,'$[0]')
,Name2 = JSON_VALUE(JS,'$[1]')
,Name3 = JSON_VALUE(JS,'$[2]')
,Name4 = JSON_VALUE(JS,'$[3]')
From #TEST4 A
Cross Apply (values ( '["'+replace(string_escape(NAME,'json'),',','","')+'"]' ) ) B(JS)
Results
Name1 Name2 Name3 Name4
a b c d
ax bde c ded
Update: XML Approach
Select Name1 = ltrim(rtrim(xDim.value('/x[1]','varchar(max)'))) -- choose the proper datatype
,Name2 = ltrim(rtrim(xDim.value('/x[2]','varchar(max)')))
,Name3 = ltrim(rtrim(xDim.value('/x[3]','varchar(max)')))
,Name4 = ltrim(rtrim(xDim.value('/x[4]','varchar(max)')))
From #Test4 A
Cross Apply ( values (cast('<x>' + replace((Select replace(NAME,',','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml))) B(xDim)

Related

SQL - separate string into different columns

I am trying to separate strings into different columns which are separated by commas. I tried all the article that is on stackoverflow but not successful.
Example:
Column1
mouse,monitor,keyboard
cable,mouse
headset,desk,cable,monitor,usb,charger
Expected results:
Column1 |Column2 |Column3 |Column4 |Column5 |Column6
mouse |monitor |keyboard | NULL | NULL | NULL
cable |mouse |NULL | NULL | NULL | NULL
headset |desk |cable | monitor | usb | charger
Please note that the strings under Column1 can be as many as 10 strings and the strings are different every week so they are undefined.
This is one of the code I tried:
Declare #TblName (id int, Column1 varchar(max))
Insert into #TblName
Select A.Column1
,B.*
From #TblNameK 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(A.Column1,',','</x><x>')+'</x>' as XML) as xDim) A
) B
You can use XML method below :
DECLARE
#t TABLE (keywords VARCHAR(MAX) )
INSERT INTO #t VALUES
('mouse,monitor,keyboard'),
('cable,mouse'),
('headset,desk,cable,monitor,usb,charger'),
('M&M,Hot&Cold,sneakers')
SELECT
ROW_NUMBER() OVER(ORDER BY keywords DESC) ID
, keywords
FROM (
SELECT
LTRIM(RTRIM(m.n.value('.[1]','VARCHAR(8000)'))) keywords
FROM (
SELECT CAST('<Root><Keyword>' + REPLACE(REPLACE(keywords,'&','&') ,',','</Keyword><Keyword>') + '</Keyword></Root>' AS XML) keywords
FROM #t
) D
CROSS APPLY keywords.nodes('/Root/Keyword')m(n)
) C
This will put each keyword in a row. From there you can count the number of keywords and do further stuff on them (like getting the distinct values, pivot them ..etc).
Since you are using SQL Server 2016, you can use the built-in string_split() function:
declare #t table (Value varchar(max));
insert into #t (Value)
values
('mouse,monitor,keyboard'),
('cable,mouse'),
('headset,desk,cable,monitor,usb,charger')
;
select *
from #t t
cross apply string_split(t.Value, ',') ss;
Having all the values in one column will be especially handy if you are actually going to get some aggregated statistics out of them.

What's the most efficient way to normalize text from column into a table?

In T-SQL I have a column with some text in it with a format like the following:
[Key1:Value1:Value2:Value3:Value4:Value5]
[Key2:Value1:Value2:Value3:Value4:Value5]
[Key3:Value1:Value2:Value3:Value4:Value5]
where there can be any number of bracket sets, but usually between 3 and 6. I'm looking for a way to quickly format them into a temp table or table variable so I can report on the data. For example, I'd want the table format to be:
|Key|Column 1|Column 2|Column 3|Column 4|Column 5|
|Key 1|Value 1|Value 2|Value 3|Value 4|Value 5|
|Key 2|Value 1|Value 2|Value 3|Value 4|Value 5|
|Key 3|Value 1|Value 2|Value 3|Value 4|Value 5|
I know this is pushing the limits of SQL and should be handled through modification of the application, but I'm hoping there's something clever I can do with T-SQL for now.
If you have a maximum number of columns, a little XML within a CROSS APPLY.
If unknown, you would have to go DYNAMIC.
Example
Declare #YourTable Table ([ID] varchar(50),[SomeCol] varchar(50))
Insert Into #YourTable Values
(1,'[Key1:Value1:Value2:Value3:Value4:Value5]')
,(2,'[Key2:Value1:Value2:Value3:Value4:Value5]')
,(3,'[Key3:Value1:Value2:Value3:Value4:Value5]')
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(replace(replace(SomeCol,'[',''),']',''),':','</x><x>')+'</x>' as xml) as xDim) as A
) B
Returns
ID Pos1 Pos2 Pos3 Pos4 Pos5 Pos6 Pos7 Pos8 Pos9
1 Key1 Value1 Value2 Value3 Value4 Value5 NULL NULL NULL
2 Key2 Value1 Value2 Value3 Value4 Value5 NULL NULL NULL
3 Key3 Value1 Value2 Value3 Value4 Value5 NULL NULL NULL
EDIT
I should add, the ltrim(rtrim(...)) is optional and the varchar(max) is my demonstrative default.
EDIT - One String delimited with CRLF
Declare #S varchar(max)='
[Key1:Value1:Value2:Value3:Value4:Value5]
[Key2:Value1:Value2:Value3:Value4:Value5]
[Key3:Value1:Value2:Value3:Value4:Value5]
'
Select B.*
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(#S,char(13)+char(10),'</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) 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(replace(replace(RetVal,'[',''),']',''),':','</x><x>')+'</x>' as xml) as xDim) as A
) B
Where A.RetVal is not null
The fastest way to split a string when you know the maximum number of columns is to use the Cascading CROSS APPLY technique. Let's say you know that their will be no more than 10 items in your string. You could do this:
DECLARE #string varchar(1000) = '[Key1:Value1:Value2:Value3:Value4:Value5]'
SELECT
[key] = SUBSTRING(t.string,1,d1.d-1),
col1 = SUBSTRING(t.string,d1.d+1,d2.d-d1.d-1),
col2 = SUBSTRING(t.string,d2.d+1,d3.d-d2.d-1),
col3 = SUBSTRING(t.string,d3.d+1,d4.d-d3.d-1),
col4 = SUBSTRING(t.string,d4.d+1,d5.d-d4.d-1),
col5 = SUBSTRING(t.string,d5.d+1,d6.d-d5.d-1),
col6 = SUBSTRING(t.string,d6.d+1,d7.d-d5.d-1),
col7 = SUBSTRING(t.string,d7.d+1,d8.d-d5.d-1),
col8 = SUBSTRING(t.string,d8.d+1,d9.d-d5.d-1),
col9 = SUBSTRING(t.string,d9.d+1,d10.d-d5.d-1)
FROM (VALUES (REPLACE(REPLACE(#string,']',':'),'[',''))) t(string)
CROSS APPLY (VALUES (CHARINDEX(':',t.string))) d1(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d1.d+1),0))) d2(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d2.d+1),0))) d3(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d3.d+1),0))) d4(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d4.d+1),0))) d5(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d5.d+1),0))) d6(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d6.d+1),0))) d7(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d7.d+1),0))) d8(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d8.d+1),0))) d9(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d9.d+1),0))) d10(d);
To use this technique against a table with the strings stored in rows would be like this:
DECLARE #table TABLE (someid int identity, somestring varchar(1000));
INSERT #table(somestring) VALUES
('[Key1:Value1:Value2:Value3:Value4:Value5]'),
('[Key2:Value1:Value2:Value3:Value4:Value5]'),
('[Key3:Value1:Value2:Value3:Value4:Value5]'),
('[Key4:Value1:Value2:Value3:Value4:Value5:Value6:Value7:Value8]'),
('[Key5:Value1:Value2:Value3:Value4:Value5:Value6:Value7:Value8:Value9:Value10]');
SELECT *
FROM #table s
CROSS APPLY
(
SELECT
[key] = SUBSTRING(t.string,1,d1.d-1),
dCount = LEN(t.string)-LEN(REPLACE(t.string,':','')),
col1 = SUBSTRING(t.string,d1.d+1,d2.d-d1.d-1),
col2 = SUBSTRING(t.string,d2.d+1,d3.d-d2.d-1),
col3 = SUBSTRING(t.string,d3.d+1,d4.d-d3.d-1),
col4 = SUBSTRING(t.string,d4.d+1,d5.d-d4.d-1),
col5 = SUBSTRING(t.string,d5.d+1,d6.d-d5.d-1),
col6 = SUBSTRING(t.string,d6.d+1,d7.d-d6.d-1),
col7 = SUBSTRING(t.string,d7.d+1,d8.d-d7.d-1),
col8 = SUBSTRING(t.string,d8.d+1,d9.d-d8.d-1),
col9 = SUBSTRING(t.string,d9.d+1,d10.d-d9.d-1)
FROM (VALUES (REPLACE(REPLACE(s.somestring,']',':'),'[',''))) t(string)
CROSS APPLY (VALUES (CHARINDEX(':',t.string))) d1(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d1.d+1),0))) d2(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d2.d+1),0))) d3(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d3.d+1),0))) d4(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d4.d+1),0))) d5(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d5.d+1),0))) d6(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d6.d+1),0))) d7(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d7.d+1),0))) d8(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d8.d+1),0))) d9(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d9.d+1),0))) d10(d)
) split
WHERE LEN(s.somestring)-LEN(REPLACE(s.somestring,':','')) < 10
If you don't know the maximum number of possible items you could take this logic and wrap it in some Dynamic SQL that creates the correct number of CROSS APPLY's. I don't have time to put together that logic but, to get the maximum number of possible delimiters you could do something like this:
DECLARE #maxDelimiters tinyint =
(SELECT MAX(LEN(s.somestring)-LEN(REPLACE(s.somestring,':',''))) FROM #table s);
Alternatively, if you wanted to use John's technique, you could also use Dynamic SQL to create his query with the exact number of "pos" values required.

Custom split email column names into multiple columns in SQL

I have an email column with 3-4 emails in each row which i want to split into one email per column:
Current columns looks like this:
Email_column
1. drone#gmail.com bob#yahoo.com drake#gmail.com
Expected output should be:
Email_1 Email_2 Email_3
1. drone#email.com bob#yahoo.com drake#gmail.com
With a CROSS APPLY and a little XML
Example
Declare #YourTable table (ID int,Email_column varchar(max))
Insert Into #YourTable values
(1,'drone#gmail.com bob#yahoo.com drake#gmail.com')
Select A.ID
,B.*
From #YourTable A
Cross Apply (
Select Pos1 = n.value('/x[1]','varchar(max)')
,Pos2 = n.value('/x[2]','varchar(max)')
,Pos3 = n.value('/x[3]','varchar(max)')
,Pos4 = n.value('/x[4]','varchar(max)')
From (Select Cast('<x>' + replace(A.Email_column,' ','</x><x>')+'</x>' as xml) as n) X
) B
Returns
ID Pos1 Pos2 Pos3 Pos4
1 drone#gmail.com bob#yahoo.com drake#gmail.com NULL

How to convert multiple values in the row with comma separator into multiple columns?

How to convert multiple comma separated values in rows into multiple columns in SQL Server like I have a table with two rows
A1,1,B1,2
C1,3,D4,4
I want output like this
col1 col2 col3 col4
A1 1 B1 2
C1 3 D4 4
Another option if you have a finite or max number of columns
Example
Declare #YourTable Table ([YourCol] varchar(50))
Insert Into #YourTable Values
('A1,1,B1,2')
,('C1,3,D4,4')
Select 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(A.YourCol,',','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml) as xDim) as x
) B
Returns
Pos1 Pos2 Pos3 Pos4 Pos5 Pos6
A1 1 B1 2 NULL NULL
C1 3 D4 4 NULL NULL
EDIT - Just for Fun, Here is a Dynamic Version of the Above
Just replace YourTable with your actual table name, and YourCol with the desired column to split.
Declare #SQL nvarchar(max)
Set #SQL = Stuff((Select concat(',Col',N,' = ltrim(rtrim(xDim.value(''/x[',N,']'',''varchar(max)'')))')
From (
Select Top ((Select max(len(YourCol)-len(replace(YourCol,',','')))+1 From YourTable))
N=Row_Number() Over (Order By (Select NULL))
From master..spt_values
) A
For XML Path ('')),1,1,'')
Set #SQL = '
Select A.*,B.*
From YourTable A
Cross Apply ( Select ' + #SQL +' From (Select Cast(''<x>'' + replace((Select replace(A.YourCol,'','',''§§Split§§'') as [*] For XML Path('''')),''§§Split§§'',''</x><x>'')+''</x>'' as xml) as xDim ) x ) B
'
--Print #SQL
Exec(#SQL)
Returns
YourCol Col1 Col2 Col3 Col4
A1,1,B1,2 A1 1 B1 2
C1,3,D4,4 C1 3 D4 4
Assuming your table name is t, you can follow the steps below
Step 1: Split the columns using the CSV tally type splitter
Step 2: PIVOT out the values
In a single query the solution will be
select * from
(
select
t.col as col,
row_number() over (partition by t.col order by t1.N asc) as row_num,
SUBSTRING( t.col, t1.N, ISNULL(NULLIF(CHARINDEX(',',t.col,t1.N),0)-t1.N,4000)) as split_values
from t
join
(
select
t.col,
1 as N
from t
UNION ALL
select
t.col,
t1.N + 1 as N
from t
join
(
select
top 4000
row_number() over(order by (select NULL)) as N
from
sys.objects s1
cross join
sys.objects s2
) t1
on SUBSTRING(t.col,t1.N,1) = ','
) t1
on t1.col=t.col
)src
PIVOT
( max(split_values) for row_num in ([1],[2],[3],[4],[5],[6],[7],[8]))p
working demo
PS: You can actually use a dynamic pivot if you do not know the maximum commas in the columns.

SQL: How to create columns dynamically

I have a table which is created dynamically. So the number of columns is unknown at the time of creation. I want to create copies of each column in the same table with first column holding the first part of comma separated value, second column the second part and so on
For example,
ID Value1 Value2 .... Valuen
1 1;2;3 4;5;6
2 A;B;C D;E;F
I want to get the output like
ID Value1Copy1 Value1Copy2 Value1Copy3 Value2Copy1 Value2Copy2 Value2Copy3 .... ValuenCopy1
1 1 2 3 4 5 6
2 A B C D E F
I am unable to achieve this for variable number of columns
The following will dynamically unpivot your data. You may notice that the only field specified is ID.
The results are dropped into a #Temp table. From there we perform a dynamic pivot
Example
Declare #YourTable table (ID int,Value1 varchar(50),Value2 varchar(50))
Insert Into #YourTable values
( 1, '1;2;3','4;5;6'),
( 2, 'A;B;C','D;E;F')
Select A.ID
,Col = concat(C.Item,'Copy',D.RetSeq)
,Value = D.RetVal
Into #Temp
From #YourTable A --<< Replace with Your actual table
Cross Apply (Select XMLData = cast((Select A.* For XML Raw) as xml ) ) B
Cross Apply (
Select Item = attr.value('local-name(.)','varchar(100)')
,Value = attr.value('.','varchar(max)')
From B.XMLData.nodes('/row') as A(r)
Cross Apply A.r.nodes('./#*') AS B(attr)
Where attr.value('local-name(.)','varchar(100)') not in ('ID','Other2Exclude')
) C
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(C.Value,';','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) D
Where A.ID is not null -- or any other WHERE statement
Declare #SQL varchar(max) = Stuff((Select Distinct ',' + QuoteName(Col) From #Temp Order by 1 For XML Path('')),1,1,'')
Select #SQL = '
Select *
From #Temp
Pivot (max(Value) For [Col] in (' + #SQL + ') ) p'
Exec(#SQL);
Returns