SQL Server 2008 存储过程 return 值

SQL Server 2008 stored procedure return value

这是我的存储过程

ALTER PROCEDURE Delete
     @ID nvarchar(64),
     @value int = 0 output
AS
BEGIN
    IF(EXISTS(SELECT * FROM A where Ap = @ID))
    BEGIN
        set @value = 1
    END
    ELSE IF(EXISTS(SELECT * FROM B where Bp = @ID))
    BEGIN
        set @value = 2
    END
    ELSE
    BEGIN
        select * 
        from Table_D
    END
END

RETURN @value

问题是当我执行它时,这 return 没有任何值

return 似乎超出了程序的范围。尝试:

ALTER PROCEDURE Delete
    @ID nvarchar(64),
    @value int=0 output
AS
BEGIN

    IF(EXISTS(SELECT * FROM A where Ap=@ID))
    BEGIN
        set @value=1
    END

    ELSE IF(EXISTS(SELECT * FROM B where Bp=@ID))
    BEGIN
        set @value=2
    END

    ELSE
    BEGIN
    set @value=5
    end --end if

    RETURN @value
end --end procedure

这就是正确使用制表符使代码更具可读性的地方,这些问题也更加明显

不要使用输出参数。相反,使用这个:

ALTER PROCEDURE Delete
@ID nvarchar(64)

AS
BEGIN

DECLARE @value int
SET @value = 0

IF(EXISTS(SELECT 1 FROM A where Ap=@ID))
BEGIN
set @value=1
END

ELSE IF(EXISTS(SELECT 1 FROM B where Bp=@ID))
BEGIN
set @value=2
END

ELSE
BEGIN
set @value=5
end

Select @value as Value, * from Table_D
end

你能试试运行 SP 作为下面的脚本吗?

declare @pID as nvarchar(64)
declare @pValue as int

set @pID = 1 -- Your ID filter

exec Delete @pID, @pValue OUTPUT

select @pValue

有多种方法可以将状态信息从存储过程return发送到应用程序。各有利弊;没有一种技术可以绝对地说在所有情况下都是正确的。即便如此,我还是要开始:

TL;DR:建议

如果您的存储过程遇到问题并且无法 return 正常 return 存储数据,请使用 RAISERROR。使用 OUTPUT 参数获取客户不能随意忽略的信息,但从逻辑上讲,这不是您的结果的一部分。如果您有客户端可以随意忽略的信息状态代码,请使用 return 值。仅当您知道自己在做什么时才使用其他结果集。

RAISERROR

如果您的存储过程遇到错误并且无法return任何数据,您可以使用RAISERROR终止执行并在客户端引发异常。

CREATE PROCEDURE [Delete]
     @ID nvarchar(64)
AS BEGIN
    IF(EXISTS(SELECT * FROM A where Ap = @ID))
    BEGIN
        RAISERROR('Wrong. Try again.', 11, 1);
        RETURN;
    END
    ELSE IF(EXISTS(SELECT * FROM B where Bp = @ID))
    BEGIN
        RAISERROR('Wrong in a different way. Try again.', 11, 2);
        RETURN;
    END
    ELSE
    BEGIN
        select * 
        from Table_D
    END
END

第二个参数(严重性)必须至少设置为 11 才能使错误作为异常传播,否则它只是一条信息性消息。这些也可以被捕获,但这超出了这个答案的范围。第三个参数(状态)可以是任何你喜欢的,并且可以用来传递错误的代码,例如,如果你需要本地化它。用户生成的消息总是有SQL错误代码50000,所以不能用来区分不同的错误,并且解析消息是脆弱的。

处理结果的C#代码:

try {
    using (var reader = command.ExecuteReader()) {
        while (reader.Read()) {
            ...
        }
    }
} catch (SqlException e) {
    Console.WriteLine(
        "Database error executing [Delete] (code {0}): {1}", e.State, e.Message
    );
}

这很适合错误,因为实际处理数据的代码保持原样,您可以在正确的位置处理异常(而不是到处传播状态代码)。但是,如果存储过程是 expected 到 return 信息性而非错误的状态,则此方法不合适,因为即使什么都没有,您也会一直捕获异常错了。

输出参数

存储过程可以设置参数值以及接收它们,方法是声明它们 OUTPUT:

