1
votes

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..

3
Is 1-A 1-A 1-A a typo?Andrew Morton
It is just result set. '-' is not important sign.aprkturk
Using a cursor is the first problem. Cursors are the slowest way to do anything in SQL, except for some very advanced cases. What are you trying to do? SQL is a set-based language. Anything you want to do can be done with queries and it will be faster than a cursor.Panagiotis Kanavos
@aprkturk Are you trying to split a string perhaps? There are dozens of duplicates that are 10-100 times faster than a cursor. SQL Server 2016 introduced STRING_SPLIT anyway.Panagiotis Kanavos
@aprkturk I don't think Andrew's asking about the hyphen, but rather you have 3 1-As; did you instead want 1-A, 1-B, 1-C?JohnLBevan

3 Answers

4
votes

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.

2
votes

If using SQL Server 2016, just use STRING_SPLIT:

select Id, cs.Value 
from Splitter
cross apply STRING_SPLIT (Vals, ',') cs
2
votes

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)