我有两张桌子:dbo.Videos
和dbo.Checkouts
dbo.Videos
表格包含视频列表,而dbo.Checkouts
表格会跟踪已检出的视频。
我的TSQL命令的目标是在dbo.Checkouts
表中插入一个新行,包括VideoId
,UserId
,CheckoutDate
。
一旦成功,我就要更新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
答案 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