【问题标题】:How to setup trigger using temporary table如何使用临时表设置触发器
【发布时间】:2017-08-04 01:10:00
【问题描述】:

我正在尝试为数据库设置触发器(我正在使用 Microsoft SQL Server)。

我有两张桌子

create table atbv_Sales_Products 
(
     ProductID integer, 
     TotalQuantity integer
);

insert into atbv_Sales_Products values (1, 1);
insert into atbv_Sales_Products values (2, 2);
insert into atbv_Sales_Products values (3, 20);
insert into atbv_Sales_Products values (4, 10);
insert into atbv_Sales_Products values (5, 20);
insert into atbv_Sales_Products values (6, 10);
insert into atbv_Sales_Products values (7, 5);
insert into atbv_Sales_Products values (8, 50);
insert into atbv_Sales_Products values (9, 1);

create table atbv_Sales_OrdersLines 
(
     OrderID integer, 
     ProductID integer, 
     Amount integer
);

insert into atbv_Sales_OrdersLines values (6, 4, 1);
insert into atbv_Sales_OrdersLines values (6, 6, 1);
insert into atbv_Sales_OrdersLines values (6, 1, 1);
insert into atbv_Sales_OrdersLines values (47, 4, 1);
insert into atbv_Sales_OrdersLines values (6, 9, 1);
insert into atbv_Sales_OrdersLines values (5, 7, 1);
insert into atbv_Sales_OrdersLines values (6, 2, 2);

