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