Reputation: 85
Here is my code;
CREATE TABLE Splitter ( Id INT ,Vals VARCHAR(10) ) INSERT INTO Splitter VALUES (1,'A,B,C'), (2,'D,E,F') DECLARE @Id INT DECLARE @Str VARCHAR(10) DECLARE @Tbl TABLE(Id int, Vals varchar(10)) DECLARE MyCursor CURSOR FOR select Id, Vals from splitter OPEN MyCursor FETCH NEXT FROM MyCursor INTO @Id, @Str WHILE @@FETCH_STATUS = 0 BEGIN insert into @Tbl select @Id, substring(@Str,charindex(',',@Str)-1,1) set @Str=substring(@Str,charindex(',',@Str)+1,len(@Str)) FETCH NEXT FROM MyCursor INTO @Id,@Str END CLOSE MyCursor DEALLOCATE MyCursor
I want to list values row by row as below;
1-A
1-A
1-A
2-D
2-E
2-F
Whats wrong for my code? I just want to know how cursor does work..Thanks in advance..
Upvotes: 1
Views: 1225
Reputation: 1931
You should try and avoid cursors where possible, they are a basic loop that leads to Row-by-Row solutions that can be very slow on large SQL datasets. Cursors also reserve space in the Buffer (memory) and can steal resources from other processes. If you have to loop you should use a WHILE construct.
SQL offers other SET based solutions that can replace a CURSOR solution.
Your goal may more efficiently be achieved with a Recursive CTE.
Here's an example of a Recursive CTE that can replace the need for your cursor:
CREATE TABLE #Splitter
(
Id INT
,Vals VARCHAR(10)
)
INSERT INTO #Splitter VALUES
(1,'A,B,C'),
(2,'D,E,F')
;WITH cte AS
(
--Anchor point, the first Value from each ID
SELECT
Id
,LEFT(Vals,CHARINDEX(',',Vals)-1) AS Val
,RIGHT(Vals,LEN(Vals)-CHARINDEX(',',Vals)) AS Remainder
FROM #Splitter
WHERE
Vals IS NOT NULL AND CHARINDEX(',',Vals)>0
UNION ALL
--Central Recursion for each value
SELECT
Id,LEFT(Remainder,CHARINDEX(',',Remainder)-1)
,RIGHT(Remainder,LEN(Remainder)-CHARINDEX(',',Remainder))
FROM cte
WHERE Remainder IS NOT NULL AND CHARINDEX(',',Remainder)>0
UNION ALL
--Error prevention, handling the end of the string
SELECT
Id,Remainder,null
FROM cte
WHERE Remainder IS NOT NULL AND CHARINDEX(',',Remainder)=0
)
SELECT ID,VAL FROM cte
If your [Vals] column is always in a set format then you could use CROSS APPLY with VALUES for a more efficient solution.
SELECT
ID
,v.Val
FROM
#Splitter S
CROSS APPLY (VALUES (left(S.Vals,1)),(SUBSTRING(Vals,3,1)),(RIGHT(Vals,1))) v(Val)
Upvotes: 3
Reputation: 82524
Your question started good with proper sample data and code, but ended bad with "Whats wrong for my code?" - Your current code returns:
1 a
2 d
This is because you are setting the @str
variable inside the loop, but then overwrite it with the cursor.
Splitting strings in SQL Server should be done using built in string_split
if you are working on 2016 or higher, and prior to that version by Adam Machanic's CLR function. If you are working on a version lower than 2016 and can't use CLR for some reason, you should probably use an XML splitter, as shown in Aaron Bertrand's Split strings the right way – or the next best way, or Jeff Moden's DelimitedSplit8K.
In SQL, a cursor is usually the wrong tool to do anything. SQL works best with a set based approach, not with a RBAR approach (which is all that a cursor can do).
Basically, a cursor just takes the resultset of it's select statement and spit it out Row By Agonizing Row with fetch next
This usually means terrible performance compared to the same operation done in a set based approach.
This does not mean that you should never use cursors, but it is what I would refer to as a last resort, and only when you absolutely have no other choice.
Upvotes: 4
Reputation: 2506
If using SQL Server 2016, just use STRING_SPLIT:
select Id, cs.Value
from Splitter
cross apply STRING_SPLIT (Vals, ',') cs
Upvotes: 3