更新/插入/删除表中的行后触发的触发器有问题

Posted

技术标签:

【中文标题】更新/插入/删除表中的行后触发的触发器有问题【英文标题】:Having issue with a trigger that fires after a row in a table is updated/inserted/deleted 【发布时间】:2019-05-06 13:25:17 【问题描述】:

我有一个小系统,我必须为一项任务创建它。它旨在使查看数据库中的相关数据更容易“管理”。

我有三张桌子:

CLIENT 仅包含客户 ID、客户名称、电话号码和电子邮件。

PROJECT 同样简单。它有一个项目 ID、引用 CLIENT 的客户端 ID 和一个项目名称。

PROJECT_PAYMENT 包含一个项目付款 ID、引用 PROJECT 的项目 ID,然后是一组包含付款到期日、已付款金额、未付金额等的行。

李>

然后我有四个视图:

PAYMENTS_COMPLETED 显示,您猜对了,已完成付款。

PAYMENTS_OUTSTANDING 与上述相反。

PAYMENTS_DISPUTED 显示客户或公司对任何付款提出异议。

PAYMENTS_PAST_DUE 显示尚未完成的付款,以及付款到期日已过的地方。

然后我有一个更新所有这四个视图的过程:

create or replace PROCEDURE UPDATE_VIEWS AUTHID CURRENT_USER
IS

PAYMENTS_COMPLETED_STMNT VARCHAR2(5000);
PAYMENTS_DISPUTED_STMNT VARCHAR2(5000);
PAYMENTS_OUTSTANDING_STMNT VARCHAR2(5000);
PAYMENTS_PAST_DUE_STMNT VARCHAR2(5000);

BEGIN

    PAYMENTS_COMPLETED_STMNT := 'CREATE OR REPLACE VIEW PAYMENTS_COMPLETED AS SELECT PP.PROJECT_PAYMENT_ID, P.PROJECT_NAME, C.CLIENT_ID, C.CLIENT_NAME, PP.PAYMENT_DUE, PP.PAYMENT_TOTAL FROM PROJECT_PAYMENT PP JOIN PROJECT P ON PP.PROJECT_ID = P.PROJECT_ID JOIN CLIENT C ON C.CLIENT_ID = P.CLIENT_ID WHERE PP.PAYMENT_PAID >= PP.PAYMENT_TOTAL';
    PAYMENTS_DISPUTED_STMNT := 'CREATE OR REPLACE VIEW PAYMENTS_DISPUTED AS SELECT PP.PROJECT_PAYMENT_ID, P.PROJECT_NAME, C.CLIENT_ID, C.CLIENT_NAME, PP.PAYMENT_DUE, PP.PAYMENT_TOTAL, PP.PAYMENT_PAID, PP.PAYMENT_TOTAL-PP.PAYMENT_PAID AS "PAYMENT_REMAINING", PP.PAYMENT_DISPUTED_CLIENT, PP.PAYMENT_DISPUTED_COMPANY FROM PROJECT_PAYMENT PP JOIN PROJECT P ON PP.PROJECT_ID = P.PROJECT_ID JOIN CLIENT C ON C.CLIENT_ID = P.CLIENT_ID WHERE UPPER(PP.PAYMENT_DISPUTED_CLIENT) = ''Y'' OR UPPER(PP.PAYMENT_DISPUTED_COMPANY) = ''Y''';
    PAYMENTS_OUTSTANDING_STMNT := 'CREATE OR REPLACE VIEW PAYMENTS_OUTSTANDING AS SELECT PP.PROJECT_PAYMENT_ID, P.PROJECT_NAME, C.CLIENT_ID, C.CLIENT_NAME, PP.PAYMENT_DUE, PP.PAYMENT_TOTAL, PP.PAYMENT_PAID, PP.PAYMENT_TOTAL - PP.PAYMENT_PAID AS "PAYMENT_REMAINING" FROM PROJECT_PAYMENT PP JOIN PROJECT P ON PP.PROJECT_ID = P.PROJECT_ID JOIN CLIENT C ON C.CLIENT_ID = P.CLIENT_ID WHERE PP.PAYMENT_PAID < PP.PAYMENT_TOTAL';
    PAYMENTS_PAST_DUE_STMNT := 'CREATE OR REPLACE VIEW PAYMENTS_PAST_DUE AS SELECT PP.PROJECT_PAYMENT_ID, P.PROJECT_NAME, C.CLIENT_ID, C.CLIENT_NAME, PP.PAYMENT_DUE, PP.PAYMENT_TOTAL, PP.PAYMENT_PAID, PP.PAYMENT_TOTAL-PP.PAYMENT_PAID AS "PAYMENT_REMAINING" FROM PROJECT_PAYMENT PP JOIN PROJECT P ON PP.PROJECT_ID = P.PROJECT_ID JOIN CLIENT C ON C.CLIENT_ID = P.CLIENT_ID WHERE PP.PAYMENT_DUE < TRUNC(SYSDATE) AND PP.PAYMENT_PAID < PP.PAYMENT_TOTAL';

    EXECUTE IMMEDIATE PAYMENTS_COMPLETED_STMNT;
    DBMS_OUTPUT.PUT_LINE('UPDATED PAYMENTS_COMPLETED VIEW.');
    EXECUTE IMMEDIATE PAYMENTS_DISPUTED_STMNT;
    DBMS_OUTPUT.PUT_LINE('UPDATED PAYMENTS_DISPUTED VIEW.');
    EXECUTE IMMEDIATE PAYMENTS_OUTSTANDING_STMNT;
    DBMS_OUTPUT.PUT_LINE('UPDATED PAYMENTS_OUTSTANDING VIEW.');
    EXECUTE IMMEDIATE PAYMENTS_PAST_DUE_STMNT;
    DBMS_OUTPUT.PUT_LINE('UPDATED PAYMENTS_PAST_DUE VIEW.');

