“必须在存储过程中声明表变量”@name“”

时间:2013-11-28 11:30:21

标签: sql sql-server

我有一个返回错误的程序:

  

必须声明表变量" @ PropIDs"。

但接下来是消息:

  

(123行(s)受影响)

使用

执行时出现错误
EXEC [dbo].[GetNeededProperties] '1,3,5,7,2,12', '06/28/2013', 'TT'

时效果很好
EXEC [dbo].[GetNeededProperties] NULL, '06/28/2013', 'TT'

任何人都可以帮助我吗? 程序:

CREATE PROCEDURE [dbo].[GetNeededProperties]
@NotNeededWPRNs nvarchar(max), --string like '1,2,3,4,5'
@LastSynch datetime,
@TechCode varchar(5)
AS
BEGIN

DECLARE @PropIDs TABLE
(ID bigint)
Declare @ProductsSQL nvarchar(max);
SET @ProductsSQL = 'Insert into @PropIDs (ID) 
SELECT [WPRN] FROM [dbo].[Properties] WHERE(WPRN in (' + @NotNeededWPRNs + '))'
exec sp_executesql @ProductsSQL

SELECT  p.WPRN AS ID,
p.Address  AS Address,
p.Address AS Street
  FROM [dbo].[Properties] AS p
WHERE 
   p.WPRN NOT IN( SELECT ID FROM @PropIDs)

我在宣布这样的表时找到了一种解决方案:

IF OBJECT_ID('#PropIDs', 'U') IS NOT NULL
  DROP TABLE #PropIDs

CREATE  TABLE  #PropIDs

但是当从C#(linq sql)执行过程时,它会返回错误

4 个答案:

答案 0 :(得分:14)

问题在于您将动态SQL与非动态SQL混合在一起。

首先 - 当你将NULL放入@NotNeededWPRNs时它起作用的原因是因为当该变量为NULL时,你的@ProductsSQL变为NULL。

您需要做的是将@PropsIDs表作为非表变量以及临时表或物理表。 要么 你需要将所有东西都包装在动态SQL中并执行它。

所以简单的方法是做这样的事情:

Declare @ProductsSQL nvarchar(max);
    SET @ProductsSQL = '
    DECLARE @PropIDs TABLE
    (ID bigint)
    Insert into @PropIDs (ID) 
    SELECT [WPRN] FROM [dbo].[Properties] WHERE(WPRN in (' + @NotNeededWPRNs + '))

    SELECT  p.WPRN AS ID,
    p.Address  AS Address,
    p.Address AS Street
      FROM [dbo].[Properties] AS p
    WHERE 
       p.WPRN NOT IN( SELECT ID FROM @PropIDs)
    '

并执行它。 或者如上所述 - 将@ProdIDs更改为临时表。 (你在CREATE #ProdIds中接近的路线,但是你需要在sproc中的任何地方使用#ProdIDs而不是@ProdIDs。)

答案 1 :(得分:5)

您收到此错误的原因是表变量的范围仅限于一个批处理,因为sp_executesql在其自己的批处理中运行,它不知道您已在另一批中声明它。

@NotNeededWPRNsNULL时,它会起作用,因为连接NULL会产生NULL(除非另有设定),所以您只是执行:

exec sp_executesql null;

我还要说,如果您使用的是SQL Server 2008或更高版本,请考虑使用table valued parameters而不是分隔的字符串列表。这样更安全,更高效,并验证输入,如果我将1); DROP TABLE dbo.Prioperties; --作为@NotNeededWPRNs传递,您可能会发现自己没有属性表。

首先,您需要创建类型(我倾向于使用通用名称来实现可重用性):

CREATE TYPE dbo.IntegerList TABLE (Value INT);

然后您可以将其添加到您的程序中:

CREATE PROCEDURE [dbo].[GetNeededProperties]
    @NotNeededWPRNs dbo.IntegerList READONLY,
    @LastSynch DATETIME,
    @TechCode VARCHAR(5)
    AS
    BEGIN

    SELECT  p.WPRN AS ID,
            p.Address  AS Address,
            p.Address AS Street
     FROM   [dbo].[Properties] AS p
    WHERE   p.WPRN NOT IN (SELECT Value FROM @NotNeededWPRNs)

在不相关的说明中,您应该尽可能避免使用区域性敏感的日期格式,06/28/2013显然应该是6月28日,但06/07/2013如果没有设置DATEFORMAT或者,您如何知道这些语言是否会在7月6日或6月7日被阅读?使用的最佳格式是yyyyMMdd,它永远不会模糊,即使在某些设置中ISO标准格式yyyy-MM-dd也可以解释为yyyy-dd-MM

答案 2 :(得分:2)

将您的代码更改为:

Declare @ProductsSQL nvarchar(max);
SET @ProductsSQL = 'DECLARE @PropIDs TABLE
(ID bigint);
Insert into @PropIDs (ID) 
SELECT [WPRN] FROM [dbo].[Properties] WHERE(WPRN in (' + @NotNeededWPRNs + '))'
exec sp_executesql @ProductsSQL

在动态SQL外部声明的表变量将不可用于动态SQL。

答案 3 :(得分:0)

你可以通过创建一个使用CTE的sql函数来避免使用动态sql(我在sqlservercentral上发现了多年前的代码--Amit Gaur):

使用以下内容更改过程的主体:

SELECT  p.WPRN AS ID,
p.Address  AS Address,
p.Address AS Street
  FROM [dbo].[Properties] AS p
WHERE 
   p.WPRN NOT IN ( SELECT item FROM dbo.strToTable(@NotNeededWPRNs, ','))

在将字符串转换为表格的sql代码下面:

CREATE FUNCTION [dbo].[strToTable] 
(
    @array varchar(max),
    @del char(1)
)
RETURNS 
@listTable TABLE 
(
    item int
)
AS
BEGIN

    WITH rep (item,list) AS
    (
        SELECT SUBSTRING(@array,1,CHARINDEX(@del,@array,1) - 1) as item,
        SUBSTRING(@array,CHARINDEX(@del,@array,1) + 1, LEN(@array)) + @del list

        UNION ALL

        SELECT SUBSTRING(list,1,CHARINDEX(@del,list,1) - 1) as item,
        SUBSTRING(list,CHARINDEX(@del,list,1) + 1, LEN(list)) list
        FROM rep
        WHERE LEN(rep.list) > 0
    )
    INSERT INTO @listTable
    SELECT item FROM rep

    RETURN 
END