split a string by commas (,) SQL SERVER - sql-server

I would like to split a string by commas (,) or pipe (|) to each character in SQL SERVER. Example 'APPLE'. Expected result: 'A|P|P|L|E'. Preferably without creating function.

You can do it with CTE:
DECLARE #s NVARCHAR(MAX) = 'APPLE'
DECLARE #result NVARCHAR(MAX)
;WITH cte(N, S) AS
(
SELECT 1 AS N, SUBSTRING(#s, 1, 1)
UNION ALL
SELECT N + 1, SUBSTRING(#s, N + 1, 1)
FROM cte
WHERE N < LEN(#s)
)
SELECT #result = COALESCE(#result + '|', '') + S FROM cte
SELECT #result
Output:
A|P|P|L|E
Or even shorter version:
DECLARE #s NVARCHAR(MAX) = 'APPLE'
;WITH cte(N, S, D) AS
(
SELECT 1 AS N, SUBSTRING(#s, 1, 1), D = SUBSTRING(#s, 1, 1)
UNION ALL
SELECT N + 1, SUBSTRING(#s, N + 1, 1), D = D + '|' + SUBSTRING(#s, N + 1, 1)
FROM cte
WHERE N < LEN(#s)
)
SELECT TOP 1 D FROM cte
ORDER BY N DESC

You could use a concept like the "Tally Table String Splitter" to achieve what you want.
http://www.sqlservercentral.com/articles/Tally+Table/72993/

DECLARE #txt varchar(50) ='APPLE'
;WITH cte(x) as
(
SELECT top (len(#txt)) ';'
+ substring(#txt, row_number() over (order by (select 1)), 1)
FROM master..spt_values x1
cross join
master..spt_values x2
FOR XML PATH('')
)
SELECT stuff(x, 1, 1, '')
FROM CTE
Result
A;P;P;L;E

Related

How to split a comma delimited string using ONLY a select statement?

Here's the situation:
I cannot implement a custom function. PLEASE let's not get into a debate about why. That's just the way the sitation is and I cannot change that
I would need to somehow split the comma delimited values up using a SELECT statement of some kind
I cannot use the built in STRING_SPLIT function because I'd need to set the database compatibly to 130, which I cannot do, due to permission issue
So with that all mentioned, how can I spit up something like 'This,Is,A,Sentence' using a select statement?
I am using SQL Server 2008.
Query
Declare #String nvarchar(500) = 'This,Is,A,Sentence';
SELECT Split.a.value('.', 'VARCHAR(100)') Words
FROM (
SELECT Cast ('<X>'
+ Replace(#String, ',', '</X><X>')
+ '</X>' AS XML) AS Data
) AS t CROSS APPLY Data.nodes ('/X') AS Split(a);
Result Set:
╔══════════╗
║ Words ║
╠══════════╣
║ This ║
║ Is ║
║ A ║
║ Sentence ║
╚══════════╝
A plus 1 to the guys suggesting cte's. You're probably going to find that their suggestions are going to be the best option. The code I use for this is usually wrapped in a user defined function, but I've stripped it out for an example here:
DECLARE #str NVARCHAR(MAX) = 'This,Is,A,Sentence'
, #Delimiter NCHAR(1) = ',';
WITH cte AS
(SELECT 1 AS ID
, CASE WHEN CHARINDEX(#Delimiter, #str, 1) = 0 THEN #str
ELSE LEFT(#str, CHARINDEX(#Delimiter, #str, 1) - 1)
END Words
, CASE WHEN CHARINDEX(#Delimiter, #str, 1) = 0 THEN ''
ELSE STUFF(#str, 1, CHARINDEX(#Delimiter, #str, 1), '')
END Remainder
UNION ALL
SELECT cte.ID + 1
, CASE WHEN CHARINDEX(#Delimiter, cte.Remainder, 1) = 0 THEN cte.Remainder
ELSE LEFT(cte.Remainder, CHARINDEX(#Delimiter, cte.Remainder, 1) - 1)
END
, CASE WHEN CHARINDEX(#Delimiter, cte.Remainder, 1) = 0 THEN ''
ELSE STUFF(cte.Remainder, 1, CHARINDEX(#Delimiter, cte.Remainder, 1), '')
END Remainder
FROM cte
WHERE Remainder <> '')
SELECT cte.ID [Index]
, cte.Words
FROM cte;
Result set:
You can of course strip out the id/index column if you're not going to need it
Incidentally, if you compare this to the built in Split function in the latest version of SQL Server, this is actually far more efficient. Running both together cte version 16% of load, built in function 84%. That's a big difference.
Well, not a single SELECT, but here's something you can use without calling a proc or UDF:
CREATE TABLE #OutputTable( SValues VARCHAR(100) )
DECLARE #StringInput VARCHAR(MAX)
,#StringTemp VARCHAR(100)
WHILE LEN(#StringInput) > 0
BEGIN
SET #StringTemp = LEFT(#StringInput,
ISNULL(NULLIF(CHARINDEX(',', #StringInput) - 1, -1),
LEN(#StringInput)))
SET #StringInput = SUBSTRING(#StringInput,
ISNULL(NULLIF(CHARINDEX(',', #StringInput), 0),
LEN(#StringInput)) + 1, LEN(#StringInput))
INSERT INTO #OutputTable ( SValues )
VALUES ( #StringTemp )
END
#OutputTable will hold each "word" as a record. You can then query it as needed.
If you can use a CTE .....
DECLARE #word VARCHAR(200) = 'This,Is,A,Sentence'
;WITH
a AS (SELECT i=-1, j=0 UNION ALL SELECT j, CHARINDEX(',', #word, j + 1) FROM a WHERE j > i),
b AS (SELECT SUBSTRING(#word, i+1, IIF(j>0, j, LEN(#word)+1)-i-1) word FROM a WHERE i >= 0)
SELECT * FROM b
With credit to question: Find nth Occurrence in a string
DECLARE #Val varchar(200) = 'this,is,a,string'
;with t as (
select #Val as val, 1 as starts, charindex(',', #Val) as pos
union all
select #Val, pos + 1, charindex(',', #Val, pos + 1)
from t
where pos > 0
)
select
*, substring(#Val, starts, case when pos > 0 then pos - starts else len(#Val) end) token
from T
order by starts
This is what I would do:
DECLARE #t TABLE
(
ID INT,
Words VARCHAR(500)
)
INSERT #t VALUES (1,'This,Is,A,Sentence')
SELECT
LTRIM(RTRIM(m.n.value('.[1]','varchar(8000)'))) AS Words
FROM
(
SELECT CAST('<XMLRoot><RowData>' + REPLACE(Words,',','</RowData><RowData>') + '</RowData></XMLRoot>' AS XML) AS x
FROM #t
)t
CROSS APPLY x.nodes('/XMLRoot/RowData')m(n)
Results:
|Words|
This
Is
A
Sentence

comma separated string using CTE

I have a string '1,2,3,4,5,6,', i want the result in array like :
1
2
3
4
5
I have tried it using function and also done by convertingit to the xml.
I have a query:
with cte1 (str1,str2) AS
(
SELECT SUBSTRING('1,2,3,4,5,6,',1,1) X,
SUBSTRING('1,2,3,4,5,6,',CHARINDEX(',','1,2,3,4,5,6,,') +1,LEN('1,2,3,4,5,6,')-2) Y
UNION all
SELECT SUBSTRING(str2,1,1) X ,SUBSTRING(str2,CHARINDEX(',',str2)+1,LEN(str2)-2) Y
FROM CTE1
WHERE SUBSTRING(str2,CHARINDEX(',',str2)+0,1) <> ' ' )
SELECT str1 FROM CTE1;
which gives the result as expected.
but if i am changing the string it gives random reults like :
with cte1 (str1,str2) AS
(
SELECT SUBSTRING('24,78,45,56,',1,1) X,
SUBSTRING('24,78,45,56,',CHARINDEX(',','24,78,45,56,') +1,LEN('24,78,45,56,')-2) Y
UNION all
SELECT SUBSTRING(str2,1,1) X ,SUBSTRING(str2,CHARINDEX(',',str2)+1,LEN(str2)-2) Y
FROM CTE1
WHERE SUBSTRING(str2,CHARINDEX(',',str2)+0,1) <> ' ' )
SELECT str1 FROM CTE1;
result :
2
7
4
5
This will work only when string is like '12,34,45,56....'i.e string contains two digit comm separated values
with cte1 (str1,str2) AS
(
SELECT SUBSTRING('24,78,45,56,',1,2) X,
SUBSTRING('24,78,45,56,',CHARINDEX(',','24,78,45,56,') +1,LEN('24,78,45,56,')-2) Y
UNION all
SELECT SUBSTRING(str2,1,2) X ,SUBSTRING(str2,CHARINDEX(',',str2)+1,LEN(str2)-2) Y
FROM CTE1
WHERE SUBSTRING(str2,CHARINDEX(',',str2)+0,2) <> ' ' )
SELECT str1 FROM CTE1;
You should go with generic solution by creating on user define function which accepts comma separated string and give table value for this string
Function definition like this
CREATE FUNCTION SplitItem( #ItemIDs VARCHAR(MAX))
RETURNS #ItemTable TABLE ( Item VARCHAR(200) )
AS
BEGIN
DECLARE #Item VARCHAR(200)
DECLARE #Index INT
WHILE LEN(#ItemIDs) <> 0
BEGIN
SET #Index = PATINDEX('%,%', #ItemIDs)
IF #Index > 0
BEGIN
SET #Item = SUBSTRING(#ItemIDs, 1, #Index - 1)
SET #ItemIDs = RIGHT(#ItemIDs, LEN(#ItemIDs) - #Index)
INSERT INTO #ItemTable
VALUES ( #Item )
END
ELSE
BEGIN
BREAK
END
END
SET #Item = #ItemIDs
INSERT INTO #ItemTable
VALUES ( #Item )
RETURN
END
And Use this function like this
SELECT Item
FROM SplitItem('1,2,3,44,55,66,77')
This will gives output like this
1
2
3
44
55
66
77
You could use a recursive CTE
Declare #list NVARCHAR(MAX) = '1,2,3,4,5'
DECLARE #length INT = LEN(#list) + 1;
WITH a AS
(
SELECT
[start] = 1,
[end] = COALESCE(NULLIF(CHARINDEX(',',
#List, 1), 0), #length),
[value] = SUBSTRING(#list, 1,
COALESCE(NULLIF(CHARINDEX(',',
#List, 1), 0), #length) - 1)
UNION ALL
SELECT
[start] = CONVERT(INT, [end]) + 1,
[end] = COALESCE(NULLIF(CHARINDEX(',',
#list, [end] + 1), 0), #length),
[value] = SUBSTRING(#list, [end] + 1,
COALESCE(NULLIF(CHARINDEX(',',
#list, [end] + 1), 0), #length)-[end]-1)
FROM a
WHERE [end] < #length
)
SELECT [value]
FROM a
WHERE LEN([value]) > 0
OPTION (MAXRECURSION 0);
You can do something like this:
DECLARE #string NVARCHAR(MAX) = '1,2,3,4,5,6,',
#xml xml
select #xml = cast('<d><q>'+REPLACE(#string,',','</q><q>')+'</q></d>' as xml)
SELECT n.v.value('.','nvarchar(2)')
FROM #xml.nodes('/d/q') AS n(v);
The result:
----
1
2
3
4
5
6
(7 row(s) affected)

Compare Two Comma Separated String return Matching String Index,matching count,blank count

How to compare to String of type below
#Qstring='C,D,B,C,D,C,E,B,E,A,D,'
#Astring='C,D,C,E,D, ,E, ,E, ,A,'
and produce the string similar below.
is there method compare without using loop and temp table
Blankstring='6,8,10'
BlankCount=3
MatchingString=1,2,5,7,9
matchcount =5
You probably shouldn't do it in DB but why not:
Warning: this is sketch only and should be improved if needed.
SqlFiddleDemo
Code:
DECLARE
#Qstring NVARCHAR(100) ='C,D,B,C,D,C,E,B,E,A,D,',
#Astring NVARCHAR(100) ='C,D,C,E,D, ,E, ,E, ,A,';
DECLARE
#Qxml XML = CONVERT(XML,'<Vals><Val>' + REPLACE(LEFT(#Qstring,LEN(#Qstring)-1),',', '</Val><Val>') + '</Val></Vals>'),
#Axml XML = CONVERT(XML,'<Vals><Val>' + REPLACE(LEFT(#Astring,LEN(#Astring)-1),',', '</Val><Val>') + '</Val></Vals>');
;WITH QStringTab AS
(
SELECT
[val] = x.i.value('.', 'NVARCHAR(10)')
,[rn] = ROW_NUMBER() OVER (ORDER BY (SELECT 1))
FROM #Qxml.nodes('/Vals/Val') AS x(i)
), AStringTab AS
(
SELECT
[val] = x.i.value('.', 'NVARCHAR(10)')
,[rn] = ROW_NUMBER() OVER (ORDER BY (SELECT 1))
FROM #Axml.nodes('/Vals/Val') AS x(i)
), matchcount AS
(
SELECT c = COUNT(*)
FROM QStringTab q
JOIN AStringTab a
ON q.rn = a.rn
AND q.val = a.val
), blankcount AS
(
SELECT c = COUNT(*)
FROM AStringTab a
WHERE val = ''
), blankstring AS
(
SELECT
[s] = STUFF( (SELECT ',' + CAST(a.rn AS NVARCHAR(10))
FROM AStringTab a
WHERE a.val = ''
ORDER BY rn ASC
FOR XML PATH('')),
1, 1, '')
), matchingstring AS
(
SELECT
[s] = STUFF( (SELECT ',' + CAST(a.rn AS NVARCHAR(10))
FROM QStringTab q
JOIN AStringTab a
ON q.rn = a.rn
AND q.val = a.val
ORDER BY q.rn ASC
FOR XML PATH('')),
1, 1, '')
)
SELECT [statistics] = 'BlankString = ' + ISNULL(bs.s, '')
FROM blankstring bs
UNION ALL
SELECT [statistics] = 'BlankCount = ' + CAST(b.c AS NVARCHAR(100))
FROM blankcount b
UNION ALL
SELECT [statistics] = 'MatchingString = ' + ISNULL(ms.s, '')
FROM matchingstring ms
UNION ALL
SELECT [statistics] = 'Matchcount = ' + CAST(m.c AS NVARCHAR(100))
FROM matchcount m;
Doubts:
I assume that you want to compare values between commas and strings have the same number of commas
BlankCount should count from both or only #AString?
BlankString only for #AString, what if there are blank in both strings?
MatchingString should match blanks?
I removed last comma because it will be one more blank

SQL - Column value manipulation -return dataset

I have column values = 1,2,3 AND 0,1 across 2 records.
When selecting both records from a view I am trying to remove the '1,' and '1'
My attempt is below;
CAST(CASE WHEN (column like '%1%') THEN (ReturnTheValueWithout 1 OR 1,)
ELSE column END AS VARCHAR) AS NewColumnName
You can use REPLACE:
SELECT REPLACE(REPLACE(column,'1,','')),',1','')
FROM TableName
Result:
2,3
0
Sample result in SQL Fiddle.
One way to do this is using REPLACE with other string functions like LEFT and STUFF
Query
SELECT LEFT(STUFF(REPLACE(',' + Col1 + ',',',1,',','),1,1,''),LEN(STUFF(REPLACE(',' + Col1 + ',',',1,',','),1,1,''))-1)
Test Script
DECLARE #v varchar(50) = '1,2,3'
--SET #v = '0,1'
SELECT LEFT(STUFF(REPLACE(',' + #v + ',',',1,',','),1,1,''),LEN(STUFF(REPLACE(',' + #v + ',',',1,',','),1,1,''))-1)
Output
0
2,3
Edit
A shorter version with REVERSE
SELECT REVERSE(STUFF(REVERSE(STUFF(REPLACE(',' + Col1 + ',',',1,',','),1,1,'')),1,1,''))
FROM...
SQL Fiddle
Solution1:
DECLARE #s VARCHAR(20) = '1,11,2,1,3,4,1'
SELECT REPLACE(CASE WHEN LEFT(#s, 2) = '1,'
AND RIGHT(#s, 2) = ',1'
THEN SUBSTRING(#s, 3, LEN(#s) - 4)
WHEN LEFT(#s, 2) = '1,' THEN RIGHT(#s, LEN(#s) - 2)
WHEN RIGHT(#s, 2) = ',1' THEN LEFT(#s, LEN(#s) - 2)
ELSE #s
END, ',1,', ',')
Output:
11,2,3,4
Solution2:
SELECT SUBSTRING(REPLACE(',' + #s + ',', ',1,', ','), 2, LEN(REPLACE(',' + #s + ',', ',1,', ','))-2)
Shorter version:
SELECT SUBSTRING(s, 2, LEN(s) - 2) FROM (SELECT REPLACE(',' + #s + ',', ',1,', ',') s)t
Solution3:
Most short:
SELECT REPLACE(',' + REPLACE(#s, ',1,', ',,1,') + ',', ',1,', '')
Solution4:
Above solutions failed here and there on some complex string. The following works on
DECLARE #s VARCHAR(120) = '1,31,11,2,1,3,4,1234,1,1,1,1,1,1,1,1,1,sfds,23,12,11,1'
SELECT REPLACE(REPLACE(REPLACE(CASE WHEN LEFT(#s, 2) = '1,'
AND RIGHT(#s, 2) = ',1'
THEN SUBSTRING(#s, 3, LEN(#s) - 4)
WHEN LEFT(#s, 2) = '1,' THEN RIGHT(#s, LEN(#s) - 2)
WHEN RIGHT(#s, 2) = ',1' THEN LEFT(#s, LEN(#s) - 2)
ELSE #s
END, ',1,', ',,1,,'), ',1,', ''), ',,', ',')
Output:
31,11,2,3,4,1234,sfds,23,12,11

Split Housenumbers on chars and ints

I have some fields in a db, with values of 54D, 325A, 2E and so on, letters first and numbers last.
How can I split those in a select statement, or filter it to only show the letters or numbers? I cant use udf functions for this.
I need to be able to insert 54 into another column and D into another and so on.
Im on MS SQL Server
Thanks
One way using the position of the first non-digit:
;with T(f) as (
select '325A' union
select '54D' union
select '2E' union
select '555' union
select 'Z'
)
select
f,
rtrim(left(f, patindex('%[^0-9]%', f + ' ') - 1)),
rtrim(substring(f + ' ', patindex('%[^0-9]%', f + ' '), len(f)))
from T
----
2E 2 E
325A 325 A
54D 54 D
555 555
Z Z
declare #t table(col1 varchar(20))
insert #t values('54D'),('325A'),('2E'), ('A'), ('3')
SELECT
substring(col1, 0, patindex('%[^0-9]%', col1 + 'a')),
stuff('0' + col1, 1, patindex('%[^0-9]%', col1 + 'a'), '')
FROM #t
Result:
54 D
325 A
2 E
A
3
Here is some brutal solution, but it will be able to separate mixed strings also:
DECLARE #t TABLE(ID INT, S NVARCHAR(MAX))
INSERT INTO #t VALUES (1, '123AB'), (2, '45CDEF'), (3, '1AS^&*876YU')
DECLARE #m INT
SELECT #m = MAX(LEN(S)) FROM #t
;WITH cte AS
(SELECT ID, SUBSTRING(S, 1, 1) AS S, 1 AS N, ISNUMERIC(SUBSTRING(S, 1, 1)) AS NU FROM #t
UNION ALL
SELECT t.ID, SUBSTRING(t.S, N + 1, 1) AS S, N + 1 AS N, ISNUMERIC(SUBSTRING(t.S, N + 1, 1)) AS NU FROM cte
JOIN #t t ON t.ID = cte.ID
WHERE N < #m
)
SELECT
(SELECT S FROM cte c2 WHERE c2.ID = c1.ID AND NU = 1 FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)') AS NumericPart,
(SELECT S FROM cte c2 WHERE c2.ID = c1.ID AND NU = 0 FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)') AS TextPart
FROM cte c1
WHERE S <> ''
GROUP BY ID
Output:
NumericPart TextPart
123 AB
45 CDEF
1876 AS^&*YU

Resources