本文適用: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 -