在触发器崩溃 Oracle Apex 中使用 pragma autonomous_transaction 立即执行 'alter trigger ... disable'

Execute immediate 'alter trigger ... disable' with pragma autonomous_transaction in a trigger crashes Oracle Apex

我正在尝试使用预连接反规范化对这两个表进行反规范化,并且必须添加所有需要的触发器。

表格基本上是这样的:

CLIENTS 
Client_ID CHAR(13),
Client_Name VARCHAR2(20),
Client_Surname VARCHAR2 (30),
...
PRIMARY KEY (Client_ID)


ACCOUNTS
Account_ID VARCHAR(20),
...
Client_ID CHAR(13),
Client_Name VARCHAR2(20),
Client_Surname VARCHAR2(30),
PRIMARY KEY (ACCOUNT_ID),
FOREIGN KEY (Client_ID) REFERENCES CLIENTS(Client_ID)

我有两个触发器与我的问题相关;一个用于禁用 Client_Name 和 Client_Surname 的手动编辑,如下所示:

CREATE OR REPLACE TRIGGER ACCOUNTS_NAME_SURNAME_UPDATE_DISABLE 
BEFORE UPDATE OF CLIENT_NAME, CLIENT_SURNAME ON ACCOUNTS 
FOR EACH ROW 
BEGIN 
    RAISE_APPLICATION_ERROR( NUM => -20002, MSG => 'Updating client_name and client_surname is not permitted!');
END;

第二个触发器用于在添加新帐户时自动插入客户端名称和姓氏,即连接到客户端。它看起来像这样:

CREATE OR REPLACE TRIGGER ACCOUNTS_INSERT_UPDATE 
BEFORE INSERT OR UPDATE ON ACCOUNTS 
FOR EACH ROW 
DECLARE 
    PRAGMA AUTONOMOUS_TRANSACTION;
    new_name VARCHAR2(20);
    new_surname VARCHAR2(30); 
BEGIN 
    EXECUTE IMMEDIATE 'ALTER TRIGGER ACCOUNTS_NAME_SURNAME_UPDATE_DISABLE DISABLE';

    INSERT Client_Name INTO new_name FROM CLIENTS 
    WHERE Client_ID = :NEW.Client_ID; 

    :NEW.Client_Name := new_name;

    INSERT Client_Surname INTO new_surname FROM CLIENTS 
    WHERE Client_ID = :NEW.Client_ID; 

    :NEW.Client_Surname := new_surname;

    EXECUTE IMMEDIATE 'ALTER TRIGGER ACCOUNTS_NAME_SURNAME_UPDATE_DISABLE ENABLE';
END;

而且有效!只要我删除 Execute immediate statements 和 pragma autonomous transaction(并手动禁用第一个触发器)。它达到了预期的效果 - 它用客户的名字和姓氏填充了行。

但是当我添加 pragma 自治事务并执行立即语句时,Apex Oracle(SQL 命令)页面冻结然后崩溃,我必须重新加载页面。

需要使用触发器来实现此效果,尽管它看起来确实不适合这项工作。

我使用的是最新版本的 Oracle Apex,在撰写此问题时为 Oracle APEX 22.1.0-17。

如果有人能帮助解决这个问题,我将不胜感激。提前致谢!

两个 row-level 触发器都在 accounts table 上触发 BEFORE UPDATE。就好像 Oracle 无法决定它并试图禁用一个触发器,该触发器只是为了通知您您正在尝试做一些您不应该做的事情并且 - 有点 - 最终陷入无限循环。

那么,哪个触发器会先 运行?据我所知,Oracle 没有指定它,而是让 使用followsprecedes 来决定它。像这样:

CREATE OR REPLACE TRIGGER accounts_name_surname_update_disable
   BEFORE UPDATE OF client_name, client_surname
   ON accounts
   FOR EACH ROW
   FOLLOWS accounts_insert_update                   --> this
BEGIN
   raise_application_error (
      num  => -20002,
      msg  => 'Updating client_name and client_surname is not permitted!');
END;

可以简化此触发器(顺便说一句,您发布的代码似乎无效;没有这样的 INSERT 语句 - 应该是 SELECT):

CREATE OR REPLACE TRIGGER accounts_insert_update
   BEFORE INSERT OR UPDATE
   ON accounts
   FOR EACH ROW
   PRECEDES accounts_name_surname_update_disable    --> this
DECLARE
   PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
   EXECUTE IMMEDIATE 'ALTER TRIGGER ACCOUNTS_NAME_SURNAME_UPDATE_DISABLE DISABLE';

   SELECT client_name, client_surname
     INTO :new.client_name, :new.client_surname
     FROM clients
    WHERE client_id = :new.client_id;

   EXECUTE IMMEDIATE 'ALTER TRIGGER ACCOUNTS_NAME_SURNAME_UPDATE_DISABLE ENABLE';
END;

另一方面 - 即使可行 - 整个过程对我来说都是错误的。 accounts_name_surname_update_disable 触发器如何知道它应该 - 或不应该 - 触发并允许对这两列进行更改?对我来说,只有一个触发器就足够了——第二个(但经过修改)。

所以:放弃触发器 accounts_name_surname_update_disable,因为它没用。

重新创建第二个触发器:不需要disable/enable任何东西,因为另一个触发器不再存在。始终从 clients 获取客户端名称(因此,有人可能 键入 这些值并不重要 - 触发器将覆盖它们):

CREATE OR REPLACE TRIGGER accounts_insert_update
   BEFORE INSERT OR UPDATE
   ON accounts
   FOR EACH ROW
BEGIN
   SELECT client_name, client_surname
     INTO :new.client_name, :new.client_surname
     FROM clients
    WHERE client_id = :new.client_id;
END;

终于,就这些了吗?我不这么认为。整个数据模型是错误的。 ACCOUNTS table 应该包含 only CLIENT_ID 列,这是一个外键,查看 CLIENTS.CLIENT_ID:

create table clients 
  (client_id        number primary key,
   client_name      varchar2(20) not null,
   client_surname   varchar2(20) not null
  );
  
create table accounts
  (account_id       number primary key,
   client_id        number constraint fk_acc_cli references clients (client_id)
  );

因此,当 accounts 需要知道客户的姓名时,查询将执行 join 并获取该数据:

select a.account_id, c.client_name
from accounts a join clients c on c.client_id = a.client_id
where a.account_id = 1234;

这应该可以解决您的问题。