还有一个插入表(它实际上是自动生成的,但为了清楚起见我们把它放在这里

create table Inserted 
(
     OrderID integer, 
     ProductID integer, 
     Amount integer
);

insert into Inserted values (48, 4, 9);
insert into Inserted values (48, 1, 10);
insert into Inserted values (48, 8, 100);
insert into Inserted values (48, 2, 1);

为了更容易理解,这里是这些表格的图形外观:

产品表

OrdersLines 表

插入表格

现在触发器应该检查插入的金额值 + 之前的金额值是否超过 TotalQuantity(这是一个静态值。或者换句话说,当新订单进来时它不会改变),如果是则回滚更改

为了过滤我使用这部分代码

IF EXISTS (select p.ProductID 
           from atbv_Sales_Products p 
           join Inserted i
           on p.ProductID = i.ProductID
           join atbv_Sales_OrdersLines ol
           on p.ProductID = i.ProductID
           group by i.ProductID, i.Amount, p.TotalQuantity
           having (SUM(ol.Amount) + i.Amount) > p.TotalQuantity)

然后我一直在尝试使用代码的以下代码部分来回滚更改并警告错误

BEGIN
    DECLARE @ProductID NVARCHAR(60)
            SET @ProductID = (SELECT p.ProductID 
                                    FROM atbv_Sales_Products p
                                    JOIN inserted i 
                                    ON i.ProductID = p.ProductID)
            RAISERROR ('----There is not enough items number (%s) left----', 18, 1, @ProductName) ROLLBACK TRANSACTION 
            RETURN
END

如果只有一个插入行,这很好,但我不知道如果像当前示例中那样有多行,该怎么办。我想我已经在某个地方读过我可以创建一个临时表,但同样,不知道如何将那些被代码的第一部分过滤掉的值插入其中,然后使用这些值显示在错误消息中。

【问题讨论】:

  • 您是否只想要一个不同的产品 ID 列表,用逗号分隔?
  • @Xedni 我想在错误消息和回滚更改中显示超过 TotalQuantity 的产品 ID,因为它们无法应用(第二部分由 ROLLBACK TRANSACTION

标签: sql-server tsql


【解决方案1】:

如果需要,您可以使用临时表,但我认为这会引入不必要的步骤。我要做的只是将所有不良产品 ID 序列化为一个变量,然后检查该变量是否为空(这将替换您的 EXISTS 语句。

declare @BadProducts varchar(max)

select @BadProducts = 
    stuff((select ',' + p.ProductId
           from atbv_Sales_Products p 
           join Inserted i
           on p.ProductID = i.ProductID
           join atbv_Sales_OrdersLines ol
           on p.ProductID = i.ProductID
           group by i.ProductID, i.Amount, p.TotalQuantity
           having (SUM(ol.Amount) + i.Amount) > p.TotalQuantity
           for xml path('')), 1, 1, '')

if @BadProducts is not null
begin
    raiserror('These products are bad: %s', 16, 1, @BadProducts)
    return
end

替代方法是将行插入临时表或表变量(如您所建议的那样),然后对临时表进行存在检查或@@rowcount 检查,如果匹配,则将产品 ID 序列化为与第一个示例大致相同(除了用您的临时表替换整个大子查询)。像这样的:

declare @badProductsTable table
(
    ProductId int
)

insert into @badProductsTable (ProductId)
select p.ProductId
from atbv_Sales_Products p 
join Inserted i
on p.ProductID = i.ProductID
join atbv_Sales_OrdersLines ol
on p.ProductID = i.ProductID
group by i.ProductID, i.Amount, p.TotalQuantity
having (SUM(ol.Amount) + i.Amount) > p.TotalQuantity

if @@rowcount > 0
    -- or you could do
    -- if exists (select 1 from @badProductsTable)
begin
    select @BadProducts = 
        stuff((select ',' + ProductId
               from @badProductsTable
               for xml path('')), 1, 1, '')

    raiserror('These products are bad: %s', 16, 1, @BadProducts)
    return

end

【讨论】:

  • 从来没有这样想过(也不知道东西的功能)。也试过第一种方法,效果不错。谢谢,我学到了一些东西
  • 如果你不介意,为什么选择语句中究竟有','?我似乎没有找到任何关于此的陈述
  • 对。因此,当您使用FOR XML 时,它会连接每个ProductId。麻烦的是,如果你不输入分隔符,你最终会得到产品 ID 1、2 和 3 成为字符串 '123',而你真正想要的是 '1,2,3'。因此,您将逗号添加到每个产品 ID。现在,其他编程语言已经足够好,可以知道您不想在此处使用前导逗号,但 SQL 不需要。因此可以使用STUFF()函数将第一个字符(即第一个逗号)替换为空字符串。
【解决方案2】:

您的 If Exists 语句确实是您所需要的。这个脚本对我有用...

-- DROP table atbv_Sales_Products 
create table atbv_Sales_Products 
(
     ProductID integer, 
     TotalQuantity integer
);

insert into atbv_Sales_Products values (1, 10);

-- DROP table atbv_Sales_OrdersLines 
create table atbv_Sales_OrdersLines 
(
     OrderID integer, 
     ProductID integer, 
     Amount integer
);

insert into atbv_Sales_OrdersLines values (100, 1, 4);


CREATE TRIGGER MyTrigger ON atbv_Sales_OrdersLines AFTER INSERT, UPDATE AS
    IF EXISTS (
        SELECT      *
        FROM        (SELECT ProductID, SUM(Amount) as [Amount] FROM Inserted GROUP BY ProductID) I
        LEFT JOIN   (SELECT ProductID, SUM(Amount) as [Amount] FROM atbv_Sales_OrdersLines X WHERE NOT EXISTS (SELECT * FROM Inserted Y WHERE Y.OrderID = X.OrderID) GROUP BY ProductID) OL ON OL.ProductID = I.ProductID
        JOIN        atbv_Sales_Products P ON P.ProductID = I.ProductID
        WHERE       I.Amount + ISNULL(OL.Amount, 0) > P.TotalQuantity
    )
        THROW 51000, 'My Error Message.', 1;
GO

-------------

SELECT * FROM atbv_Sales_Products
SELECT * FROM atbv_Sales_OrdersLines

insert into atbv_Sales_OrdersLines values (101, 1, 4);
insert into atbv_Sales_OrdersLines values (102, 1, 2);
insert into atbv_Sales_OrdersLines values (103, 1, 1);

另外,考虑找到一种方法来使用 CHECK 约束而不是触发器。触发器通常会导致维护问题和性能问题。

最后,这个例子只是学术性的。由于订单在几天内收到并且可用数量波动,因此此解决方案将无法很好地工作。它可以说明一个观点,但我希望这只是出于学术目的。

【讨论】:

  • 如果需要列出所有透支的产品 ID,请稍微更改触发器。将“*”更改为 ProductID 并使用 STUFF 和 FOR XML PATH 连接 ID。 Here is a link that should be helpful.
  • 虽然它是出于学术目的,正如 DeadZone 所说,列出透支的产品是一项要求,因此我接受了 Xedni 的建议(也是 DeadZone 的建议)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-25
  • 1970-01-01
  • 2011-07-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多