我在存储过程中编写了这个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,我希望在变量中获取值。我该怎么办?
答案 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行