【问题标题】:T-SQL If Statement issueT-SQL If 语句问题
【发布时间】:2018-08-05 11:00:24
【问题描述】:

我有 2 张桌子:dbo.Videosdbo.Checkouts
dbo.Videos 表包含视频列表,而dbo.Checkouts 表跟踪已签出的视频。
我的 TSQL 命令的目标是在 dbo.Checkouts 表中插入一个新行,包括 VideoIdUserIdCheckoutDate
成功后,我想更新dbo.Videos,并根据VideoID 减少TotalCopies 列的值,仅当值大于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

【问题讨论】:

  • below throws an error. 什么错误
  • 删除IF 并使用UPDATE dbo.Videos SET TotalCopies = TotalCopies - 1 WHERE VideoID = 32 AND TotalCopies > 0
  • 你不应该在插入Checkouts之前检查TotalCopies的值吗?

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


【解决方案1】:

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

第一个版本

您至少需要三个步骤才能做到正确:
首先,您检查是否有要签出的副本。
如果没有,您无需执行任何操作,只需返回一条消息,指出没有可结帐的免费副本。
如果有副本,则需要更新Video表(TotalCopies -= 1) 最后 - 您需要将记录插入到checkouts

这里最重要的是,如果这些步骤中的任何一个失败,它们都会失败 - 例如,如果由于某种原因您未能将行插入到 checkouts,则必须恢复在 @ 上所做的更新987654328@ 表,因为您无法完成该过程。

这是您需要将整个过程包装在事务中的第一个原因。
您需要事务的第二个原因是,如果有要签出的副本,则避免测试与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

【讨论】:

  • @mjwills 好的,我明白了。是的,这可能行得通,而且可能比我们的两个答案都快。
  • 作为类比为什么这种方法值得考虑@Nisarg - 让我们想象一下我的妻子让我去买一些鸡蛋。一种选择是让她让我去商店,看看是否有鸡蛋(SELECT),如果有,请店主把它们放在一边(LOCK 提示)然后回家.然后,我告诉这里有鸡蛋,所以她让我去拿一些(UPDATE)。这确实有效。但是,如果她只是让我去买一些有库存的鸡蛋 (UPDATE) 并让她知道我是否成功了 (@@ROWCOUNT),那就更简单了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多