Split comma separated values in sql based on condition [duplicate] - sql

This question already has answers here:
T-SQL split string
(27 answers)
Closed 5 years ago.
Hi all i am newbie in SQL i have a table in which there is a column named dilution_name in this column there are values coming in comma separated format like A,B,C etc. also these values may vary like in some row the values are A,B,C and in some case its like A,B,C,D i just want to separate these values and print them in multiple column if there is only 3 comma separated values then there should be 3 values in comma would be written rest should be null
I have tried
select ParsedData.*
from dilution_table mt
cross apply ( select str = mt.dilution_name + ',,' ) f1
cross apply ( select p1 = charindex( ',', str ) ) ap1
cross apply ( select p2 = charindex( ',', str, p1 + 1 ) ) ap2
cross apply ( select p3 = charindex( ',', str, p2 + 2 ) ) ap3
cross apply ( select p4 = charindex( ',', str, p3 + 3 ) ) ap4
cross apply ( select p5 = charindex( ',', str, p4 + 4 ) ) ap5
cross apply ( select p6 = charindex( ',', str, p5 + 5 ) ) ap6
cross apply ( select val1 = substring( str, 1, p1-1 )
, val2 = substring( str, p1+1, p2-p1-1 ),
val3 = substring( str, p2+1, p2-p1-1 ),
val4 = substring( str, p3+1, p2-p1-1 ),
val5 = substring( str, p4+1, p2-p1-1 ),
val6 = substring( str, p5+1, p2-p1-1 ),
val7 = substring( str, p6+1, p2-p1-1 )
) ParsedData
[sample data][1]
sample data

