基于变化表中的值在插入之前触发 PL/SQL 触发器

Posted

技术标签:

【中文标题】基于变化表中的值在插入之前触发 PL/SQL 触发器【英文标题】:PL/SQL Trigger BEFORE INSERT based on values in the changing table 【发布时间】:2021-12-30 14:06:23 【问题描述】:

所以我有这个问题。

如果客户在插入每一行之前已经使用触发器至少有一个未付款的购物车(以防插入多个新购物车),我想阻止添加新购物车。 Tables' model screenshot

我设法在 T-SQL 计算修改表的paidTime 列中包含 NULL 的记录中很容易地解决了这个问题。

CREATE TRIGGER one_unpaid_cart_per_client  ON Cart
FOR INSERT
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE newCart_cursor CURSOR FOR SELECT IdClient, IdCart, isPaid FROM inserted;

    DECLARE @IdClient int;
    DECLARE @IdCart int;
    DECLARE @isPaid datetime;

    OPEN newCart_cursor;
    FETCH NEXT FROM newCart_cursor INTO @IdClient, @IdCart, @isPaid;

    WHILE @@FETCH_STATUS = 0
    BEGIN
        IF @isPaid is null
        BEGIN
            if 1 < (SELECT COUNT(1) FROM Cart WHERE IdClient = @IdClient AND isPaid is Null)
            BEGIN
                PRINT 'Unpaid cart already exists for client id: ' + Cast(@IdClient as Varchar);
                DELETE FROM Cart WHERE IdCart = @IdCart;
                Raiserror('New cart has not been added', 1, 1);
            END;
        END;
        FETCH NEXT FROM newCart_cursor INTO @IdClient, @IdCart, @isPaid;
    END;

    CLOSE newCart_cursor;
    DEALLOCATE newCart_cursor;
    SET NOCOUNT OFF;
END;

虽然据我所知,在 PL/SQL 中您无法查询更改表,所以我无法检查那里有多少未付的购物车。

我可以做些什么来使用 PL/SQL 中的触发器来完成这项工作吗?

感谢您的帮助!

【问题讨论】:

阅读复合触发器 【参考方案1】:

正如 OldProgrammer 所建议的,您可以使用 Oracle 中的复合触发器来完成您想做的事情。在这种方法中,您将有一个 BEFORE INSERT...FOR EACH ROW 触发器来记录内存中的客户端 ID(PL/SQL 数组或其他东西),然后有一个 BEFORE INSERT 语句级触发器来检查多个未付费购物车中的任何一个记录的客户端 ID(如果发现则抛出异常)。

但是,使用触发器来强制执行完整性很难做到正确。这主要是因为触发器在事务提交之前触发。所以,例如:

会话 A,时间 1:为客户 100 插入未付费购物车(触发检查 通过) 会话 B,时间 2:为客户 100 插入未付费购物车(触发 检查也通过了,因为会话 A 尚未提交!) 会话 A:时间 3:提交 会话 B:时间 4:提交

...而您的触发器未能实现其目的。

更好的方法是创建一个唯一约束,依赖于唯一约束不会拒绝约束的所有列中具有NULL 值的行这一事实。

类似下面的例子应该避免上面描述的竞争条件并且工作 100% 的时间。

--drop table cart;

CREATE TABLE cart
  ( cart_id          NUMBER NOT NULL,
    client_id        NUMBER NOT NULL,
    payment_time     TIMESTAMP,
    unpaid_client_id NUMBER 
      INVISIBLE GENERATED ALWAYS AS (DECODE(payment_time,NULL,client_id, NULL)) VIRTUAL,
    is_unpaid        VARCHAR2(1) 
      INVISIBLE GENERATED ALWAYS AS (DECODE(payment_time,NULL,'Y',NULL)) VIRTUAL,
    CONSTRAINT cart_pk PRIMARY KEY ( cart_id ),
    CONSTRAINT max_one_unpaid UNIQUE ( unpaid_client_id, is_unpaid)
  );

delete from cart;  
  
-- This should be allowed: the first unpaid cart for a client
insert into cart ( cart_id, client_id, payment_time ) values ( 100, 1, null );
--1 row(s) inserted.

-- This should be rejected: a second unpaid cart for a client
insert into cart ( cart_id, client_id, payment_time ) values ( 101, 1, null );
--ORA-00001: unique constraint (SQL_PRUOVRWCMSHZECACDIWPGIBED.MAX_ONE_UNPAID) violated ORA-06512: at "SYS.DBMS_SQL", line 1721

-- This should be allowed, a paid cart for a client having an unpaid one    
insert into cart ( cart_id, client_id, payment_time ) values ( 102, 1, SYSTIMESTAMP );
--1 row(s) inserted.

-- This should be allowed, a second paid cart for a client having an unpaid one    
insert into cart ( cart_id, client_id, payment_time ) values ( 103, 1, SYSTIMESTAMP );
--1 row(s) inserted.

【讨论】:

这也可以在没有计算列的情况下实现 是的,我知道。我更喜欢对约束使用约束,对性能/访问路径使用索引。约束告诉其他开发人员和 DBA 其用途。例如,我不希望一些过于急切的 DBA 注意到索引从未使用过并删除它。不太可能,但这就是那种推理——清晰。这只是我的偏好。正如您所说,索引也可以很好地工作。

以上是关于基于变化表中的值在插入之前触发 PL/SQL 触发器的主要内容,如果未能解决你的问题,请参考以下文章

PL/SQL 触发器插入下一个值

在使用 Entity Framework 数据库优先在表上插入行之前,如何从 PL/SQL 执行触发器?

PL/SQL 触发器在插入语句之前检查值

使用 PL/SQL 在触发器中中止插入/更新操作

PL/SQL 触发器练习

如何使用触发器在同一张表中插入新行(Oracle PL/SQL 10G)?