C# T-SQL If语句问题

C# T-SQL If语句问题,c#,sql-server,tsql,if-statement,ado.net,C#,Sql Server,Tsql,If Statement,Ado.net,我有两张表:dbo.Videos和dbo.checkout dbo.Videos表包含视频列表,dbo.checkout表跟踪已签出的视频。 我的TSQL命令的目标是在dbo.checkout表中插入一个新行,包括VideoId,UserId,CheckoutDate 成功后,我想更新dbo.Videos,并仅当值大于0时,根据所选VideoID减小TotalCopies列值。 如果小于0,我想抛出异常。 两个表中的VideoID都通过外键链接。 但是,我在下面的语句中包含的IF语句抛出了一个错

我有两张表:
dbo.Videos
dbo.checkout

dbo.Videos
表包含视频列表,
dbo.checkout
表跟踪已签出的视频。
我的TSQL命令的目标是在
dbo.checkout
表中插入一个新行,包括
VideoId
UserId
CheckoutDate

成功后,我想更新
dbo.Videos
,并仅当值大于0时,根据所选
VideoID
减小
TotalCopies
列值。
如果小于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

你把它倒过来了。
您不需要将记录添加到
签出
,然后测试视频是否在
视频
中,而需要先检查您是否有一份可以签出的副本。
这就像你去任何一家商店买东西一样——首先你把商品从货架上拿下来,然后你才付钱。
如果产品不在货架上,你就没有必要付钱

第一版 您至少需要三个步骤才能正确操作:
首先,您要检查是否有副本要签出。
如果没有,您什么也不做,只需返回一条消息,说明没有免费副本可供签出。
如果有副本,则需要更新
视频
表(TotalCopies-=1) 最后-您需要将记录插入到
签出

这里最重要的一点是,如果这些步骤中的任何一个都失败了,那么所有步骤都会失败——例如,如果由于某种原因您未能将行插入到
签出
,您必须恢复您在
视频
表上所做的更新,因为您无法完成该过程

这是您需要将整个流程包装到事务中的第一个原因。
需要事务的第二个原因是,如果有副本要签出,则要避免测试与更新
视频
表之间的竞争条件。你可以在丹·古兹曼的博客文章中了解更多

说到这里,让我们展示一些代码:

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的全局变量返回受影响的行数(通常。链接中记录了一些例外情况)-使用该变量可以将测试部分与更新部分统一起来-如果更新未影响任何行,则让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

下面的
抛出一个错误。
什么错误删除
IF
并使用
更新dbo。视频设置TotalCopies=TotalCopies-1,其中VideoID=32,TotalCopies>0
。在插入
签出之前是否也要检查
TotalCopies
值?@mjwills好的,我理解。是的,这可能行得通,而且可能比我们两个答案都快。作为一个类比,为什么这个方法值得考虑@Nisarg——让我们想象一下,我妻子让我去买一些鸡蛋。一个选择是让她让我去商店,看看是否有鸡蛋(
选择
),然后如果有鸡蛋,让店主把鸡蛋放在一边(锁
提示),然后回家。然后,我告诉她这里有鸡蛋,所以她让我去买一些(
UPDATE
)。这确实有效。但如果她只是让我去买一些有库存的鸡蛋(
更新
),并让她知道我是否成功(
@@ROWCOUNT
)。