In SQL Server 2016+ you can use string_split() (though it has no ordinal number).
In SQL Server pre-2016, using a CSV Splitter table valued function by Jeff Moden:
declare #str varchar(128) = 'a,b,c,d'
select s.ItemNumber, s.Item
from dbo.delimitedsplit8k(#str,',') s;
rextester demo: http://rextester.com/EGZ24917
returns:
+------------+------+
| ItemNumber | Item |
+------------+------+
| 1 | a |
| 2 | b |
| 3 | c |
| 4 | d |
+------------+------+
To pivot the data after splitting, you can use conditional aggregation like so:
select
v1 = max(case when s.ItemNumber = 1 then s.Item end)
, v2 = max(case when s.ItemNumber = 2 then s.Item end)
, v3 = max(case when s.ItemNumber = 3 then s.Item end)
, v4 = max(case when s.ItemNumber = 4 then s.Item end)
, v5 = max(case when s.ItemNumber = 5 then s.Item end)
from dbo.delimitedsplit8k(#str,',') s;
returns:
+----+----+----+----+------+
| v1 | v2 | v3 | v4 | v5 |
+----+----+----+----+------+
| a | b | c | d | NULL |
+----+----+----+----+------+
splitting strings reference:
Tally OH! An Improved SQL 8K “CSV Splitter” Function - Jeff Moden
Splitting Strings : A Follow-Up - Aaron Bertrand
Split strings the right way – or the next best way - Aaron Bertrand
string_split() in SQL Server 2016 : Follow-Up #1 - Aaron Bertrand
Ordinal workaround for **string_split()** - Solomon Rutzky

Related

Select Item in String List In SQL Based On Value in Column

I have a SQL table with a column (Grouped_Identifer) that has a string list. The items on the list are separated by commas. There is another column (Position) which has a value. I would like to create or return a column let's call it Position_Identifer that has the portion or item in the Grouped_Identifer which correspond to the value in the Position column.
So we start with the following table:
And would like to end up with a table that looks like the following:
The string list in the Grouped_Identifier column can vary in the number (up to 20) of items.
Unfortunately, SQL Server does not provide the position in string_split(). And, there is no guarantee on the ordering of results from the function.
If the string has no duplicates, you can use charindex() to find the position:
select t.*, s.value
from t outer apply
(select *
from (select s.value,
row_number() over (order by charindex(',' + s.value + ',', ',' + t.gi + ',')) as seqnum
from string_split(t.gi, ',') s
) s
where seqnum = t.pos
) s;
Here is a db<>fiddle.
The new column can be calculated with:
select substring(Grouped_Identifier,Position*15-14,14) as Position_Identifier from yourTable
you can achieve this using string_split if your sql version supports this as follows
with data
as (select '008300000;#61,008300000;#62,008300000;#63' as gi,'1' as pos union all
select '008300000;#61,008300000;#62,008300000;#63' as gi,'2' as pos union all
select '008300000;#61,008300000;#62,008300000;#63' as gi,'3' as pos
)
,cte_d
as(
select *
,row_number() over(partition by pos order by pos) as rn
from data d
cross apply string_split(d.gi,',') x
)
select *
from cte_d
where rn=pos
In older versions you may use this
with data
as (select '008300000;#61,008300000;#62,008300000;#63' as gi,'1' as pos union all
select '008300000;#61,008300000;#62,008300000;#63' as gi,'2' as pos union all
select '008300000;#61,008300000;#62,008300000;#63' as gi,'3' as pos
)
,cte_d
as(
SELECT a.gi
,a.pos
,split.a.value('.', 'VARCHAR(100)') AS split_val
,row_number() over(partition by pos order by pos) as rn
FROM (SELECT pos
,CAST ('<M>' + REPLACE(gi, ',', '</M><M>') + '</M>' AS XML) AS col
,gi
FROM data
) a
CROSS APPLY col.nodes ('/M') AS split(a)
)
select *
from cte_d
where rn=pos
+-------------------------------------------+-----+---------------+----+
| gi | pos | value | rn |
+-------------------------------------------+-----+---------------+----+
| 008300000;#61,008300000;#62,008300000;#63 | 1 | 008300000;#61 | 1 |
| 008300000;#61,008300000;#62,008300000;#63 | 2 | 008300000;#62 | 2 |
| 008300000;#61,008300000;#62,008300000;#63 | 3 | 008300000;#63 | 3 |
+-------------------------------------------+-----+---------------+----+
dbfiddle link
https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=1f753ad7c97255351efc704ebdd966c3

Extract a part of string between multiple delimiters

I have a column with value as '/1064_MyHoldings/ONLINE/Adhoc/Rpt_CompanyCodeElig'
Now, my requirement is to extract every value which is there between the delimeters; '1064 MyHoldings', 'ONLINE', 'Adhoc' etc?
I tried the below code, but it is only taking '1064 MyHoldings'. But I need the other values as well
Can someone please help me here?
WITH yourTable AS (
SELECT '/1064_MyHoldings/ONLINE/Adhoc/Rpt_CompanyCodeElig' AS Path
)
SELECT
CASE WHEN Path LIKE '%/%/%' THEN
SUBSTRING(Path,
CHARINDEX('/', Path) + 1,
CHARINDEX('/', Path, CHARINDEX('/', Path) + 1) - CHARINDEX('/', Path) - 1)
ELSE 'NA' END AS first_component
FROM yourTable;
Use string_split():
select s.value
from t cross apply
string_split(path, '/') s
You can go for recursive search using CTE and split the strings.
WITH yourTable AS (
SELECT '/1064_MyHoldings/ONLINE/Adhoc/Rpt_CompanyCodeElig' AS Path
),
cte_splitTable as
(
SELECT value as val, 1 as lvl
from yourTable
cross apply
string_split(Path,'_')
UNION ALL
SELECT t.value as val, lvl+1 as lvl
from cte_splitTable as c
cross apply
string_split(c.val,'/') as t
where CHARINDEX('/',val) > 0
)
select * from cte_splitTable
where PATINDEX('%[_/]%',val) = 0 and len(val) > 0
+-----------------+
| val |
+-----------------+
| CompanyCodeElig |
| MyHoldings |
| ONLINE |
| Adhoc |
| Rpt |
| 1064 |
+-----------------+

String split on multiple fields to one result

I have a table that looks like the below set as a field for one value:
|---------------------|------------------|------------------|
| Colour | Amount | Size |
|---------------------|------------------|------------------|
| Black,Blue,Green | 1,2,2 | 100,100,100 |
|---------------------|------------------|------------------|
I need to do a string split on each of them and return it in one go.
I've currently got this and works for colour:
SELECT value as colour
FROM [table_name]
CROSS APPLY STRING_SPLIT(colour, ',')
I can't figure out how to do multiple string splits in one go. It should return it as this then:
|---------------------|------------------|------------------|
| Colour | Amount | Size |
|---------------------|------------------|------------------|
| Black | 1 | 100 |
|---------------------|------------------|------------------|
| Blue | 2 | 100 |
|---------------------|------------------|------------------|
| Green | 2 | 100 |
|---------------------|------------------|------------------|
Any help would be great!
Unfortunately, string_split() doesn't provide the option to preserve the order of the substrings it produces. Hence, that is very, very tricky to use in this case.
I prefer a recursive CTE (until the function gets fixed):
with cte as (
select convert(varchar(max), null) as color,
convert(varchar(max), null) as amount,
convert(varchar(max), null) as size,
convert(varchar(max), colors + ',') as rest_colors,
convert(varchar(max), amounts + ',') as rest_amounts ,
convert(varchar(max), sizes + ',') as rest_sizes,
0 as lev
from t
union all
select left(rest_colors, charindex(',', rest_colors) - 1),
left(rest_amounts, charindex(',', rest_amounts) - 1),
left(rest_sizes, charindex(',', rest_sizes) - 1),
stuff(rest_colors, 1, charindex(',', rest_colors), ''),
stuff(rest_amounts, 1, charindex(',', rest_amounts), ''),
stuff(rest_sizes, 1, charindex(',', rest_sizes), ''),
lev + 1
from cte
where rest_colors <> ''
)
select color, amount, size
from cte
where lev > 0;
Here is a db<>fiddle.
As Gordon mentioned, string_split() does not GTD sequence. That said, and if you are open to a Table-Value Function, consider the following where we UNPIVOT your data and then apply a final PIVOT. Note: The RN = ... could be replaced with your own ID (if you have one)
I adjusted the values to illustrate there is a proper sequencing.
Example
;with cte as (
Select RN = row_number() over (order by (select null))
,[Colour]
,[Amount]
,[Size]
From YourTable
)
Select *
From (
Select RN,Item='Colour',B.* From cte Cross Apply [dbo].[tvf-Str-Parse](Colour,',') B
Union All
Select RN,Item='Amount',B.* From cte Cross Apply [dbo].[tvf-Str-Parse](Amount,',') B
Union All
Select RN,Item='Size' ,B.* From cte Cross Apply [dbo].[tvf-Str-Parse](Size ,',') B
) src
Pivot ( max(RetVal) for Item in ([Colour],[Amount],[Size] ) ) pvt
Returns
RN RetSeq Colour Amount Size
1 1 Black 1 100
1 2 Blue 2 200
1 3 Green 3 300
The Function 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 1/0)
,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)
);

