Related
Code:
Declare #a VARCHAR(100) = ';2;9;12;13;14;16;17;21;'
Declare #b VARCHAR(100) = ';2;12;13;'
Declare #c VARCHAR(100)
While len(#a) > 1
Begin
Set #c = substring(#a,1,charindex(';',#a,2))
Set #b += #c Where #c Not In #b ----This statement gives a problem and shows a syntax error
Set #a = substring(#a,charindex(';',#a,2),len(#a))
Select #a, #b, #c
End
What I'm trying to accomplish here is that I have declared 2 variables of VARCHAR type and assigned them some value. A third variable I've declared is a sub-string derived from the 1st variable. The value of the 3rd variable I'm trying to find it's presence in the 2nd variable and if it's not I'm trying to concatenate it with the 2nd one.
So the first value that #c would get is: ';2;' and it should compare with #b and if not present in #b it should concatenate with #b.
So basically the result should look something like this in the end.
#b = ';2;12;13;9;14;16;17;21;'
Kindly help. Thanks.
To solve your problem straightforwardly, change:
Set #b += #c Where #c Not In #b
To:
SET #b += CASE WHEN CHARINDEX(#c, #b) > 0 THEN '' ELSE #c END
If I understand your problem correctly, I believe you would want to use sets (tables) instead of strings. You could do this with strings if absolutely necessary using a split function (exists in SQL Server 2016 but will have to write your own for prior versions. There are tons of examples on S.O.)
To use sets, Try this:
DECLARE #a TABLE (id INT identity(1, 1), val INT)
DECLARE #b TABLE (Val INT)
DECLARE #c INT
DECLARE #aSetStr VARCHAR(max)
DECLARE #bSetStr VARCHAR(max)
INSERT INTO #a
VALUES (2), (9), (12), (13), (14), (16), (17), (21)
INSERT INTO #b
VALUES (2), (12), (13)
DECLARE #i INT = 0
WHILE EXISTS (
SELECT A.id
FROM #a A
LEFT JOIN #b B
ON A.Val = B.Val
WHERE A.id > #i AND B.Val IS NULL
)
BEGIN
SELECT TOP 1 #i = A.id, #c = A.Val
FROM #a A
LEFT JOIN #b B
ON A.Val = B.Val
WHERE A.id > #i AND B.Val IS NULL
ORDER BY A.id ASC
IF NOT EXISTS (
SELECT 1
FROM #b B
WHERE B.Val = #c
)
INSERT INTO #b (Val)
SELECT #c
SELECT #aSetStr = STUFF((
SELECT ';' + CAST(val AS VARCHAR(max))
FROM #a
FOR XML PATH('')
), 1, 0, '') + ';'
SELECT #bSetStr = STUFF((
SELECT ';' + CAST(val AS VARCHAR(max))
FROM #b
FOR XML PATH('')
), 1, 0, '') + ';'
SELECT #aSetStr AS [Set A], #bSetStr AS [Set B], cast(#C AS VARCHAR(255)) AS [Value of C]
END
This will Yield
Set A Set B Value of C
-------------------------- ------------------------- ------------
;2;9;12;13;14;16;17;21; ;2;12;13;9; 9
Set A Set B Value of C
-------------------------- ------------------------- ------------
;2;9;12;13;14;16;17;21; ;2;12;13;9;14; 14
Set A Set B Value of C
-------------------------- ------------------------- ------------
;2;9;12;13;14;16;17;21; ;2;12;13;9;14;16; 16
Set A Set B Value of C
-------------------------- ------------------------- ------------
;2;9;12;13;14;16;17;21; ;2;12;13;9;14;16;17; 17
Set A Set B Value of C
-------------------------- ------------------------- ------------
;2;9;12;13;14;16;17;21; ;2;12;13;9;14;16;17;21; 21
TRY THIS CODE
Declare #a VARCHAR(100) = ';2;9;12;13;14;16;17;21;'
Declare #b VARCHAR(100) = ';2;12;13;'
Declare #c VARCHAR(100)
While len(#a) > 1
Begin
Set #c = substring(#a,1,charindex(';',#a,2))
IF (#c) NOT IN (#b) begin
Set #b += #c
end
Set #a = substring(#a,charindex(';',#a,2),len(#a))
Select #a, #b, replace(#c,';','')
End
Another way of getting expected Result
Declare #a VARCHAR(100) = ';2;9;12;13;14;16;17;21;'
Declare #b VARCHAR(100) = ';2;12;13;'
DECLARE #Tempa TABLE (Value nvarchar(1000))
INSERT INTO #Tempa VALUES(#a)
DECLARE #Tempb TABLE (Value nvarchar(1000))
INSERT INTO #Tempb VALUES(#b)
;WITH cte
AS (SELECT Cast(dataa AS INT) AS DataA
FROM (SELECT split.a.value('.', 'nvarchar(1000)') AS DataA
FROM (SELECT Cast('<S>' + Replace(value, ';', '</S><S>') +
'</S>'
AS
XML) AS
Data
FROM #Tempa)AS A
CROSS apply data.nodes('S') AS Split(a)) dt
WHERE dt.dataa <> ''
UNION
SELECT Cast(dataa AS INT) AS DataA
FROM (SELECT split.a.value('.', 'nvarchar(1000)') AS DataA
FROM (SELECT Cast('<S>' + Replace(value, ';', '</S><S>') +
'</S>'
AS
XML) AS
Data
FROM #Tempb)AS A
CROSS apply data.nodes('S') AS Split(a))dt
WHERE dt.dataa <> '')
SELECT ''';' + Stuff((SELECT '; '+Cast(dataa AS VARCHAR(10)) FROM cte ORDER BY
dataa
FOR xml path ('')), 1, 1, '') + ' ;''' AS ExpectedCResult
Result
ExpectedCResult
---------------------------------
'; 2; 9; 12; 13; 14; 16; 17; 21 ;'
If your values are unique, you can use UNION to merge your strings:
Declare #a VARCHAR(100) = ';2;9;12;13;14;16;17;21;'
Declare #b VARCHAR(100) = ';2;7;12;13;'
Declare #c VARCHAR(100)
;WITH
a AS
(
SELECT #a AS FIELD, SUBSTRING(#a, 1, CHARINDEX(';', #a)) AS num
UNION ALL
SELECT RIGHT(FIELD, LEN(FIELD)-LEN(num)), SUBSTRING(RIGHT(FIELD, LEN(FIELD)-LEN(num)), 1, CHARINDEX(';', RIGHT(FIELD, LEN(FIELD)-LEN(num))))
FROM a
WHERE FIELD != num
),
b AS
(
SELECT #b AS FIELD, SUBSTRING(#b, 1, CHARINDEX(';', #b)) AS num
UNION ALL
SELECT RIGHT(FIELD, LEN(FIELD)-LEN(num)), SUBSTRING(RIGHT(FIELD, LEN(FIELD)-LEN(num)), 1, CHARINDEX(';', RIGHT(FIELD, LEN(FIELD)-LEN(num))))
FROM b
WHERE FIELD != num
)
SELECT #c =
(
SELECT ';' + CAST(v.num AS VARCHAR)
FROM (
SELECT CAST(REPLACE(a.num, ';', '') AS INT) num
FROM a
WHERE a.num != ';'
UNION
SELECT CAST(REPLACE(b.num, ';', '') AS INT)
FROM b
WHERE b.num != ';'
) v
FOR XML PATH('')
) + ';'
SELECT #c
column of data in sqlserver like
numbers
1000-1050, 1054, 1090-1230, 1245
numbers
-------
1000-1050, 1054, 1090-1230, 1245
how to get the style like below:
numbers
-------
1000
1001
1002
1003
1004
...
1050
1054
1090
1091
1092
...
1245
You could use a split function and APPLY like this
DECLARE #SampleData AS TABLE
(
numbers varchar(200)
)
INSERT INTO #SampleData
VALUES ('1000-1050, 1054, 1090-1230, 1245')
;WITH temp AS
(
SELECT 1 AS Number
UNION ALL
SELECT t.Number + 1 FROM temp t
WHERE t.Number < 5000
) -- return table from 1 --> 5000
SELECT DISTINCT
ca2.*
FROM #SampleData sd
CROSS APPLY
(
SELECT pos, LTRIM(Value) AS Value
FROM dbo.SplitString(sd.Numbers,',')
) ca1
CROSS APPLY
(
SELECT *
FROM temp t WHERE t.Number BETWEEN LEFT(ca1.[Value], charindex('-', ca1.[Value] + '-') - 1) AND
CASE
WHEN len(ca1.[Value]) < charindex('-', ca1.[Value] + '-') THEN ca1.[Value]
ELSE RIGHT(ca1.[Value], len(ca1.[Value]) - charindex('-', ca1.[Value] + '-'))
END
) ca2
OPTION (MAXRECURSION 0)
Split function
CREATE FUNCTION [dbo].[SplitString] (#Text varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select Pos = Row_Number() over (Order By (Select null))
,Value = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>'+ Replace(#Text,#Delimiter,'</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
Demo link: http://rextester.com/GIPGR78132
First split the comma seperated values, then get the values using a recursive common table expression.
Declare #values nvarchar(max) = '1000-1050, 1054, 1090-1230, 1245'
;with ranges_cte as
(
select cast(case pos when 0 then ResultValue else left(ResultValue,pos-1) end as int) first, cast(case pos when 0 then ResultValue else substring(ResultValue,pos+1,len(ResultValue)-pos) end as int) Last
from (
select ResultValue, charINDEx('-',ResultValue) pos
from dbo.SplitString(#values,',')) x
)
, values_rte as
(
select first,last,first as active
from ranges_cte
union all
select first,last,active +1 as active
from values_rte
where active< last
)
select *
from values_rte
OPTION (MAXRECURSION 0)
Function to split the values:
Create FUNCTION [dbo].[SplitString] (#StringArray NVARCHAR(MAX), #Delimiter NVARCHAR(10))
RETURNS #ResultedValues table
(
nr int,
ResultValue nvarchar(max)
)
AS
--select * from dbo.splitstring ('123,456,789',',')
BEGIN
declare #string nvarchar(max)
declare #nr int = 1
set #string = #StringArray
WHILE (CHARINDEX(#Delimiter,#String)>0)
BEGIN
INSERT INTO #ResultedValues (nr,ResultValue) VALUES (#nr,LTRIM(RTRIM(SUBSTRING(#String,1,CHARINDEX(#Delimiter,#String)-1))))
SET #String = SUBSTRING(#String, CHARINDEX(#Delimiter,#String)+LEN(#Delimiter),LEN(#String))
set #nr = #nr +1
END
INSERT INTO #ResultedValues (nr,ResultValue ) VALUES ( #nr, LTRIM(RTRIM(#String)))
RETURN
END
Create a split function:
CREATE FUNCTION [dbo].[SplitIDsTest]
(
#idList varchar(4000)
)
RETURNS #parsedList TABLE
(
Id varchar(50),
Nr int
)
AS
BEGIN
DECLARE #id varchar(10), #pos int
DECLARE #nr int = 0;
SET #idList = LTRIM(RTRIM(#idList)) + ','
SET #Pos = CHARINDEX(',', #idList)
IF REPLACE(#idList, ',', '') <> ''
BEGIN
WHILE #Pos > 0
BEGIN
SET #id = LTRIM(RTRIM(LEFT(#idList, #pos - 1)))
IF #id <> ''
BEGIN
set #nr += 1;
INSERT INTO #ParsedList (Id, Nr)
VALUES (#id, #nr);
END
SET #idList = RIGHT(#idList, LEN(#idList) - #pos) -- 'WMPC,' (inklusive Komma) links vom Eingabeparameter abschneiden, weil jetzt der nächste Wert gesucht wird
SET #pos = CHARINDEX(',', #idList, 1) -- Nächste Position eines Kommas suchen und in der WHILE-Schleife weitermachen
END
END
RETURN
END
Then a tally table:
select top 1000000 N=identity(int, 1, 1)
into dbo.Tally
from master.dbo.syscolumns a cross join master.dbo.syscolumns b;
Then use:
select distinct Tally.N
from SplitIDsTest('10-12, 34, 9') splitted
join Tally on 1 = case
when CHARINDEX('-', splitted.Id) > 0 then
case
when Tally.N between cast(left(splitted.Id, CHARINDEX('-', splitted.Id) - 1) as int)
and cast(right(splitted.Id, len(splitted.Id) - CHARINDEX('-', splitted.Id)) as int) then 1
else 0
end
when Tally.N = cast(splitted.Id as int) then 1
else 0
end
order by Tally.N
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)
I’m looking to split '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15...' (comma delimited) into a table or table variable.
Does anyone have a function that returns each one in a row?
Try this
DECLARE #xml xml, #str varchar(100), #delimiter varchar(10)
SET #str = '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15'
SET #delimiter = ','
SET #xml = cast(('<X>'+replace(#str, #delimiter, '</X><X>')+'</X>') as xml)
SELECT C.value('.', 'varchar(10)') as value FROM #xml.nodes('X') as X(C)
OR
DECLARE #str varchar(100), #delimiter varchar(10)
SET #str = '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15'
SET #delimiter = ','
;WITH cte AS
(
SELECT 0 a, 1 b
UNION ALL
SELECT b, CHARINDEX(#delimiter, #str, b) + LEN(#delimiter)
FROM CTE
WHERE b > a
)
SELECT SUBSTRING(#str, a,
CASE WHEN b > LEN(#delimiter)
THEN b - a - LEN(#delimiter)
ELSE LEN(#str) - a + 1 END) value
FROM cte WHERE a > 0
Many more ways of doing the same is here How to split comma delimited string?
Here is somewhat old-fashioned solution:
/*
Splits string into parts delimitered with specified character.
*/
CREATE FUNCTION [dbo].[SDF_SplitString]
(
#sString nvarchar(2048),
#cDelimiter nchar(1)
)
RETURNS #tParts TABLE ( part nvarchar(2048) )
AS
BEGIN
if #sString is null return
declare #iStart int,
#iPos int
if substring( #sString, 1, 1 ) = #cDelimiter
begin
set #iStart = 2
insert into #tParts
values( null )
end
else
set #iStart = 1
while 1=1
begin
set #iPos = charindex( #cDelimiter, #sString, #iStart )
if #iPos = 0
set #iPos = len( #sString )+1
if #iPos - #iStart > 0
insert into #tParts
values ( substring( #sString, #iStart, #iPos-#iStart ))
else
insert into #tParts
values( null )
set #iStart = #iPos+1
if #iStart > len( #sString )
break
end
RETURN
END
In SQL Server 2008 you can achieve the same with .NET code. Maybe it would work faster, but definitely this approach is easier to manage.
You've tagged this SQL Server 2008 but future visitors to this question (using SQL Server 2016+) will likely want to know about STRING_SPLIT.
With this new builtin function you can now just use
SELECT TRY_CAST(value AS INT)
FROM STRING_SPLIT ('1,2,3,4,5,6,7,8,9,10,11,12,13,14,15', ',')
Some restrictions of this function and some promising results of performance testing are in this blog post by Aaron Bertrand.
This is most like .NET, for those of you who are familiar with that function:
CREATE FUNCTION dbo.[String.Split]
(
#Text VARCHAR(MAX),
#Delimiter VARCHAR(100),
#Index INT
)
RETURNS VARCHAR(MAX)
AS BEGIN
DECLARE #A TABLE (ID INT IDENTITY, V VARCHAR(MAX));
DECLARE #R VARCHAR(MAX);
WITH CTE AS
(
SELECT 0 A, 1 B
UNION ALL
SELECT B, CONVERT(INT,CHARINDEX(#Delimiter, #Text, B) + LEN(#Delimiter))
FROM CTE
WHERE B > A
)
INSERT #A(V)
SELECT SUBSTRING(#Text,A,CASE WHEN B > LEN(#Delimiter) THEN B-A-LEN(#Delimiter) ELSE LEN(#Text) - A + 1 END) VALUE
FROM CTE WHERE A >0
SELECT #R
= V
FROM #A
WHERE ID = #Index + 1
RETURN #R
END
SELECT dbo.[String.Split]('121,2,3,0',',',1) -- gives '2'
here is the split function that u asked
CREATE FUNCTION [dbo].[split](
#delimited NVARCHAR(MAX),
#delimiter NVARCHAR(100)
) RETURNS #t TABLE (id INT IDENTITY(1,1), val NVARCHAR(MAX))
AS
BEGIN
DECLARE #xml XML
SET #xml = N'<t>' + REPLACE(#delimited,#delimiter,'</t><t>') + '</t>'
INSERT INTO #t(val)
SELECT r.value('.','varchar(MAX)') as item
FROM #xml.nodes('/t') as records(r)
RETURN
END
execute the function like this
select * from dbo.split('1,2,3,4,5,6,7,8,9,10,11,12,13,14,15',',')
DECLARE
#InputString NVARCHAR(MAX) = 'token1,token2,token3,token4,token5'
, #delimiter varchar(10) = ','
DECLARE #xml AS XML = CAST(('<X>'+REPLACE(#InputString,#delimiter ,'</X><X>')+'</X>') AS XML)
SELECT C.value('.', 'varchar(10)') AS value
FROM #xml.nodes('X') as X(C)
Source of this response:
http://sqlhint.com/sqlserver/how-to/best-split-function-tsql-delimited
I am tempted to squeeze in my favourite solution. The resulting table will consist of 2 columns: PosIdx for position of the found integer; and Value in integer.
create function FnSplitToTableInt
(
#param nvarchar(4000)
)
returns table as
return
with Numbers(Number) as
(
select 1
union all
select Number + 1 from Numbers where Number < 4000
),
Found as
(
select
Number as PosIdx,
convert(int, ltrim(rtrim(convert(nvarchar(4000),
substring(#param, Number,
charindex(N',' collate Latin1_General_BIN,
#param + N',', Number) - Number))))) as Value
from
Numbers
where
Number <= len(#param)
and substring(N',' + #param, Number, 1) = N',' collate Latin1_General_BIN
)
select
PosIdx,
case when isnumeric(Value) = 1
then convert(int, Value)
else convert(int, null) end as Value
from
Found
It works by using recursive CTE as the list of positions, from 1 to 100 by default. If you need to work with string longer than 100, simply call this function using 'option (maxrecursion 4000)' like the following:
select * from FnSplitToTableInt
(
'9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ' +
'9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ' +
'9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ' +
'9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ' +
'9, 8, 7, 6, 5, 4, 3, 2, 1, 0'
)
option (maxrecursion 4000)
CREATE FUNCTION Split
(
#delimited nvarchar(max),
#delimiter nvarchar(100)
) RETURNS #t TABLE
(
-- Id column can be commented out, not required for sql splitting string
id int identity(1,1), -- I use this column for numbering splitted parts
val nvarchar(max)
)
AS
BEGIN
declare #xml xml
set #xml = N'<root><r>' + replace(#delimited,#delimiter,'</r><r>') + '</r></root>'
insert into #t(val)
select
r.value('.','varchar(max)') as item
from #xml.nodes('//root/r') as records(r)
RETURN
END
GO
usage
Select * from dbo.Split(N'1,2,3,4,6',',')
This simple CTE will give what's needed:
DECLARE #csv varchar(max) = '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15';
--append comma to the list for CTE to work correctly
SET #csv = #csv + ',';
--remove double commas (empty entries)
SET #csv = replace(#csv, ',,', ',');
WITH CteCsv AS (
SELECT CHARINDEX(',', #csv) idx, SUBSTRING(#csv, 1, CHARINDEX(',', #csv) - 1) [Value]
UNION ALL
SELECT CHARINDEX(',', #csv, idx + 1), SUBSTRING(#csv, idx + 1, CHARINDEX(',', #csv, idx + 1) - idx - 1) FROM CteCsv
WHERE CHARINDEX(',', #csv, idx + 1) > 0
)
SELECT [Value] FROM CteCsv
This is another version which really does not have any restrictions (e.g.: special chars when using xml approach, number of records in CTE approach) and it runs much faster based on a test on 10M+ records with source string average length of 4000. Hope this could help.
Create function [dbo].[udf_split] (
#ListString nvarchar(max),
#Delimiter nvarchar(1000),
#IncludeEmpty bit)
Returns #ListTable TABLE (ID int, ListValue nvarchar(1000))
AS
BEGIN
Declare #CurrentPosition int, #NextPosition int, #Item nvarchar(max), #ID int, #L int
Select #ID = 1,
#L = len(replace(#Delimiter,' ','^')),
#ListString = #ListString + #Delimiter,
#CurrentPosition = 1
Select #NextPosition = Charindex(#Delimiter, #ListString, #CurrentPosition)
While #NextPosition > 0 Begin
Set #Item = LTRIM(RTRIM(SUBSTRING(#ListString, #CurrentPosition, #NextPosition-#CurrentPosition)))
If #IncludeEmpty=1 or LEN(#Item)>0 Begin
Insert Into #ListTable (ID, ListValue) Values (#ID, #Item)
Set #ID = #ID+1
End
Set #CurrentPosition = #NextPosition+#L
Set #NextPosition = Charindex(#Delimiter, #ListString, #CurrentPosition)
End
RETURN
END
/* *Object: UserDefinedFunction [dbo].[Split] Script Date: 10/04/2013 18:18:38* */
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[Split]
(#List varchar(8000),#SplitOn Nvarchar(5))
RETURNS #RtnValue table
(Id int identity(1,1),Value nvarchar(100))
AS
BEGIN
Set #List = Replace(#List,'''','')
While (Charindex(#SplitOn,#List)>0)
Begin
Insert Into #RtnValue (value)
Select
Value = ltrim(rtrim(Substring(#List,1,Charindex(#SplitOn,#List)-1)))
Set #List = Substring(#List,Charindex(#SplitOn,#List)+len(#SplitOn),len(#List))
End
Insert Into #RtnValue (Value)
Select Value = ltrim(rtrim(#List))
Return
END
go
Select *
From [Clv].[Split] ('1,2,3,3,3,3,',',')
GO
Using tally table here is one split string function(best possible approach) by Jeff Moden
CREATE FUNCTION [dbo].[DelimitedSplit8K]
(#pString VARCHAR(8000), #pDelimiter CHAR(1))
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 0 up to 10,000...
-- enough to cover NVARCHAR(4000)
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), --10E+1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT TOP (ISNULL(DATALENGTH(#pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(#pString,t.N,1) = #pDelimiter
),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
SELECT s.N1,
ISNULL(NULLIF(CHARINDEX(#pDelimiter,#pString,s.N1),0)-s.N1,8000)
FROM cteStart s
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l
;
Referred from Tally OH! An Improved SQL 8K “CSV Splitter” Function
This blog came with a pretty good solution using XML in T-SQL.
This is the function I came up with based on that blog (change function name and result type cast per need):
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[SplitIntoBigints]
(#List varchar(MAX), #Splitter char)
RETURNS TABLE
AS
RETURN
(
WITH SplittedXML AS(
SELECT CAST('<v>' + REPLACE(#List, #Splitter, '</v><v>') + '</v>' AS XML) AS Splitted
)
SELECT x.v.value('.', 'bigint') AS Value
FROM SplittedXML
CROSS APPLY Splitted.nodes('//v') x(v)
)
GO
CREATE Function [dbo].[CsvToInt] ( #Array varchar(4000))
returns #IntTable table
(IntValue int)
AS
begin
declare #separator char(1)
set #separator = ','
declare #separator_position int
declare #array_value varchar(4000)
set #array = #array + ','
while patindex('%,%' , #array) <> 0
begin
select #separator_position = patindex('%,%' , #array)
select #array_value = left(#array, #separator_position - 1)
Insert #IntTable
Values (Cast(#array_value as int))
select #array = stuff(#array, 1, #separator_position, '')
end
This works great for me https://www.sqlshack.com/the-string-split-function-in-sql-server/
After two hours of resarching this topic this is the simplest solution (without using XML ect.).
You should only remember to use string_split after from.
DROP TABLE IF EXISTS #Countries
GO
DROP TABLE IF EXISTS #CityList
GO
CREATE TABLE #Countries
(Continent VARCHAR(100),
Country VARCHAR(100))
GO
CREATE TABLE #CityList
(Country VARCHAR(100),
City VARCHAR(5000))
GO
INSERT INTO #Countries
VALUES('Europe','France'),('Europe','Germany')
INSERT INTO #CityList
VALUES('France','Paris,Marsilya,Lyon,Lille,Nice'), ('Germany','Berlin,Hamburg,Munih,Frankfurt,Koln')
SELECT
CN.Continent,CN.Country,value
FROM #CityList CL CROSS APPLY string_split(CL.City,',') INNER JOIN
#Countries CN ON CL.Country = CN.Country
DROP TABLE IF EXISTS #Countries
GO
DROP TABLE IF EXISTS #CityList
You write this function in sql server after that problem will be solved.
http://csharpdotnetsol.blogspot.in/2013/12/csv-function-in-sql-server-for-divide.html
Could you explain me the strange behaviour?
DECLARE #t VARCHAR(256) = ''
SELECT #t = #t + CAST(smb.symbol AS VARCHAR(256))
FROM (
SELECT 1,'7'
UNION ALL
SELECT 2,'8'
UNION all
SELECT 3,'9'
) AS smb(n, symbol)
ORDER BY n
SELECT #t
Outputs:
789
Thats OK for me.
DECLARE #t VARCHAR(256) = ''
SELECT #t = #t + CAST(smb.symbol AS VARCHAR(256))
FROM (
SELECT NUMS.N-1 AS N, CHAR(N-1) AS symbol
FROM (
SELECT 1 + n.n1 + nn.n2 * 10 + nnn.n3 * 100 as N
FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) AS n(n1)
CROSS JOIN (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) AS nn(n2)
CROSS JOIN (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) AS nnn(n3)
) AS NUMS
WHERE NUMS.N BETWEEN 56 AND 58
) AS smb(N, symbol)
ORDER BY smb.N
SELECT #t
Outputs:
9
So why does the second example outputs the last symbol only?
Don't rely on order by when using mutlirows variable assignment.
try this for instance:
DECLARE #c INT = 0
SELECT
#c = #c + x
FROM (VALUES(1),(2),(3)) AS src(x)
WHERE x BETWEEN 1 AND 3
ORDER BY 1 - x DESC
SELECT #c
SET #c = 0
SELECT
#c = #c + x
FROM (VALUES(1),(2),(3)) AS src(x)
WHERE x BETWEEN 1 AND 3
ORDER BY x DESC
SELECT #c
http://sqlmag.com/sql-server/multi-row-variable-assignment-and-order