END;

现在谈谈我的问题。我创建了以下触发器:

create or replace TRIGGER UPDATE_VIEWS_ON_PP_INSERT_TG
AFTER INSERT OR UPDATE OR DELETE ON PROJECT_PAYMENT
BEGIN
    UPDATE_VIEWS();
    DBMS_OUTPUT.PUT_LINE('ALL VIEWS HAVE BEEN UPDATED.');
END;

我的意图是每当有人从PROJECT_PAYMENT 插入新行、更新行或删除行时都会触发它。触发器确实触发了,但是它给了我以下错误,并阻止我刚刚尝试插入的行被提交:

One error saving changes to table "O015596H"."PROJECT_PAYMENT":
Row 11: ORA-04092: cannot COMMIT in a trigger
ORA-06512: at "O015596H.UPDATE_VIEWS", line 16
ORA-06512: at "O015596H.UPDATE_VIEWS_ON_PP_INSERT_TG", line 2
ORA-04088: error during execution of trigger 'O015596H.UPDATE_VIEWS_ON_PP_INSERT_TG'
ORA-06512: at line 1

Local changes cleared

我不知道这个错误是什么意思或如何解决它,所以在这里帮助某人可以告诉我问题是什么。我知道它说它不能在触发器中提交,但我不知道我应该如何摆脱错误。

编辑 1:

我用谷歌搜索了一下,发现添加了:

FOR EACH ROW
DECLARE
    PRAGMA AUTONOMOUS_TRANSATION;

...在“BEGIN”行上方可以工作,但现在我收到“权限不足”错误:

One error saving changes to table "O015596H"."PROJECT_PAYMENT":
Row 11: ORA-01031: insufficient privileges
ORA-06512: at "O015596H.UPDATE_VIEWS", line 16
ORA-06512: at "O015596H.UPDATE_VIEWS_ON_PP_INSERT_TG", line 4
ORA-04088: error during execution of trigger 'O015596H.UPDATE_VIEWS_ON_PP_INSERT_TG'
ORA-06512: at line 1

【问题讨论】:

它说过程update_views 提交数据(第16行)。您不能在触发器中提交。 我用谷歌搜索了一下,显然有办法解决它。请查看我的编辑。 @JohnYuki 视图是一个存储查询。当基础数据发生变化时,您无需更新它。 【参考方案1】:

视图是永久数据库结构。只需像对表或其他任何东西一样编写 DDL 脚本。运行一次,然后将视图权限授予需要访问权限的人员。乔布斯是个好人。

除此之外,您不希望每次有人记录项目付款时都重新创建这些对象。获取对象锁会让人头疼,而且用户会发现他们查询这些视图的尝试由于会话状态失效而不断失败。

解释你得到的错误:

ORA-04092:无法在触发器中提交

Oracle 中的 DDL - 例如 CREATE VIEW 语句 - 发出隐式提交。 Oracle 不允许我们在触发器中包含 COMMIT(或 ROLLBACK),因为触发器作为事务的一部分触发,但在触发器触发时事务不一定完成。

ORA-01031: 权限不足

您似乎拥有通过角色授予的 CREATE VIEW 权限。我们不能使用通过程序单元(存储过程、视图或触发器)中的角色间接授予的角色。


作业指定我必须展示有关如何使用过程、函数和触发器的知识。

使用触发器创建视图并不能证明您知道“如何使用过程、函数和触发器”。事实上恰恰相反。

触发器的更简单和更好的用法是使用amount paid 来维护PROJECT_PAYMENT 上的amount outstanding

create or replace TRIGGER UPDATE_VIEWS_ON_PP_INSERT_TG
before update ON PROJECT_PAYMENT for each row
BEGIN
    :new.amount_outstanding := :old.amount_outstanding - :new.amount_paid;
END;

这会将amount outstanding 减少最新付款的金额。 (我假设 PROJECT_PAYMENT 中的每条记录都代表一次付款,amount_paid 不是滚动总数。)

【讨论】:

对不起,我应该指出我对 SQL 很陌生,所以我不懂很多术语。什么是 DDL 脚本,我如何为自己授予权限以使触发器按我的意愿工作? @JohnYuki 。 . .如果您是 SQL 新手,触发器可能不是最好的起点。您不需要触发器或存储过程来创建这些视图。 这是一个作业,作业指定我必须展示如何使用过程、函数和触发器的知识。当我说“新”时,我并不是指全新的,而是经验不足,无法真正申请入门级数据库开发人员职位。 “什么是 DDL 脚本” - 它就是你用来创建这些表的任何东西。 DDL = 数据定义语言,通常是任何以createalterdroptruncate 开头的命令(还有更多)。这些是对架构的永久性更改。

以上是关于更新/插入/删除表中的行后触发的触发器有问题的主要内容,如果未能解决你的问题,请参考以下文章

是否有可能在同一个触发器运行中发生混合更新和插入?

更新同一表中的值的 PL/SQL 插入和删除触发器

使用触发器删除 Oracle 表中的行

Mysql - 创建触发器以根据插入另一个表中的行插入新行

如何在 SQL Server 触发器中复制插入、更新、删除的行

触发仅将变量的 1 个字符插入表中