CREATE PROCEDURE [Delete]
     @ID nvarchar(64),
     @StatusCode INT OUTPUT
AS BEGIN
    IF(EXISTS(SELECT * FROM A where Ap = @ID))
    BEGIN
        SET @StatusCode = 1;
    END
    ELSE IF(EXISTS(SELECT * FROM B where Bp = @ID))
    BEGIN
        SET @StatusCode = 2;
    END
    ELSE
    BEGIN
        SET @StatusCode = 0;
        select * 
        from Table_D
    END
END

在 C# 中,这是在标记为输出参数的参数中捕获的:

SqlParameter statusCodeParameter = command.Parameters.Add(
    new SqlParameter {
        ParameterName = "@StatusCode",
        SqlDbType = SqlDbType.Int,
        Direction = ParameterDirection.Output 
    }
);
using (var reader = command.ExecuteReader()) {
    int statusCode = (int) statusCodeParameter.Value;
    if (statusCode != 0) {
        // show alert
        return;
    }
    while (reader.Read()) {
        ...    
    }
}

这里的好处是客户端不能忘记声明参数(必须提供),你不局限于单个INT,你可以根据参数的值来决定你想用结果集做什么。 Return以这种方式处理结构化数据很麻烦(很多 OUTPUT 参数),但您可以在单个 XML 参数中捕获它。

Return值

每个存储过程都有一个 return 值,它是一个 INT。如果您没有使用 RETURN 显式设置它,它将保持为 0。

CREATE PROCEDURE [Delete]
     @ID nvarchar(64)
AS BEGIN
    IF(EXISTS(SELECT * FROM A where Ap = @ID))
    BEGIN
        RETURN 1
    END
    ELSE IF(EXISTS(SELECT * FROM B where Bp = @ID))
    BEGIN
        RETURN 2
    END
    ELSE
    BEGIN
        select * 
        from Table_D
    END
END

在 C# 中,return 值必须在标记为 return 值的单个特殊参数中捕获:

SqlParameter returnValueParameter = command.Parameters.Add(
    new SqlParameter { Direction = ParameterDirection.ReturnValue }
);
using (var reader = command.ExecuteReader()) {
    // this could be empty
    while (reader.Read()) {
        ...
    }
}
int returnValue = (int) returnValueParameter.Value;

请务必注意,在您处理完存储过程生成的所有其他结果集(如果有)之前,return 值将不可用,因此如果您将它用于状态指示没有行的代码,在获得状态代码之前,您仍然必须先处理空结果集。你不能 return 除了 INT 以外的任何东西。 Frameworks/OR 映射器通常不支持 return 值。最后,请注意客户端不需要对 return 值执行任何操作,因此您必须仔细记录其预期用途。

结果集

存储过程可以简单地 return 它想要的结果集,就像它 return 其他数据一样。存储过程允许 return 多个结果集,因此即使您的状态在逻辑上与其他数据分开,您也可以 return 它作为一行。

CREATE PROCEDURE [Delete]
     @ID nvarchar(64)
AS BEGIN
    DECLARE @StatusCode INT = 0;
    IF(EXISTS(SELECT * FROM A where Ap = @ID))
    BEGIN
        SET @StatusCode = 1;
    END
    ELSE IF(EXISTS(SELECT * FROM B where Bp = @ID))
    BEGIN
        SET @StatusCode = 2;
    END

    SELECT @StatusCode AS StatusCode;

    IF @StatusCode = 0
    BEGIN
        select * 
        from Table_D
    END
END

要用 C# 处理这个,我们需要 SqlDataReader.NextResult:

using (var reader = command.ExecuteReader()) {
    if (!reader.Read()) throw new MyException("Expected result from stored procedure.");
    statusCode = reader.GetInt32(reader.GetOrdinal("StatusCode"));
    if (statusCode != 0) {
        // show alert
        return;
    }
    reader.NextResult();
    while (reader.Read()) {
        // use the actual result set
    }
}

这里的主要缺点是存储过程 return 可变数量的结果集并不直观,而且很少有数据 frameworks/OR 映射器支持它,所以你几乎总是最终编写这样的手动代码。 Returning 多个结果集并不真正适合 returning 像状态代码这样的单个数据,但它可能是 returning 结构化数据的替代方法XML 输出参数(特别是如果有很多的话)。