Retrieve initials from a SQL Server Table

I've been working on treating a sql table, and splitting the data. I've come to splitting some initials from the last name. The only problem is, the initials are spaced out. For example (data from my table)
Hanse J S P > J S P are the initials
Gerson B D V > B D V are the initials
J D Timberland > J D are the initials
So basically, it's up to four initials, that can be either at the begin, middle, or end of the string. I'm at a loss as to how I should import these. into a seperate column where the result will be:
COL A | COL B
J S P | Jansen
B D V | Gerson
J D | Timberland
Can anyone please point me in the right direction? I'm using SQL Server.
Here's a rather hamfisted way of doing it by abusing the Parsename function. The big caveat here is that Parsename is limited to 4 tokens so J S P Jansen will work but J S P C Jansen or John J S P Jansen will not.
With parsedname AS
(
SELECT
PARSENAME(replace(name, ' ', '.'), 1) name1,
PARSENAME(replace(name, ' ', '.'), 2) name2,
PARSENAME(replace(name, ' ', '.'), 3) name3,
PARSENAME(replace(name, ' ', '.'), 4) name4
FROM yourtable
)
SELECT
CASE WHEN LEN(name4) = 1 THEN name4 ELSE '' END +
CASE WHEN LEN(name3) = 1 THEN name3 ELSE '' END +
CASE WHEN LEN(name2) = 1 THEN name2 ELSE '' END +
CASE WHEN LEN(name1) = 1 THEN name1 ELSE '' END as initials,
CASE WHEN LEN(name1) > 1 THEN name1
WHEN LEN(name2) > 1 THEN name2
WHEN LEN(name3) > 1 THEN name3
WHEN LEN(name4) > 1 THEN name4
END as surname
FROM parsedname
Here is a sqlfiddle of this in action
CREATE TABLE NAMES (name varchar(50));
INSERT INTO NAMES VALUES ('J S P Jansen');
INSERT INTO NAMES VALUES ('B D V Gerson');
INSERT INTO NAMES VALUES ('J D Timberland');
With parsedname AS
(
SELECT
PARSENAME(replace(name, ' ', '.'), 1) name1,
PARSENAME(replace(name, ' ', '.'), 2) name2,
PARSENAME(replace(name, ' ', '.'), 3) name3,
PARSENAME(replace(name, ' ', '.'), 4) name4
FROM names
)
SELECT
CASE WHEN LEN(name4) = 1 THEN name4 ELSE '' END +
CASE WHEN LEN(name3) = 1 THEN name3 ELSE '' END +
CASE WHEN LEN(name2) = 1 THEN name2 ELSE '' END +
CASE WHEN LEN(name1) = 1 THEN name1 ELSE '' END as initials,
CASE WHEN LEN(name1) > 1 THEN name1
WHEN LEN(name2) > 1 THEN name2
WHEN LEN(name3) > 1 THEN name3
WHEN LEN(name4) > 1 THEN name4
END as surname
FROM parsedname
+----------+------------+
| initials | surname |
+----------+------------+
| JSP | Jansen |
| BDV | Gerson |
| JD | Timberland |
+----------+------------+
If a space is needed in between those letters you can just flip around that CASE statement to something like:
TRIM(CASE WHEN LEN(name4) = 1 THEN name4 + ' ' ELSE '' END +
CASE WHEN LEN(name3) = 1 THEN name3 + ' ' ELSE '' END +
CASE WHEN LEN(name2) = 1 THEN name2 + ' ' ELSE '' END +
CASE WHEN LEN(name1) = 1 THEN name1 + ' ' ELSE '' END) as initials
SQLFiddle with the spaces
+----------+------------+
| initials | surname |
+----------+------------+
| J S P | Jansen |
| B D V | Gerson |
| J D | Timberland |
+----------+------------+
This one uses CHARINDEX and recursive CTE to extract space delimited substrings from name:
Find the substring before the first space
Feed the remaining substring to the same CTE
Once you have the substrings, it is only a matter of gluing them back:
WITH yourdata(FullName) AS (
SELECT 'Hanse J S P' UNION
SELECT 'Gerson B D V' UNION
SELECT 'J D Timberland' UNION
SELECT 'TEST 1 TEST 2 TEST 3'
), cte AS (
SELECT
FullName,
CASE WHEN Pos1 = 0 THEN FullName ELSE SUBSTRING(FullName, 1, Pos1 - 1) END AS LeftPart,
CASE WHEN Pos1 = 0 THEN Null ELSE SUBSTRING(FullName, Pos1 + 1, Pos2 - Pos1) END AS NextPart,
1 AS PartSort
FROM yourdata
CROSS APPLY (SELECT CHARINDEX(' ', FullName) AS Pos1, LEN(FullName) AS Pos2) AS CA
UNION ALL
SELECT
FullName,
CASE WHEN Pos1 = 0 THEN NextPart ELSE SUBSTRING(NextPart, 1, Pos1 - 1) END,
CASE WHEN Pos1 = 0 THEN Null ELSE SUBSTRING(NextPart, Pos1 + 1, Pos2 - Pos1) END,
PartSort + 1
FROM cte
CROSS APPLY (SELECT CHARINDEX(' ', NextPart) AS Pos1, LEN(NextPart) AS Pos2) AS CA
WHERE NextPart IS NOT NULL
)
SELECT yourdata.FullName, STUFF(CA1.XMLStr, 1, 1, '') AS Initials, STUFF(CA2.XMLStr, 1, 1, '') AS Names
FROM yourdata
CROSS APPLY (
SELECT CONCAT(' ', LeftPart)
FROM cte
WHERE FullName = yourdata.FullName AND LEN(LeftPart) = 1
ORDER BY PartSort
FOR XML PATH('')
) AS CA1(XMLStr)
CROSS APPLY (
SELECT CONCAT(' ', LeftPart)
FROM cte
WHERE FullName = yourdata.FullName AND LEN(LeftPart) > 1
ORDER BY PartSort
FOR XML PATH('')
) AS CA2(XMLStr)
Result:
| FullName | Initials | Names |
|----------------------|----------|----------------|
| Gerson#B#D#V | B D V | Gerson |
| Hanse#J#S#P | J S P | Hanse |
| J#D#Timberland | J D | Timberland |
| TEST#1#TEST#2#TEST#3 | 1 2 3 | TEST TEST TEST |
Similar to JNevil's answer (+1), but not limited to 4 tokens.
Example
Declare #YourTable table (SomeCol varchar(50))
Insert Into #YourTable values
('Hanse J S P')
,('Gerson B D V')
,('J D Timberland')
,('J D Timberland / J R R Tolkien')
Select A.SomeCol
,ColA = ltrim(
concat(IIF(len(Pos1)=1,' '+Pos1,null)
,IIF(len(Pos2)=1,' '+Pos2,null)
,IIF(len(Pos3)=1,' '+Pos3,null)
,IIF(len(Pos4)=1,' '+Pos4,null)
,IIF(len(Pos5)=1,' '+Pos5,null)
,IIF(len(Pos6)=1,' '+Pos6,null)
,IIF(len(Pos7)=1,' '+Pos7,null)
,IIF(len(Pos8)=1,' '+Pos8,null)
,IIF(len(Pos9)=1,' '+Pos9,null)
)
)
,ColB = ltrim(
concat(IIF(Pos1 not Like '[a-z]',' '+Pos1,null)
,IIF(Pos2 not Like '[a-z]',' '+Pos2,null)
,IIF(Pos3 not Like '[a-z]',' '+Pos3,null)
,IIF(Pos4 not Like '[a-z]',' '+Pos4,null)
,IIF(Pos5 not Like '[a-z]',' '+Pos5,null)
,IIF(Pos6 not Like '[a-z]',' '+Pos6,null)
,IIF(Pos7 not Like '[a-z]',' '+Pos7,null)
,IIF(Pos8 not Like '[a-z]',' '+Pos8,null)
,IIF(Pos9 not Like '[a-z]',' '+Pos9,null)
)
)
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(SomeCol,' ','</x><x>')+'</x>' as xml) as xDim) as A
) B
Returns
SomeCol ColA ColB
Hanse J S P J S P Hanse
Gerson B D V B D V Gerson
J D Timberland J D Timberland
J D Timberland / J R R Tolkien J D / J R R Timberland / Tolkien
I used some built-in functions for this. The general idea is to use string_split to split the string into rows, use ROW_NUMBER to save the order according to length and the char(s) position in the string, then use FOR XML PATH() to concatenate from rows to a single column.
--Assume your data structure
DECLARE #temp TABLE (thestring varchar(1000))
INSERT INTO #temp VALUES
('Hanse J S P'), ('Gerson B D V'), ('J D Timberland')
;WITH CTE AS
(
SELECT *
,ROW_NUMBER() OVER (PARTITION BY thestring ORDER BY thestring, LEN(value) ASC, pos ASC) [order]
FROM (
SELECT *
, value AS [theval]
, CHARINDEX(CASE WHEN len(value) = 1 THEN ' ' + value ELSE value END, thestring) AS [pos]
FROM #temp CROSS APPLY string_split(thestring, ' ')
) AS dT
)
SELECT ( SELECT value + ' ' AS [text()]
FROM cte
WHERE cte.thestring = T.thestring
AND LEN(theval) = 1
FOR XML PATH('')
) AS [COL A]
,( SELECT value + ' ' AS [text()]
FROM cte
WHERE cte.thestring = T.thestring
AND LEN(theval) > 1
FOR XML PATH('')
) AS [COL B]
FROM #temp T
GROUP BY thestring
Produces output:
COL A COL B
----- -----
B D V Gerson
J S P Hanse
J D Timberland
Which version of SQL Server do you have? Is STRING_SPLIT() available?
If yes, split using the space as a delimiter, iterate through the resulting strings, evaluate their length and concatenate a result string with the string when said string is one character in length and is a letter.
Add a space before unless the result string is so far empty.
If STRING_SPLIT() is not available... Well... Here are a few solutions:
T-SQL split string based on delimiter
-- Addendum
To your second part of the question (which did not originally exist when I originally posted my reply) where you would like to isolate the non-initials part into a second column, I would basically separate two blocks of logic with two result strings based on the length of each element.
Note: this is not going to be very elegant in pre-2016 SQL Server and may even require a CURSOR (sigh)
I know I am going to be downvoted for mentioning a cursor.

