【问题标题】:Cursor Usage in SQL ServerSQL Server 中的光标用法
【发布时间】:2018-10-30 20:41:42
【问题描述】:

这是我的代码;

创建表拆分器 ( 标识 INT ,Vals VARCHAR(10) ) 插入分离器值 (1,'A,B,C'), (2,'D,E,F') 声明 @Id INT 声明 @Str VARCHAR(10) DECLARE @Tbl TABLE(Id int, Vals varchar(10)) 声明 MyCursor CURSOR FOR 从拆分器中选择 Id、Vals 打开我的光标 从 MyCursor 获取下一个到 @Id、@Str 而@@FETCH_STATUS = 0 开始 插入@Tbl 选择@Id, substring(@Str,charindex(',',@Str)-1,1) 设置@Str=substring(@Str,charindex(',',@Str)+1,len(@Str)) 从 MyCursor 获取下一个到 @Id,@Str 结尾 关闭我的光标 DEALLOCATE MyCursor

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

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

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

【问题讨论】:

  • 1-A 1-A 1-A 是错字吗?
  • 只是结果集。 '-' 不是重要的符号。
  • 使用游标是第一个问题。游标是在 SQL 中执行任何操作的最慢方式,除了一些非常高级的情况。你想做什么? SQL 是一种基于集合的语言。任何你想做的事情都可以通过查询来完成,而且比游标更快。
  • @aprkturk 您是否尝试拆分一个字符串?有几十个副本比光标快 10-100 倍。 SQL Server 2016 无论如何都引入了STRING_SPLIT
  • @aprkturk 我不认为安德鲁问的是连字符,而是你有 3 个 1-As;你想要1-A1-B1-C吗?

标签: sql sql-server database-cursor


【解决方案1】:

您应该尽可能避免使用游标,它们是导致逐行解决方案的基本循环,在大型 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)

【讨论】:

    【解决方案2】:

    您的问题以正确的示例数据和代码开始,但以“我的代码有什么问题?”而结束。 - 您当前的代码返回:

    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 的 DelimitedSplit8K 所示。

    在 SQL 中,游标通常是做任何事情的错误工具。 SQL 最适用于基于集合的方法,而不是 RBAR 方法(这是游标所能做的全部)。

    基本上,游标只是获取它的 select 语句的结果集,然后将其吐出 Row By Agonizing Row with fetch next
    与在基于集合的方法中完成的相同操作相比,这通常意味着糟糕的性能。
    这并不意味着你永远不应该使用游标,但这是我所说的最后手段,只有当你绝对别无选择时。

    【讨论】:

    • “这是因为您在循环内设置了@str 变量”谢谢!,我更正了我的查询。
    • @aprkturk 问题首先是使用游标。 Zohar 已经发布了指向快速、经过充分测试的答案 的链接。这些答案可以非常轻松地转换为表值函数,从而更容易在查询中使用它们
    【解决方案3】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-20
      • 2018-02-12
      相关资源
      最近更新 更多