必须声明标量变量

Müs*_*ÜRK 1 t-sql sql-server stored-procedures executequery

我在存储过程中编写了这个SQL但没有工作,

declare @tableName varchar(max) = 'TblTest'
declare @col1Name varchar(max) = 'VALUE1'
declare @col2Name varchar(max) = 'VALUE2'
declare @value1 varchar(max)
declare @value2 varchar(200)

execute('Select TOP 1 @value1='+@col1Name+', @value2='+@col2Name+' From '+ @tableName +' Where ID = 61')

select @value1

execute('Select TOP 1 @value1=VALUE1, @value2=VALUE2 From TblTest Where ID = 61')
Run Code Online (Sandbox Code Playgroud)

此SQL抛出此错误:

必须声明标量变量"@ value1".

我正在动态生成SQL,我想在变量中获取值.我该怎么办?

Ava*_*rkx 21

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.
Run Code Online (Sandbox Code Playgroud)

会议:

引用锁定到特定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;
Run Code Online (Sandbox Code Playgroud)

打开一个新会话(具有单独spid的连接),上面的第二批将失败,因为该会话将无法解析#t_Test对象名称.

批次:

正常变量(例如您的@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;
Run Code Online (Sandbox Code Playgroud)

好的,那又怎样?

动态语句在这里发生的是该EXECUTE()命令有效地评估为一个单独的批处理,而不会破坏您执行它的批处理. EXECUTE()是好的,但是自从引入之后sp_executesql(),我只在最简单的实例中使用前者(显然,当我的语句中只有很少的"动态"元素时,主要是"欺骗"否则不合适的DDL CREATE语句在其他批次中间运行). @ 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;
Run Code Online (Sandbox Code Playgroud)


Aar*_*and 7

Declare @v1 varchar(max), @v2 varchar(200);

Declare @sql nvarchar(max);

Set @sql = N'SELECT @v1 = value1, @v2 = value2
FROM dbo.TblTest -- always use schema
WHERE ID = 61;';

EXEC sp_executesql @sql,
  N'@v1 varchar(max) output, @v2 varchar(200) output',
  @v1 output, @v2 output;
Run Code Online (Sandbox Code Playgroud)

您还应该将输入(如61来自哪里)作为正确的参数传递(但您无法以这种方式传递表名和列名).