【问题标题】:Using a cursor with dynamic SQL in a stored procedure在存储过程中使用带有动态 SQL 的游标
【发布时间】:2010-11-05 23:06:36
【问题描述】:

我有一个在存储过程中创建的动态 SQL 语句。我需要使用游标迭代结果。我很难找出正确的语法。这就是我正在做的事情。

SELECT @SQLStatement = 'SELECT userId FROM users'

DECLARE @UserId

DECLARE users_cursor CURSOR FOR
EXECUTE @SQLStatment --Fails here. Doesn't like this

OPEN users_cursor
FETCH NEXT FROM users_cursor
INTO @UserId

WHILE @@FETCH_STATUS = 0
BEGIN

EXEC asp_DoSomethingStoredProc @UserId

END
CLOSE users_cursor
DEALLOCATE users_cursor

这样做的正确方法是什么?

【问题讨论】:

  • “正确”的方法是不使用动态 sql 或游标。你能举一个你想要完成的真实例子吗?
  • 完全正确 - 一个存储过程中的两个 SQL 服务器的大 NO-NO :-)
  • 动态 SQL 不一定是邪恶的,特别是如果您使用 sp_executesql 而不是 EXEC。游标,虽然是被诅咒的。
  • 例如,如果将目标数据库名称参数化,是否有替代动态 SQL 的方法?例如。 Procs 与生产环境和测试环境数据库对话。

标签: tsql stored-procedures dynamic-sql database-cursor


【解决方案1】:

游标只接受一个选择语句,所以如果 SQL 确实需要动态的,请将声明游标作为您正在执行的语句的一部分。要使以下内容正常工作,您的服务器必须使用全局游标。

Declare @UserID varchar(100)
declare @sqlstatement nvarchar(4000)
--move declare cursor into sql to be executed
set @sqlstatement = 'Declare  users_cursor CURSOR FOR SELECT userId FROM users'

exec sp_executesql @sqlstatement


OPEN users_cursor
FETCH NEXT FROM users_cursor
INTO @UserId

WHILE @@FETCH_STATUS = 0
BEGIN
Print @UserID
EXEC asp_DoSomethingStoredProc @UserId

FETCH NEXT FROM users_cursor --have to fetch again within loop
INTO @UserId

END
CLOSE users_cursor
DEALLOCATE users_cursor

如果您需要避免使用全局游标,您还可以将动态 SQL 的结果插入到临时表中,然后使用该表填充您的游标。

Declare @UserID varchar(100)
create table #users (UserID varchar(100))

declare @sqlstatement nvarchar(4000)
set @sqlstatement = 'Insert into #users (userID) SELECT userId FROM users'
exec(@sqlstatement)

declare users_cursor cursor for Select UserId from #Users
OPEN users_cursor
FETCH NEXT FROM users_cursor
INTO @UserId

WHILE @@FETCH_STATUS = 0
BEGIN

EXEC asp_DoSomethingStoredProc @UserId

FETCH NEXT FROM users_cursor
INTO @UserId

END
CLOSE users_cursor
DEALLOCATE users_cursor

drop table #users

【讨论】:

  • 避免使用游标——它们是邪恶的! :-)
  • 光标不是邪恶的。当然,资源密集型和不建议在生产场景中使用。邪恶的没有。仅仅因为某些东西可以错误地使用并不意味着您不应该知道如何使用它。也许我误读了这个问题,但它似乎是在问如何使用光标,而不是在问他们使用的利弊。
  • 我要与邪恶同行......是的,OP 询问了如何使用光标。他还询问了“……这样做的正确方法”。负责任的答案是提供有关如何在没有光标的情况下完成最终结果的信息——(几乎)总是可以这样做。
  • 这让我有能力编写完全无法理解的代码!在非动态 sql 中使用的动态生成的 SQL 中声明的变量在执行路径中更远!我喜欢它!
  • 很少有代码结构本质上是邪恶的(我在看你的COBOL,可重写代码邪恶的),游标不是其中之一。当然,初学者可能很想接触它们,因为他们不了解 SQL 中的处理集,但有时它们可​​以挽救生命 - 我已经开发了几个 SSRS 报告,你不可能以任何其他方式处理
【解决方案2】:

此代码是带有游标的动态列的一个很好的示例,因为您不能在@STATEMENT 中使用“+”:

