PL/SQL 根据变化中的值触发 BEFORE INSERT table

PL/SQL Trigger BEFORE INSERT based on values in the changing table

所以我有这个问题。

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

Tables' model screenshot

我在 T-SQL 中很容易地解决了这个问题,计算修改后的 table.

中 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 虽然据我所知你无法查询更改 table,所以我无法检查有多少未付款的购物车。

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

感谢您的帮助!

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

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

  • 会话 A,时间 1:为客户 100 插入未付款的购物车(触发检查 通过)
  • 会话 B,时间 2:为客户 100 插入未付款的购物车(触发器 检查 ALSO 通过,因为会话 A 尚未提交!)
  • 届会 A: 时间 3: commit
  • 会话 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.