SQL Server中的游标用法

时间:2018-05-21 08:18:45

标签: sql sql-server database-cursor

这是我的代码;

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 

我想逐行列出值,如下所示;

1-A
1-A
1-A
2-D
2-E
2-F

我的代码有什么问题?我只想知道光标是如何工作的......提前感谢..

3 个答案:

答案 0 :(得分:4)

你的问题在适当的样本数据和代码开始时很好,但结果很糟糕"我的代码有什么问题?" - 您当前的代码返回:

1    a
2    d

这是因为你在循环中设置了@str变量,然后用光标覆盖它。

如果您使用2016或更高版本,并且在该版本之前使用Adam Machanic的CLR功能,则应使用内置string_split分割SQL Server中的字符串。如果您的工作版本低于2016且由于某种原因无法使用CLR,则应该使用XML分割器,如Aaron Bertrand的Split strings the right way – or the next best way或Jeff Moden&#39所示。 ; s DelimitedSplit8K

在SQL中,游标通常是执行任何操作的错误工具。 SQL最适合使用基于集合的方法,而不是使用RBAR方法(这是游标可以执行的所有操作)。

基本上,光标只是获取它的select语句的结果集,然后用fetch next吐出行按Agonizing Row 与基于集合的方法中的相同操作相比,这通常意味着可怕的性能 这并不意味着你永远不应该使用游标,但这是我所说的最后手段,并且只有当你绝对没有其他选择时。

答案 1 :(得分:2)

如果使用SQL Server 2016,只需使用STRING_SPLIT:

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

答案 2 :(得分:2)

你应该尽可能地避免使用游标,它们是一个基本的循环,导致逐行解决方案在大型SQL数据集上可能会非常慢。游标还在缓冲区(内存)中保留空间,并可以从其他进程中窃取资源。如果必须循环,则应使用WHILE构造。

SQL提供了其他基于SET的解决方案,可以取代CURSOR解决方案。

使用递归CTE可以更有效地实现目标。

这是一个递归CTE的例子,它可以取代光标的需要:

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 

如果您的[Vals]列始终采用设定格式,那么您可以使用CROSS APPLY和VALUES来获得更有效的解决方案。

SELECT
    ID
    ,v.Val
FROM
    #Splitter S
        CROSS APPLY (VALUES (left(S.Vals,1)),(SUBSTRING(Vals,3,1)),(RIGHT(Vals,1))) v(Val)