ALTER PROCEDURE dbo.spTEST
AS
    SET NOCOUNT ON
    DECLARE @query NVARCHAR(4000) = N'' --DATA FILTER
    DECLARE @inputList NVARCHAR(4000) = ''
    DECLARE @field sysname = '' --COLUMN NAME
    DECLARE @my_cur CURSOR
    EXECUTE SP_EXECUTESQL
        N'SET @my_cur = CURSOR FAST_FORWARD FOR
            SELECT
                CASE @field
                    WHEN ''fn'' then fn
                    WHEN ''n_family_name'' then n_family_name
                END
            FROM
                dbo.vCard
            WHERE
                CASE @field
                    WHEN ''fn'' then fn
                    WHEN ''n_family_name'' then n_family_name
                END
                LIKE ''%''+@query+''%'';
            OPEN @my_cur;',
        N'@field sysname, @query NVARCHAR(4000), @my_cur CURSOR OUTPUT',
        @field = @field,
        @query = @query,
        @my_cur = @my_cur OUTPUT
    FETCH NEXT FROM @my_cur INTO @inputList
    WHILE @@FETCH_STATUS = 0
    BEGIN
        PRINT @inputList
        FETCH NEXT FROM @my_cur INTO @inputList
    END
    RETURN

【讨论】:

  • 这对我有用,并且避免了全局游标和临时表。谢谢
  • 完美,非常有用,谢谢!。注意,似乎游标也必须在动态 sql 中打开——SQL Server 似乎认为游标没有初始化,如果在动态内容之后完成,则会向我抛出错误。
  • 我看不出在这里将@query 连接到sql 中是如何工作的。我想你会得到LIKE '%'xx'%';,假设@query 设置为'xx',这是无效的sql。以及 SP_EXECUTESQL 状态的文档:“@stmt 必须是 Unicode 常量或 Unicode 变量。”,这意味着您无法构建字符串并在没有临时变量的情况下立即执行它。
  • 如果它解释了为什么 OPEN CURSOR 语句需要放在动态 SQL 中,那将更加有用,而人们并不希望将它放在其中。
【解决方案3】:

通过 ODBC 连接使用非关系型数据库(IDMS 吗?)是游标和动态 SQL 似乎是唯一途径的时代之一。

select * from a where a=1 and b in (1,2)

需要 45 分钟来响应,而重写以使用没有 in 子句的键集将在 1 秒内运行:

select * from a where (a=1 and b=1)
union all
select * from a where (a=1 and b=2)

如果 B 列的 in 语句包含 1145 行,则使用游标创建单个语句并作为动态 SQL 执行它们比使用 in 子句快得多。傻了吧?

是的,在关系数据库中没有时间应该使用游标。我简直不敢相信我遇到了一个游标循环快几个数量级的实例。

【讨论】:

    【解决方案4】:

    首先,尽可能避免使用光标。以下是一些资源,可在您似乎离不开它时将其根除:

    There Must Be 15 Ways To Lose Your Cursors... part 1, Introduction

    Row-By-Row Processing Without Cursor

    不过,尽管如此,您可能最终还是会遇到一个问题——我从您的问题中了解的不够多,无法确定其中任何一个都适用。如果是这种情况,您就会遇到不同的问题——您的游标的选择语句必须是 实际 SELECT 语句,而不是 EXECUTE 语句。你被卡住了。

    但是请参阅 cmsjr(在我写作时出现)关于使用临时表的答案。我会避免使用 global 游标,而不是“普通”游标......

    【讨论】:

    • 仅供参考 - 这两篇文章都需要您立即注册才能查看。
    • 第二个例子真的很糟糕。他创建了一个存储过程和一个触发器,只是为了避免使用游标。我怀疑他的方法比简单的前向光标更好/更高效。
    【解决方案5】:

    我想和你分享另一个例子
    :D http://www.sommarskog.se/dynamic_sql.html#cursor0

    【讨论】:

    • 感谢您引用 Erland Sommarskog。他的帖子很有见地
    【解决方案6】:

    最近从 Oracle 切换到 SQL Server(雇主偏好)后,我注意到 SQL Server 中的游标支持滞后。与尝试通过重新排列或添加优化提示来调整复杂查询相比,游标并不总是邪恶的,有时需要,有时更快,有时更干净。 “游标是邪恶的”观点在 SQL Server 社区中更为突出。

    所以我猜这个答案是切换到Oracle或给MS一个线索。

    【讨论】:

      【解决方案7】:

      SQL Server 中的另一个选项是对存储过程中的表变量进行所有动态查询,然后使用游标进行查询和处理。至于可怕的光标辩论:),我看到研究表明,在某些情况下,如果正确设置,光标实际上可以更快。当所需的查询太复杂,或者不是人为的(对我来说;))可能时,我自己会使用它们。

      【讨论】:

        【解决方案8】:

        此代码可能对您有用。

        sql server 中游标使用示例

        DECLARE sampleCursor CURSOR FOR 
              SELECT K.Id FROM TableA K WHERE ....;
        OPEN sampleCursor
        FETCH NEXT FROM sampleCursor INTO @Id
        WHILE @@FETCH_STATUS <> -1
        BEGIN
        
        UPDATE TableB
           SET 
              ...
        

        【讨论】:

          猜你喜欢
          • 2015-07-03
          • 1970-01-01
          • 1970-01-01
          • 2010-10-06
          • 1970-01-01
          • 2010-10-20
          • 2018-12-08
          • 1970-01-01
          • 2015-01-28
          相关资源
          最近更新 更多