简体   繁体   English

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

[英]How to THROW exception in inner transaction without ending SQL Server stored procedure execution?

My objective is to throw an exception back to the caller but continue execution of the SQL Server stored procedure. 我的目标是将异常返回给调用者,但继续执行SQL Server存储过程。 So, in essence, what I'm trying to accomplish is a try..catch..finally block , even though SQL Server has no concept of a try..catch..finally block, to my knowledge. 因此,从本质上讲,据我所知,即使SQL Server没有try..catch..finally块的概念,我想要完成的也是try..catch..finally block

I have a sample stored procedure to illustrate. 我有一个示例stored procedure来说明。 It's just an example I came up with off the top of my head, so please don't pay too much attention to the table schema. 这只是我想到的一个例子,所以请不要过多地关注表模式。 Hopefully, you understand the gist of what I'm trying to carry out here. 希望您了解我正在尝试执行的要点。 Anyway, the stored proc contains an explicit transaction that throws an exception within the catch block . 无论如何,存储的proc包含一个explicit transaction ,该explicit transactioncatch block内引发exception There's further execution past the try..catch block but it's never executed, if THROW is executed. 在try..catch块之后还有进一步的执行,但是如果执行THROW ,则永远不会执行。 From what I understand, at least in SQL Server, THROW cannot distinguish between inner and outer transactions or nested transactions. 据我了解,至少在SQL Server中,THROW无法区分内部事务和外部事务还是嵌套事务。

In this stored procedure, I have two tables: Tbl1 and Tbl2 . 在此存储过程中,我有两个表: Tbl1Tbl2 Tbl1 has a primary key on Tbl1.ID . TBL1有一个primary keyTbl1.ID。 Tbl2 has a foreign key on EmpFK that maps to Tbl1.ID . TBL2有一个foreign key映射到Tbl1.IDEmpFK。 EmpID has a unique constraint. EmpID具有唯一约束。 No duplicate records can be inserted into Tbl1 . 不能将重复的记录插入Tbl1中 Both Tbl1 and Tbl2 have primary key on ID and employ identity increment for auto-insertion. Tbl1Tbl2都具有ID上的主键,并采用身份增量进行自动插入。 The stored proc has three input parameters, one of which is employeeID . 存储的proc具有三个输入参数,其中之一是employeeID

Within the inner transaction, a record is inserted in Tbl1 -- a new employee ID is added. 在内部事务中,将一条记录插入Tbl1中 -添加新的员工ID。 If it fails, the idea is the transaction should gracefully error out but the stored proc should still continue running until completion. 如果失败,则认为事务应正常出错,但存储的proc应仍继续运行直到完成。 Whether table insert succeeds or fails, EmpID will be employed later to fill in EmpFk . 无论表插入成功还是失败,稍后都会使用EmpID来填充EmpFk

After the try..catch block, I perform a lookup of Tbl1.ID , via the employeeID parameter that's passed into the stored proc. 在try..catch块之后,我通过传递给存储proc的employeeID参数执行Tbl1.ID的查找。 Then, I insert a record into TBl2; 然后,我在TBl2中插入一条记录; Tbl1.ID is the value for Tbl2.EmpFK . Tbl1.IDTbl2.EmpFK值。

(And you might be asking "why use such a schema? Why not combine into one table with such a small dataset?" Again, this is just an example. It doesn't have to be employees. You can pick anything. It's just a widget. Imagine Tbl1 may contain a very, very large data set. What's set in stone is there are two tables which have a primary key / foreign key relationship.) (您可能会问“为什么要使用这样的模式?为什么不将一个如此小的数据集合并到一个表中呢?”再次,这只是一个示例。它不必是雇员。您可以选择任何东西。想象一下Tbl1可能包含非常非常大的数据集。 确切地说 ,有两个表具有主键/外键关系。)

Here's the sample data set: 这是示例数据集:

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

Here's the sample stored procedure: 这是示例存储过程:

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;

So, again, I still want to return the error to the caller to, ie log the error, but I don't wish for it to stop stored procedure execution cold in its tracks. 因此,再次,我仍然想将错误返回给调用者,即记录该错误,但是我不希望它在其轨道中停止存储过程的执行。 It should continue on very similarly to a try..catch..finally block. 它应该继续非常类似于try..catch..finally块。 Can this be accomplished with THROW or I must use alternative means? 可以使用THROW完成此操作,还是必须使用其他方法?

Maybe I'm mistaken but isn't THROW an upgraded version of RAISERROR and, going forward, we should employ the former for handling exceptions? 也许我错了,但不THROW的升级版RAISERROR ,并展望未来,我们应该使用前者为处理异常?

I've used RAISERROR in the past for these situations and it's suited me well. 在过去的这些情况下,我使用过RAISERROR ,它非常适合我。 But THROW is a more simpler, elegant solution, imo, and may be better practice going forward. 但是, THROW是一个更简单,更优雅的解决方案,imo,并且可能是以后的更好实践。 I'm not quite sure. 我不太确定

Thank you for your help in advance. 提前谢谢你的帮助。

What's set in stone is there are two tables which have a primary key / foreign key relationship. 刻板的是有两个具有主键/外键关系的表。

Using THROW in an inner transaction is not the way to do what you want. 在内部事务中使用THROW并不是实现您想要的方式。 Judging from your code, you want to insert a new employee, unless that employee already exists, and then, regardless of whether the employee already existed or not, you want to use that employee's PK/id in a second insert into a child table. 从您的代码判断,您想要插入一个新员工,除非该员工已经存在,然后,无论该员工是否已经存在,您都希望在该子表的第二次插入中使用该员工的PK / id。

One way to do this is to split the logic. 一种方法是拆分逻辑。 This is psuedocode for what I mean: 这是我的意思的伪代码:

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

SELECT @employeeFK like you are doing.

INSERT into Table2 like you are doing.

If you still need to raise an error when an @employeeId that already exists is passed, you can put an ELSE after the IF, and populate a string variable, and at the end of the proc, if the variable was populated, then throw/raise an error. 如果在传递已经存在的@employeeId时仍然需要引发错误,则可以在IF后面放置一个ELSE,并填充一个字符串变量,如果在proc末尾填充了该变量,则抛出/引发错误。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM