2012-06-19 26 views
1

我在存儲過程中觀察到一個奇怪的事物,在表變量上進行選擇。它總是返回在遊標的第一次迭代中獲取的值(在後續迭代中)。以下是一些證明這一點的示例代碼。遊標內部的表變量,奇怪的行爲 - SQL Server

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的代碼,並試圖各種組合,我觀察到去除從下面的線頂部,給我的右值超出表變量的一個光標的內部。

SELECT TOP 1 @outid = LogId FROM @Log; 

這將使它像這樣

SELECT @outid = LogId FROM @Log; 

我不知道這裏發生了什麼。我認爲表格變量的TOP 1應該工作,認爲在循環的每次迭代中都會創建一個新表格。有人可以在表變量範圍和壽命期間拋出光明。

更新:我有解決方案來繞過這裏的奇怪行爲。 作爲一種解決方案,我已經在循環前面的頂部聲明瞭該表並刪除了循環開始處的所有行。

+0

如果我正確地讀取了這段代碼,您可以在每個循環中創建一個新的@Log表。如果是這樣的話@Log總是隻有一個記錄!?你想要達到的目標是什麼,你期望什麼,真正發生了什麼。到目前爲止,我需要更多的澄清來提供幫助。 – YvesR

+0

@YvesR - 您沒有正確閱讀。它是一個聲明語句,不是可執行的行。它被隱式創建一次。代碼甚至不必到達那條線。例如'IF(1 = 0)開始聲明@T TABLE(X INT)END; SELECT * FROM @T;'(儘管解析器在它看到聲明之前遇到引用時會報錯) –

+0

@MartinSmith,你是對的。感謝您的解釋。 –

回答

4

這段代碼有很多事情要做。

首先,您錯誤地回滾了您的嵌入式交易,但我從來沒有看到您在成功時承諾。正如所寫,這將泄漏一個交易,這可能會在下面的代碼中引起重大問題。

對於@Log表情境,您可能會感到困惑的是,SQL Server不使用與C++或其他標準編程語言相同的變量範圍和生命週期規則。即使在光標塊中聲明表變量時,也只會得到一個@Log表,然後該表保留該批處理的其餘部分,並將多行插入到該表中。

因此,您使用TOP 1沒有意義,因爲沒有ORDER BY子句在表上施加任何確定性排序。如果沒有這些,你會得到SQL Server認爲適合給你的任何順序,在這種情況下,看起來就是插入順序,每次運行SELECT時,都會給出該日誌表的第一個插入元素。

如果您確實只需要最後一個ID值,則需要爲您的@Log表提供一些真實的排序標準 - 某些形式的自動編號或日期字段與數據列一起可用於提供正確的排序你想做什麼。

+0

我已刪除交易件,因爲它與所述問題無關。 –

+0

我使用top 1作爲衡量標準。但考慮到可應用的變量範圍和生命週期規則,關於order by子句的觀點是有效的。作爲解決方案,我已經在外部聲明瞭表變量,並且在插入語句插入任何內容之前刪除光標內的所有行。 –