简体   繁体   English

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

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

so I have this problem.所以我有这个问题。

I want to prevent adding new carts if the client already at least one unpaid shopping cart using trigger before insert on each row (in case more than one new cart is being inserted).如果客户在插入每一行之前已经使用触发器至少有一个未付款的购物车(以防插入多个新购物车),我想防止添加新的购物车。

Tables' model screenshot表的 model 截图

I managed to solve this problem quite easily in T-SQL counting records containing NULL in paidTime column in the modified table.我设法在修改表的paidTime 列中包含 NULL 的 T-SQL 计数记录中很容易地解决了这个问题。

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;

In PL/SQL though as far as I know you cannot query the changing table, so I can't check how many unpaid carts are there.在 PL/SQL 中,据我所知,您无法查询更改表,因此我无法检查那里有多少未付费的购物车。

Is there anything I can do to get this done using trigger in PL/SQL?我可以做些什么来使用 PL/SQL 中的触发器来完成这项工作吗?

Thanks for any help!谢谢你的帮助!

As OldProgrammer suggested, what you are thinking of doing can be done using a compound trigger in Oracle.正如 OldProgrammer 建议的那样,您可以使用 Oracle 中的复合触发器来完成您想做的事情。 In that approach, you would have a BEFORE INSERT...FOR EACH ROW trigger to record the client IDs in memory (a PL/SQL array or something) and then a BEFORE INSERT statement level trigger to check for multiple unpaid carts for any of the recorded client IDs (throwing an exception if any are found).在这种方法中,您将有一个 BEFORE INSERT...FOR EACH ROW 触发器来记录 memory(PL/SQL 数组或其他东西)中的客户端 ID,然后是一个 BEFORE INSERT 语句级别触发器来检查多个未付费购物车中的任何一个记录的客户端 ID(如果找到,则抛出异常)。

However, using triggers to enforce referential integrity is hard to get right.但是,使用触发器来强制执行参照完整性是很难做到的。 This is mostly because triggers fire before transactions are committed.这主要是因为触发器在事务提交之前触发。 So, for example:因此,例如:

  • Session A, time 1: insert unpaid cart for client 100 (trigger check passes) Session A,时间 1:为客户 100 插入未付款的购物车(触发检查通过)
  • Session B, time 2: insert unpaid cart for client 100 (trigger check ALSO passes, because session A has not committed yet!) Session B,时间 2:为客户 100 插入未付款的购物车(触发检查也通过,因为 session A 尚未提交!)
  • Session A: time 3: commit Session A:时间 3:提交
  • Session B: time 4: commit Session B:时间 4:提交

... and your trigger has failed to accomplish its purpose. ...而您的触发器未能实现其目的。

A better way to do this is to create a unique constraint, relying on the fact that unique constraints do not reject rows having NULL values in all the columns of the constraint.一个更好的方法是创建一个唯一约束,依赖于唯一约束不拒绝在约束的所有列中具有NULL值的行这一事实。

Something like the example below should avoid the race condition described above and work 100% of the time.像下面的例子应该避免上面描述的竞争条件并且 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.

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM