【问题标题】:What are the different ways to replace a cursor?替换光标的不同方法是什么?
【发布时间】:2009-06-08 21:10:07
【问题描述】:

我想知道您在现有代码中替换 SQL Server 游标的经验,或者您如何解决程序专家会使用游标解决的问题,并以集合为基础。

光标用来解决什么问题?你是怎么替换光标的?

【问题讨论】:

    标签: sql-server database-cursor


    【解决方案1】:

    尽量不要循环,处理数据集。

    您可以一次插入、更新、删除多行。这里是一个插入多行的示例:

    INSERT INTO YourTable
            (col1, col2, col3, col4)
        SELECT
            cola, colb+Colz, colc, @X
            FROM ....
                LEFT OUTER JOIN ...
            WHERE...
    

    当查看一个循环时,看看它在里面做了什么。如果只是插入/删除/更新,请重新编写以使用单个命令。如果有 IF,请查看这些是否可以是 CASE 语句或插入/删除/更新的 WHERE 条件。如果是这样,请删除循环并使用 set 命令。

    我采用了循环并将它们替换为基于集合的命令,并将执行时间从几分钟减少到几秒钟。我已经使用了许多嵌套循环和过程调用的过程并保留了循环(不可能只使用插入/删除/更新),但我删除了游标,并且看到更少的锁定/阻塞和大量的性能提升。这里有两种比游标循环更好的循环方法...

    如果你必须循环,在一个集合上做这样的事情:

    --this looks up each row for every iteration
    DECLARE @msg VARCHAR(250)
    DECLARE @hostname sysname
    
    --first select of currsor free loop
    SELECT @hostname= min(RTRIM(hostname))
        FROM  master.dbo.sysprocesses (NOLOCK)
        WHERE  hostname <> ''
    
    WHILE @hostname is not null
    BEGIN
        set @msg='exec master.dbo.xp_cmdshell "net send ' 
            + RTRIM(@hostname) + ' '
            + 'testing  "'
        print @msg
        --EXEC (@msg)
    
        --next select of cursor free loop
        SELECT @hostname= min(RTRIM(hostname))
            FROM master.dbo.sysprocesses (NOLOCK)
            WHERE  hostname <> ''
            and hostname > @hostname
    END
    

    如果您有一组合理的项目(不是 100,000 个)要循环,您可以这样做:

    --this will capture each Key to loop over
    DECLARE @msg VARCHAR(250)
    DECLARE @From   int
    DECLARE @To     int
    CREATE TABLE #Rows
    (
         RowID     int not null primary key identity(1,1)
        ,hostname  varchar(100)
    )
    
    INSERT INTO #Rows
    SELECT DISTINCT hostname
        FROM  master.dbo.sysprocesses (NOLOCK)
        WHERE  hostname <> ''
    SELECT @From=0,@To=@@ROWCOUNT
    
    WHILE @From<@To
    BEGIN
        SET @From=@From+1
    
        SELECT @msg='exec master.dbo.xp_cmdshell "net send ' 
            + RTRIM(hostname) + ' '
            + 'testing  "'
            FROM #Rows WHERE RowID=@From
        print @msg
        --EXEC (@msg)
    END
    

    【讨论】:

    • 在您的第二个代码块中,是否有更深层的理由使用RTRIM(@hostname),因为该值已经是 RTRIMed?顺便说一句,答案很好!
    【解决方案2】:

    我已经用 WHILE 循环替换了一些游标。

    DECLARE @SomeTable TABLE
    (
         ID int IDENTITY (1, 1) PRIMARY KEY NOT NULL,
         SomeNumber int,
         SomeText varchar
    )
    
    DECLARE @theCount int
    DECLARE @theMax int
    
    DECLARE @theNumber int
    DECLARE @theText varchar
    
    INSERT INTO @SomeTable (SomeNumber, SomeText)
    SELECT Number, Text
    FROM PrimaryTable
    
    SET @theCount = 1
    SELECT @theMax = COUNT(ID) FROM @SomeTable
    
    WHILE (@theCount <= @theMax)
    BEGIN
    
         SET @theNumber = 0
         SET @theText = ''
    
         SELECT @theNumber = IsNull(Number, 0), @theText = IsNull(Text, 'nothing')
         FROM @SomeTable
         WHERE ID = @theCount
    
         -- Do something.
         PRINT 'This is ' + @theText + ' from record ' + CAST(@theNumber AS varchar) + '.'
    
         SET @theCount = @theCount + 1
    
    END
    
    PRINT 'Done'
    

    【讨论】:

      【解决方案3】:

      嗯,习惯于程序化编程的应用程序开发人员通常会出于习惯 - 尝试以程序方式执行所有操作,即使在 SQL 中也是如此。

      大多数情况下,带有正确参数的 SELECT 可能会这样做 - 或者您可能正在处理 UPDATE 语句。

      真正的重点是:您需要开始思考集合操作,并告诉您的 RDBMS 您想要做什么,而不是如何一步一步地去做。

      很难给出一个单一的“正确”答案......你几乎必须用一个具体的例子来展示它。

      马克

      【讨论】:

        【解决方案4】:

        我编写了一些代码来计算与给定年份相关的财务数据的运行总计。在每个季度中,我必须将当前季度的值添加到运行总计中,同时适当地处理 NULL,以便在当前季度的值为 NULL 时结转上一季度的运行总计。

        最初,我使用游标执行此操作,从功能的角度来看,这满足了业务需求。从技术的角度来看,它被证明是一个阻碍,因为随着数据量的增加,代码花费的时间呈指数增长。解决方案是用一个满足功能要求并消除任何性能问题的相关子查询来替换游标。

        希望这会有所帮助,

        比尔

        【讨论】:

          猜你喜欢
          • 2012-05-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多