【SQL】當心在循環中聲明變量——淺析SQL變量做用域

本文適用:T-SQL(SQL Server)sql

先看這個語句:spa

DECLARE @i INT = 0
WHILE @i < 3 --跑3圈
BEGIN
    --每圈都定義一個表變量,並插入一行
    DECLARE @t TABLE(Col INT PRIMARY KEY) --主鍵惟一約束
    INSERT @t VALUES (1)

    SET @i += 1
END

若是你認爲這個語句跑起來沒問題,那你值得看下去,會避免之後踩到【SQL變量做用域】的坑。code

事實上這個語句會報2次「違反了PRIMARY KEY約束…」,緣由是@t這個表變量,並非在每一圈都從新聲明一個新的,而是聲明1次後就一直沿用,因爲該表具備主鍵約束,因此以後的兩圈在插入的時候,因爲已經存在相同主鍵,因而報上述錯誤。server

換成普通變量也同樣:blog

DECLARE @i INT = 0
WHILE @i < 3 --跑3圈
BEGIN
    --一樣,該變量也只會聲明1次,以後沿用
    DECLARE @s VARCHAR(20)

    IF @s IS NULL --因此第1圈會進入該分支
        SET @s = 's'
    ELSE          --以後的圈則進入該分支
        SET @s += 's'
    
    PRINT @s
    
    SET @i += 1
END

--執行結果:
s
ss
sss

因此到這裏能得出一個結論:element

循環中的變量只會聲明一次,並在以後一直沿用。作用域

理解這一點很重要,由於這與C#等編譯語言很是不一樣,C#中每一圈聲明的變量都至關於從新建一個,與上一圈的毫無關係,但在sql中不能這麼思考。rem

 

嘗試把上面的語句小改一下:get

DECLARE @i INT = 0
WHILE @i < 3 --跑3圈
BEGIN
    DECLARE @s VARCHAR(20) = 's' --聲明並賦值
    SET @s += 's'
    
    PRINT @s
    
    SET @i += 1
END

此次獲得的結果會是3個ss,看起來是@s在每一圈獲得了重建,那這彷佛與上面的結論有悖,不是隻會聲明1次嗎?其實並無矛盾,而是【declare @s xxx = 's'】至關於【declare @s xxx】+【set @s = 's'】倆語句,聲明的確只有1次,但稍後的賦值倒是每圈都在進行,至關於每圈一開始都把@s重置爲's',因此是這個結果。這也提醒:見到declare @x xxx = xxx時,要當作兩個動做編譯

 

其實這個問題本質上是一個變量做用域問題,只不過SQL中的變量做用域,與C#等語言按語句塊劃分不同,SQL的變量做用域是【批】,這一點在MSDN中有說。好比下面的語句:

IF 1 = 2
    DECLARE @s VARCHAR(20)

SELECT @s

按說declare @s並不會獲得執行,@s並無聲明,但事實上這個語句一切正常,不會報錯。緣由就在於聲明語句比較特殊,它並不依賴位置,系統「見到」就算數,因此無論變量在多深的語句塊中聲明,它在本批接下來的語句中都是有效的。印象中某種SQL的寫法是聲明在一個區,邏輯在一個區,既然你t-sql的聲明具備「提高」這種特色,我認爲作成那種比較好,而不是混在邏輯語句中搞特殊。

回到開頭的問題,如今咱們清楚,雖然變量在循環中聲明,但它並不會被屢次執行,甚至不是在第1圈的時候執行,而是在某個時機由系統將全部聲明統一執行,大概相似C#的靜態字段,無論定義在哪裏,CLR會確保在使用該類前完成初始化。

 

至於什麼叫一【批】SQL,我沒有找到很正式的定義,根據所學,個人理解是:沒GO就是一批;有GO的話,GO之間算一批;exec、sp_executesql算一批;ssms中選中執行的部分算一批(前提是選中部分不含上述劃分點)。若有錯漏還請指正,感謝。

 

- EOF -

相關文章
相關標籤/搜索