如何在 Oracle 数据库 10g 中编写 "IF UPDATING" 触发器?

How Can I code a "IF UPDATING" trigger in Oracle Database 10g?

我正在编写触发器以确保只能将一种货币设置为官方货币。我的意图是编写一个 "BEFORE INSERT OR UPDATE" 触发器。 INSERT 部分工作正常,但问题是编码 UPDATING 部分,因为当我尝试更新 table 时,我收到 ORA-04091 "mutanting table"。你有什么想法吗?

Table(只能设置一条记录为'Y'):

    mon_id  mon_description  mon_official
----------------------------------------------
    E            EUR              N
    D            DOL              N
    P            PES              Y

触发器:

CREATE OR REPLACE TRIGGER mon_oficial_ins_trg
BEFORE
INSERT OR UPDATE
ON monedas
FOR EACH ROW
DECLARE
    v_count  NUMBER(8);
BEGIN
    IF INSERTING THEN

        SELECT COUNT(mon_oficial)
        INTO   v_count
        FROM   monedas
        WHERE  mon_oficial = 'Y';

        IF v_count = 1 THEN
            RAISE_APPLICATION_ERROR(
                -20010, 'Only one record can be set as 'Y'');
        END IF;

    END IF;

    IF UPDATING THEN

        SELECT COUNT(:OLD.mon_oficial)
        INTO   v_count
        FROM   monedas
        WHERE  :OLD.mon_oficial = 'Y';

        IF v_count = 1 AND :NEW.mon_oficial = 'Y' THEN
                RAISE_APPLICATION_ERROR(
                    -20010, 'Only one record can be set as 'Y'');
        END IF;

    END IF;


END mon_oficial_ins_trg;
/
SHOW ERRORS;

你的代码有2个错误

第一

SELECT COUNT(:OLD.mon_oficial)
        INTO   v_count
        FROM   monedas
        WHERE  :OLD.mon_oficial = 'Y'; 

部分,更多关于变异错误的信息可以阅读这篇文章

enter link description here

第二个错误,你的逻辑不正确

IF v_count = 1 AND :NEW.mon_oficial = 'Y' THEN 部分因为它可以是我们当前的行

试一试

    CREATE OR REPLACE TRIGGER mon_oficial_ins_trg
BEFORE
INSERT OR UPDATE
ON monedas
FOR EACH ROW
DECLARE
    v_count  NUMBER(8);
BEGIN
    IF INSERTING THEN

        SELECT COUNT(mon_oficial)
        INTO   v_count
        FROM   monedas
        WHERE  mon_oficial = 'Y';

        IF v_count = 1 THEN
            RAISE_APPLICATION_ERROR(
                -20010, 'Only one record can be set as 'Y'');
        END IF;

    END IF;

    IF UPDATING THEN
     IF :NEW.mon_oficial = 'Y' then 
    for m in (SELECT *
        FROM   monedas
        WHERE  mon_oficial = 'Y'
        and    rownum=1) loop

            IF :NEW.mon_id <> m.mon_id  THEN
                    RAISE_APPLICATION_ERROR(
                        -20010, 'Only one record can be set as 'Y'');
            END IF;
        END IF;
        end loop;
    END IF;


END mon_oficial_ins_trg;
/
SHOW ERRORS;

使用 AFTER INSERT OR UPDATE 语句触发器可以非常简单地完成此操作。相同的逻辑适用于这两种操作。

SELECT COUNT(*) INTO v_count FROM MONEDAS 其中 MON_OFICIAL = 'Y';

如果 v_count > 1 那么 RAISE_APPLICATION_ERROR...

这种方法的另一个优点:它允许语句

更新 MONEDAS SET MON_OFICIAL = CASE MON_ID WHEN 'A' THEN 'Y' ELSE 'N' END;

如果 MON_ID = 'A' 的行在先前的官方货币更新为 N 之前更新为 Y,则 row-level 触发器可能会引发错误时可以正常工作.

我认为这个问题最好用约束而不是触发器来解决。我不是这个答案的作者,但我认为它在这里是相关的。在其余的答案中,有一篇博客 post 的 link 建议避免触发,但 link 似乎不起作用。

答案在这里:

这是@tony-andrews 在回答中提供的示例:

create unique index only_one_yes on mytable
(case when col='YES' then 'YES' end);