替换光标有哪些不同的方法?

发布于 2024-07-23 08:56:41 字数 105 浏览 3 评论 0原文

我想知道您在现有代码中替换 SQL Server 游标的经验,或者您如何解决程序人员使用游标来解决的问题,并基于集合进行解决。

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

I'd like to know your experience(s) with replacing SQL Server cursors in existing code, or how you took a problem that a procedural guy would use a cursor to solve, and did it set-based.

What was the problem the cursor was used to solve? How did you replace the cursor?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

假装爱人 2024-07-30 08:56:41

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

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

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

try to never loop, work on sets of data.

you can insert, update, delete multiple rows at one time. here in an example insert of multiple rows:

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

When looking at a loop see what it done inside it. If it is just inserts/deletes/updates, re-write to use single commands. If there are IFs, see if those can be CASE statements or WHERE conditions on inserts/deletes/updates. If so, remove the loop and use set commands.

I've taken loops and replaced them with the set based commands and reduced the execution time from minutes to a few seconds. I have taken procedures with many nested loops and procedure calls and kept the loops (was impossible to only use inserts/deletes/updates), but I removed the cursor, and have seen less locking/blocking and massive performance boosts as well. Here are two looping methods that are better than cursor loops...

if you have to loop, over a set do something like this:

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

if you have a reasonable set of items (not 100,000) to loop over you can do this:

--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
踏雪无痕 2024-07-30 08:56:41

我用 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'

I've replaced some cursors with WHILE loops.

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'
能怎样 2024-07-30 08:56:41

好吧,习惯了过程式编程的应用程序开发人员通常会出于习惯而尝试以过程方式完成所有事情,即使是在 SQL 中也是如此。

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

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

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

马克

Well, often an app dev used to procedural programming will - out of habit - try to do everything procedurally, even in SQL.

Most often, a SELECT with the right paramters might do - or maybe you're dealing with an UPDATE statement.

The point really is: you need to begin to think in set operations and tell your RDBMS what you want done - not how to do it step by step.

It's hard to give a single, "right" answer to this..... you'd almost have to show it with a concrete example.

Marc

独夜无伴 2024-07-30 08:56:41

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

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

希望这有帮助,

比尔

I wrote some code that calculated running totals for financial data related to a given year. In each quarter, I had to add the value for the current quarter to the running total while handling NULLs appropriately so that the running total for the previous quarter carried over when the value for the current quarter was NULL.

Originally, I did this using a cursor and from a functional standpoint this met the business requirement. From a technical standpoint, it turned out to be a show-stopper because as the amount of data increased the code took exponentially longer. The solution was to replace the cursor with a correlated sub-query which met the functional requirements and eliminated any performance issues.

Hope this helps,

Bill

为你拒绝所有暧昧 2024-07-30 08:56:41

当(看看我在那里做了什么)问题被提出并回答时,我想添加我的回答,以防它对其他人有帮助。 与许多其他人一样,我正在转换为 while 语句,但我想利用数据集中现有的主键,而不是计算结果集的大小。 我想出了这个模式,效果很好。 我对其他人和改进持开放态度。 谢谢!

declare @PrimaryKey int
/* get first record for looping */
select @PrimaryKey = min(PrimaryKey) from Table
/* it will be null when all records have iterated */
while (@PrimaryKey is not null)
  begin        
    /* do stuff */
    
    /* iterate to next key */
    select @PrimaryKey = min(PrimaryKey) from Table where PrimaryKey > @PrimaryKey
  end

While (see what I did there) the question is asked and answered, I wanted to add my response just in case it would help anyone else. Like many others, I'm converting over to while statements, but I wanted to leverage the existing primary key in my data set rather rather than counting up to the size of the result set. I came up with this pattern, which has been working well. I'm open to other others and improvements. Thanks!

declare @PrimaryKey int
/* get first record for looping */
select @PrimaryKey = min(PrimaryKey) from Table
/* it will be null when all records have iterated */
while (@PrimaryKey is not null)
  begin        
    /* do stuff */
    
    /* iterate to next key */
    select @PrimaryKey = min(PrimaryKey) from Table where PrimaryKey > @PrimaryKey
  end
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文