Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/visual-studio-code/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Sql server 如何在不结束SQL Server存储过程执行的情况下在内部事务中引发异常?_Sql Server_Exception_Stored Procedures_Transactions_Throw - Fatal编程技术网

Sql server 如何在不结束SQL Server存储过程执行的情况下在内部事务中引发异常?

Sql server 如何在不结束SQL Server存储过程执行的情况下在内部事务中引发异常?,sql-server,exception,stored-procedures,transactions,throw,Sql Server,Exception,Stored Procedures,Transactions,Throw,我的目标是向调用者抛出异常,但继续执行SQLServer存储过程。因此,本质上,我试图实现的是一个try..catch..finally块,尽管据我所知,SQL Server没有try..catch..finally块的概念 我有一个示例存储过程来说明。这只是我头脑中突然想到的一个例子,所以请不要太注意表模式。希望你能理解我在这里要做的事情的要点。无论如何,存储的proc包含一个显式事务,它在catch块中抛出一个异常。在try..catch块之后还有进一步的执行,但如果执行了THROW,则它永

我的目标是向调用者抛出异常,但继续执行
SQLServer
存储过程。因此,本质上,我试图实现的是一个
try..catch..finally块
,尽管据我所知,SQL Server没有try..catch..finally块的概念

我有一个示例
存储过程来说明。这只是我头脑中突然想到的一个例子,所以请不要太注意表模式。希望你能理解我在这里要做的事情的要点。无论如何,存储的proc包含一个
显式事务
,它在
catch块
中抛出一个
异常
。在try..catch块之后还有进一步的执行,但如果执行了
THROW
,则它永远不会执行。据我所知,至少在SQL Server中,THROW无法区分内部事务和外部事务或嵌套事务

在这个存储过程中,我有两个表:Tbl1和Tbl2。Tbl1在Tbl1.ID上有一个
主键。Tbl2在EmpFK上有一个映射到Tbl1.ID的
外键。EmpID有一个唯一的约束。不能在Tbl1中插入重复记录。Tbl1和Tbl2在ID上都有主键,并使用标识增量自动插入。存储的proc有三个输入参数,其中一个是employeeID

在内部事务中,在Tbl1中插入一条记录——添加一个新的员工ID。如果失败了,那么想法是事务应该正常地出错,但是存储的进程应该继续运行,直到完成。无论表插入成功还是失败,稍后都将使用EmpID来填写EmpFk

在try..catch块之后,我通过传递到存储过程中的employeeID参数执行Tbl1.ID的查找。然后,我在TBl2中插入一条记录;Tbl1.ID是Tbl2.EmpFK的值

(您可能会问“为什么要使用这样的模式?为什么不使用这样小的数据集合并到一个表中?”同样,这只是一个例子。它不一定是员工。你可以选择任何东西。它只是一个小部件。想象一下Tbl1可能包含一个非常非常大的数据集。实际设置是有两个表具有主键/外键关系。)

以下是示例数据集:

Tbl1
ID EmpID
1  AAA123
2  AAB123
3  AAC123

Tbl2
ID Role        Location EmpFK
1  Junior      NW       1
2  Senior      NW       2
3  Manager     NE       2
4  Sr Manager  SE       3
5  Director    SW       3
以下是示例存储过程:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

CREATE PROCEDURE [dbo].[usp_TestProc]

    @employeeID VARCHAR(10)
    ,@role VARCHAR(50)
    ,@location VARCHAR(50)

AS
BEGIN

    SET NOCOUNT ON;

    DECLARE @employeeFK INT;

    BEGIN TRY
        BEGIN TRANSACTION MYTRAN;

            INSERT [Tbl1] (
                [EmpID]
            )
            VALUES (
                @employeeID
            );

        COMMIT TRANSACTION MYTRAN;
    END TRY

    BEGIN CATCH

        IF @@TRANCOUNT > 0
        BEGIN

            ROLLBACK TRANSACTION MYTRAN;

        END;

        THROW; -- Raises exception, exiting stored procedure

    END CATCH;

    SELECT
        @employeeFK = [ID]
    FROM
        [Tbl1]
    WHERE
        [EmpID] = @employeeID;

    INSERT [Tbl2] (
        [Role]
        ,[Location]
        ,[EmpFK]
    )
    VALUES (
        @role
        ,@location
        ,@employeeFK
    );

END;
因此,我仍然希望将错误返回给调用方,即记录错误,但我不希望它停止存储过程的执行。它应该继续非常类似于try..catch..finally块。这可以通过
THROW
来实现,还是我必须使用其他方法

也许我弄错了,但是
THROW
不是
RAISERROR
的升级版本吗?而且,我们应该使用前者来处理异常

我过去在这些情况下使用过
RAISERROR
,它非常适合我。但是
THROW
是一个更简单、更优雅的解决方案,imo,并且可能是今后更好的实践。我不太确定

提前谢谢你的帮助

一成不变的是,有两个表有一个主键/ 外键关系

在内部事务中使用THROW不是您想要的方式。从代码判断,您希望插入新员工,除非该员工已经存在,然后,无论该员工是否已经存在,您都希望在第二次插入子表时使用该员工的PK/id

这样做的一种方法是分割逻辑。这是我的意思的伪代码:

IF NOT EXISTS(Select employee with @employeeId)
  INSERT the new employee

SELECT @employeeFK like you are doing.

INSERT into Table2 like you are doing.

如果在传递已存在的@employeeId时仍需要引发错误,则可以在If之后放置一个ELSE,并填充一个字符串变量,在过程结束时,如果已填充该变量,则抛出/引发错误。

RAISERROR
未弃用,因此可以使用它来实现此行为。(实际上无法在这里复制bahaviour,但是,如果您在自己的实例上使用此SQL,您将看到返回两个结果集,一个没有行,另一个有行)这简直是一个错误。@Larnu。非常感谢。在我的SQL Server实例上,会引发异常,并且第二次插入永远不会完成。据我所知,SQL Server不会在事务之间破译抛出的异常;因此,
THROW
退出存储的进程。如果不是这样,我将回到绘图板,看看是否可以在实例中复制DB Fiddle。再次感谢你的帮助。@SeanLange我不太清楚你为什么这么说。这不是面试问题,但我经常看到这种面试问题。如果在面试中有人问我这个确切的问题,我会尽我所知回答。我无法回答这是一个XY问题。我必须解决它。如果问题被定义为如何在不停止执行的情况下抛出,这就是手头的任务,那么它就不是XY问题。根据什么理解,您认为您可以向调用方抛出异常并继续执行?这不是通常定义的
try
/
catch
/
最终的方法。啊。好主意。非常感谢。我仍然可以记录异常,这是最理想的。我可以使用
THROW
吗?或者仅当它位于catch块内时才使用?否则,我将坚持使用
RAISERROR
来处理异常。再次感谢。