1

我在表变量上使用 select 的存储过程中观察到一个奇怪的事情。它总是返回在游标的第一次迭代中获取的值(在后续迭代中)。这是一些证明这一点的示例代码。

DECLARE @id AS INT;
DECLARE @outid AS INT;

DECLARE sub_cursor CURSOR FAST_FORWARD
    FOR SELECT [TestColumn]
        FROM   testtable1;

OPEN sub_cursor;

FETCH NEXT FROM sub_cursor INTO @id;

WHILE @@FETCH_STATUS = 0
BEGIN            
        DECLARE @Log TABLE (LogId BIGINT NOT NULL);
        PRINT 'id: ' + CONVERT (VARCHAR (10), @id);

        INSERT INTO Testtable2 (TestColumn)
        OUTPUT inserted.[TestColumn] INTO @Log
        VALUES (@id);

        IF @@ERROR = 0
        BEGIN
                SELECT TOP 1 @outid = LogId
                FROM   @Log;
                PRINT 'Outid: ' + CONVERT (VARCHAR (10), @outid);

                INSERT INTO [dbo].[TestTable3] ([TestColumn])
                VALUES (@outid);
        END
        FETCH NEXT FROM sub_cursor INTO @id;
    END

CLOSE sub_cursor;

DEALLOCATE sub_cursor;

但是,当我在 SO 上发布代码并尝试各种组合时,我观察到从下面的行中删除 top 会给我在游标内的表变量中的正确值。

SELECT TOP 1 @outid = LogId FROM   @Log;

这会让它像这样

SELECT @outid = LogId FROM   @Log;

我不确定这里发生了什么。我认为表变量上的 TOP 1 应该可以工作,认为循环的每次迭代都会创建一个新表。有人可以阐明表格变量范围和生命周期。

更新:我有解决方案来规避这里的奇怪行为。作为一种解决方案,我在循环之前在顶部声明了表格,并在循环开始时删除了所有行。

4

1 回答 1

4

这段代码有很多东西有点偏离。

首先,您在出错时回滚嵌入式事务,但我从未看到您成功提交它。如所写,这将泄漏事务,这可能会在以下代码中给您带来重大问题。

对于表情况,您可能会感到困惑的@Log是,SQL Server 没有使用与 C++ 或其他标准编程语言相同的变量范围和生命周期规则。即使在游标块中声明表变量时,您也只会得到一个@Log表,然后该表将用于批处理的其余部分,并且将多行插入其中。

因此,您的使用TOP 1并没有真正的意义,因为没有任何ORDER BY子句可以对表施加任何类型的确定性排序。没有它,您将获得 SQL Server 认为适合给您的任何顺序,在这种情况下,这似乎是插入顺序,每次运行时都会给您该日志表的第一个插入元素SELECT

如果你真的只想要最后一个 ID 值,你需要为你的@Log表提供一些真正的排序标准——数据列旁边的某种形式的自动编号或日期字段,可用于为你想要做的事情提供正确的排序.

于 2012-06-19T14:05:17.900 回答