必须声明标量变量

时间:2014-01-26 12:37:49

标签: sql-server tsql 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')

此SQL抛出此错误:

  

必须声明标量变量“@ value1”。

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

5 个答案:

答案 0 :(得分: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.

会话

引用锁定到特定spid的对象。在我的脑海中,我能想到的唯一类型的会话对象是一个普通的临时表,定义为#Table。处于会话范围本质上意味着在批处理(由GO终止)完成后,对该对象的引用将继续成功解析。 These are technically accessible by other sessions,但是这样做会有一定的壮举,因为他们在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对象名称。

批次

正常变量(例如@value1@value2)仅适用于声明它们的批处理。与#Temp表不同,只要查询块遇到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()以来,我只在最简单的实例中使用前者(显然,当我的语句中只有很少的“动态”元素时,主要是“欺骗”否则不合适的DDL CREATE语句在其他批次中间运行。上面的@AaronBertrand's答案是类似的,并且在性能上与以下类似,在评估动态语句时利用优化器的功能,但我认为扩展@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;

答案 1 :(得分: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;

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

答案 2 :(得分:0)

这是一个简单的例子:

Create or alter PROCEDURE getPersonCountByLastName (
@lastName varchar(20),
@count int OUTPUT
)
As
Begin
select @count = count(personSid) from Person where lastName like @lastName
End;

在一个批次中执行以下语句(通过全部选择)

1. Declare @count int
2. Exec getPersonCountByLastName kumar, @count output
3. Select @count

当我试图单独执行语句1,2,3时,我遇到了同样的错误。 但是当它们同时执行时,它运行良好。

原因是SQL在不同的会话中执行declare,exec语句。

打开进一步更正。

答案 3 :(得分:0)

如果您不立即运行所有语句,也会在SQL Server中发生这种情况。如果要突出显示一组语句并执行以下操作:

DECLARE @LoopVar INT
SET @LoopVar = (SELECT COUNT(*) FROM SomeTable)

然后尝试突出显示另一组语句,例如:

PRINT 'LoopVar is: ' + CONVERT(NVARCHAR(255), @LoopVar)

您将收到此错误。

答案 4 :(得分:0)

-创建或更改程序

ALTER PROCEDURE out(

@age INT,
@salary INT OUTPUT)

AS 开始

SELECT @salary = (SELECT SALARY FROM new_testing where AGE = @age  ORDER BY AGE OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY);

END

-----------------声明输出变量-------------------------- -------

DECLARE @test INT

---------------------执行查询时---------------------- -----------

执行25次,@salary = @test输出

打印@test

-------------------无需过程即可获得相同的输出----------------------- -------------------- SELECT * FROM new_testing,其中AGE = 25按年龄偏移排序0行仅抓取下一行1行