Generate comma separated value based on input in sql

I have a table called Rule_X_ListType in the following structure
Rue_ID ListType_ID Value
---------------------------
1 2 319
1 2 400
1 5 8150
1 5 1000
1 3 10211
2 2 400
2 6 10211
3 7 10211
3 3 8051
2 2 319
If I will give the input as Rule_ID = 1 and ListType_ID = 2, then I need the output as a string with values :
319,400
Anybody please help out...Thanks in advance...
I do not feel the neccessity for either the CTE or the FOR XML PATH.
This can be accomplished using the much more simple method of COALESCE
DECLARE #List varchar(100)
SELECT
#List = COALESCE(#List + ', ', '') + CAST(Value AS varchar(10))
FROM
Rule_X_ListType
WHERE
Rule_ID = 1 and ListType_ID = 2
SELECT #List
Try this
;WITH CTE AS
(
SELECT * FROM Rule_X_ListType WHERE Rue_ID = 1 AND ListType_ID = 2
)
SELECT STUFF
(
(SELECT ',' + A.Value FROM CTE A ORDER BY A.VALUE FOR XML PATH('')),1,1,''
) AS CSVValues
SELECT DISTINCT T1.Rule_ID,T1.ListType_ID,STUFF(VAL,1,1,'') AS VALUE
FROM Rule_X_ListType T1
CROSS APPLY (SELECT ',' + CONVERT(VARCHAR,Value)
FROM Rule_X_ListType T2
WHERE T1.Rule_ID =T2.Rule_ID and T1.ListType_ID =T2.ListType_ID
FOR XML PATH(''))A(VAL)
WHERE T1.Rule_ID = 1 and T1.ListType_ID = 2
SQL Tips and Tricks in http://sqlbay.blogspot.in/