您从动态语句中收到DECLARE
错误的原因是动态语句是在单独的批次中处理的,这归结为范围问题。虽然 SQL Server 中可用范围可能有更正式的定义,但我发现通常记住以下三个就足够了,从最高可用性到最低可用性排序:
全球:
服务器范围内可用的对象,例如使用双井号/井号创建的临时表 ( ##GLOBALTABLE
,但是您喜欢调用 # )。对全局对象要非常小心,就像对待任何应用程序、SQL Server 或其他方式一样;通常最好完全避免这些类型的事情。我实质上是说要牢记这个范围,以提醒您不要参与其中。
IF ( OBJECT_ID( 'tempdb.dbo.##GlobalTable' ) IS NULL )
BEGIN
CREATE TABLE ##GlobalTable
(
Val BIT
);
INSERT INTO ##GlobalTable ( Val )
VALUES ( 1 );
END;
GO
-- This table may now be accessed by any connection in any database,
-- assuming the caller has sufficient privileges to do so, of course.
会话:
引用锁定到特定 spid 的对象。在我的脑海中,我能想到的唯一类型的会话对象是一个普通的临时表,定义为#Table。处于会话范围内本质上意味着在批处理(由终止GO
)完成后,对该对象的引用将继续成功解析。 这些在技术上可以通过其他会话访问,但是以编程方式进行这样的操作将是一项壮举,因为它们在 tempdb 中获得了某种随机名称,并且无论如何访问它们有点让人头疼。
-- Start of session;
-- Start of batch;
IF ( OBJECT_ID( 'tempdb.dbo.#t_Test' ) IS NULL )
BEGIN
CREATE TABLE #t_Test
(
Val BIT
);
INSERT INTO #t_Test ( Val )
VALUES ( 1 );
END;
GO
-- End of batch;
-- Start of batch;
SELECT *
FROM #t_Test;
GO
-- End of batch;
打开一个新会话(与单独 spid 的连接),上面的第二批将失败,因为该会话将无法解析#t_Test
对象名称。
批次:
普通变量,例如 your@value1
和@value2
,仅适用于声明它们的批次。与#Temp
表不同,一旦您的查询块命中 a GO
,这些变量就停止对会话可用。这是产生错误的范围级别。
-- Start of session;
-- Start of batch;
DECLARE @test BIT = 1;
PRINT @test;
GO
-- End of batch;
-- Start of batch;
PRINT @Test; -- Msg 137, Level 15, State 2, Line 2
-- Must declare the scalar variable "@Test".
GO
-- End of batch;
好吧,那又怎样?
您的动态语句在这里发生的情况是该EXECUTE()
命令有效地作为一个单独的批处理进行评估,而不会破坏您从中执行它的批处理。 EXECUTE()
一切都很好,但是自从引入 以来sp_executesql()
,我只在最简单的情况下使用前者(明确地,当我的语句中根本没有“动态”元素时,主要是为了“欺骗”否则不适应 DDLCREATE
语句在其他批次的中间运行)。 @AaronBertrand上面的答案类似,并且在性能上与以下类似,在评估动态语句时利用优化器的功能,但我认为扩展@param
, 好,参数可能是值得的。
IF NOT EXISTS ( SELECT 1
FROM sys.objects
WHERE name = 'TblTest'
AND type = 'U' )
BEGIN
--DROP TABLE dbo.TblTest;
CREATE TABLE dbo.TblTest
(
ID INTEGER,
VALUE1 VARCHAR( 1 ),
VALUE2 VARCHAR( 1 )
);
INSERT INTO dbo.TblTest ( ID, VALUE1, VALUE2 )
VALUES ( 61, 'A', 'B' );
END;
SET NOCOUNT ON;
DECLARE @SQL NVARCHAR( MAX ),
@PRM NVARCHAR( MAX ),
@value1 VARCHAR( MAX ),
@value2 VARCHAR( 200 ),
@Table VARCHAR( 32 ),
@ID INTEGER;
SET @Table = 'TblTest';
SET @ID = 61;
SET @PRM = '
@_ID INTEGER,
@_value1 VARCHAR( MAX ) OUT,
@_value2 VARCHAR( 200 ) OUT';
SET @SQL = '
SELECT @_value1 = VALUE1,
@_value2 = VALUE2
FROM dbo.[' + REPLACE( @Table, '''', '' ) + ']
WHERE ID = @_ID;';
EXECUTE dbo.sp_executesql @statement = @SQL, @param = @PRM,
@_ID = @ID, @_value1 = @value1 OUT, @_value2 = @value2 OUT;
PRINT @value1 + ' ' + @value2;
SET NOCOUNT OFF;