T-SQL If Statement问题

时间:2018-02-26 03:04:30

标签: c# sql-server tsql if-statement ado.net

我有两张桌子:dbo.Videosdbo.Checkouts dbo.Videos表格包含视频列表,而dbo.Checkouts表格会跟踪已检出的视频。
我的TSQL命令的目标是在dbo.Checkouts表中插入一个新行,包括VideoIdUserIdCheckoutDate。 一旦成功,我就要更新dbo.Videos,并根据所选的TotalCopies递减VideoID列值,只有当值大于0时 如果小于0我想抛出异常 两个表中的VideoID都是通过外键链接的 但是,我在下面的声明中包含的IF语句会引发错误。

INSERT INTO dbo.Checkouts (VideoId, UserId, CheckoutDate)
VALUES (32, 'b0281f0d-8398-4a27-ba92-828bfaa9f90e', CURRENT_TIMESTAMP)

IF (SELECT TotalCopies FROM dbo.Videos WHERE VideoId = 32) > 0
UPDATE dbo.Videos SET
TotalCopies = TotalCopies - 1
WHERE VideoID = 32

1 个答案:

答案 0 :(得分:3)

你已经倒退了。
而不是将记录添加到Checkouts,然后测试您是否有Videos中的视频,您需要先检查您是否有可以查看的副本。
这就像你从任何一家商店购买东西一样 - 首先你将产品从货架上拿下来,然后才付出代价。
如果产品不在货架上,则无需付款。

第一个版本

您需要至少三个步骤才能正确执行:
首先,检查您是否有要复印的副本 如果没有,您不做任何事情,只需返回一条消息,表示没有免费的副本可以结账 如果有副本,则需要更新Video表(TotalCopies - = 1) 最后 - 您需要将记录插入checkouts

最重要的是,如果这些步骤中的任何一个失败,则所有步骤都会失败 - 例如,如果由于某种原因您未能将行插入checkouts,则必须恢复您在Video表,因为您无法完成此过程。

这是您需要在事务中包装整个流程的第一个原因 您需要事务的第二个原因是,如果有要检出的副本和video表的更新,则应避免测试之间的竞争条件。您可以在Dan Guzman关于Conditional INSERT/UPDATE Race Condition的博文中了解更多相关信息。

所以,说了这么多,让我们展示一些代码:

CREATE PROCEDURE VideoCheckout
(
     @VideoId int, 
     @UserId uniqueIdentifier,
     @Success bit OUTPUT
)
AS

    SET XACT_ABORT ON 

    SET @Success = 0 

    BEGIN TRANSACTION
    BEGIN TRY

        DECLARE @NumberOfCopies int
        SET @NumberOfCopies = ISNULL(
            (
                SELECT TotalCopies
                FROM dbo.Videos WITH (UPDLOCK, HOLDLOCK)
                WHERE VideoId = @VideoId
            )
            , 0)

        IF @NumberOfCopies > 0
        BEGIN

            UPDATE dbo.Videos
            SET TotalCopies = TotalCopies - 1
            WHERE VideoId = @VideoId;

            INSERT INTO dbo.Checkouts (VideoId, UserId, CheckoutDate)
            VALUES (@VideoId, @UserId, CURRENT_TIMESTAMP)

            SET @Success = 1
        END

        COMMIT TRANSACTION
    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0
            ROLLBACK TRANSACTION
    END CATCH

GO

更新 - 使用@@rowcount版本:

SQL Server的@@Rowcount全局变量返回受影响的行数(通常。链接中记录了一些异常) - 使用它可以将测试部分与更新部分统一起来 - 拥有SQL Server报告如果更新影响了任何行,则返回。这使您可以编写更简单的SQL,并且可能具有更好的性能。

CREATE PROCEDURE VideoCheckout
(
     @VideoId int, 
     @UserId uniqueIdentifier,
     @Success bit OUTPUT
)
AS

    SET XACT_ABORT ON 

    SET @Success = 0 

    BEGIN TRANSACTION
    BEGIN TRY

        UPDATE dbo.Videos
        SET TotalCopies = TotalCopies - 1
        WHERE VideoId = @VideoId
        AND TotalCopies > 0;

        IF @@ROWCOUNT > 0 
        BEGIN

            INSERT INTO dbo.Checkouts (VideoId, UserId, CheckoutDate)
            VALUES (@VideoId, @UserId, CURRENT_TIMESTAMP)

            SET @Success = 1

        END

        COMMIT TRANSACTION
    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0
            ROLLBACK TRANSACTION
    END CATCH

GO