Oracle - 在更新时触发以创建历史记录行

标签 oracle triggers audit

首先,我们目前拥有所需的行为,但在需要对数据库进行任何更改时维护起来并非易事。我正在寻找任何更简单、更高效或更易于维护的东西(任何能做到这 3 项中的任何一项的东西都是最受欢迎的)。当我们执行更新时,会创建一个历史记录行,它是 的副本。当前 行,然后更新当前行的值。结果是我们有该行在更新之前的历史记录。

推理:我们必须遵守一些联邦规则,并走这条路线以获得所有内容的完整审计历史,以及我们可以随时查看数据库并查看情况( future 要求) . 出于类似的原因,我无法更改历史记录的方式 ...任何解决方案都必须产生与当前触发器创建的数据相同的数据。

以下是 Contact 的当前触发器的样子 table :
(为简洁起见,去掉了无用的字段,字段的数量无关紧要)

更新前(每行):

DECLARE
     indexnb number;
BEGIN
  :new.date_modified := '31-DEC-9999';
  indexnb := STATE_PKG.newCONTACTRows.count + 1;
  :new.date_start := sysdate;
  :new.version := :old.version + 1;
  state_pkg.newCONTACTRows(indexnb).ID := :old.ID;
  state_pkg.newCONTACTRows(indexnb).PREFIX := :old.PREFIX;
  state_pkg.newCONTACTRows(indexnb).FIRST_NAME := :old.FIRST_NAME;
  state_pkg.newCONTACTRows(indexnb).MIDDLE_NAME := :old.MIDDLE_NAME;
  state_pkg.newCONTACTRows(indexnb).LAST_NAME := :old.LAST_NAME;
  --Audit columns after this
  state_pkg.newCONTACTRows(indexnb).OWNER := :old.OWNER;
  state_pkg.newCONTACTRows(indexnb).LAST_USER := :old.LAST_USER;
  state_pkg.newCONTACTRows(indexnb).DATE_CREATED := :old.DATE_CREATED;
  state_pkg.newCONTACTRows(indexnb).DATE_MODIFIED := sysdate;
  state_pkg.newCONTACTRows(indexnb).VERSION := :old.VERSION;
  state_pkg.newCONTACTRows(indexnb).ENTITY_ID := :old.id;
  state_pkg.newCONTACTRows(indexnb).RECORD_STATUS := :old.RECORD_STATUS;
  state_pkg.newCONTACTRows(indexnb).DATE_START := :old.DATE_START;
END;

更新前(所有行一次):
BEGIN
  state_pkg.newCONTACTRows := state_pkg.eCONTACTRows;
END;

更新后(所有行一次):
DECLARE
BEGIN
  for i in 1 .. STATE_PKG.newCONTACTRows.COUNT loop
    INSERT INTO "CONTACT" (
      ID, 
      PREFIX, 
      FIRST_NAME, 
      MIDDLE_NAME, 
      LAST_NAME, 
      OWNER, 
      LAST_USER, 
      DATE_CREATED, 
      DATE_MODIFIED, 
      VERSION, 
      ENTITY_ID, 
      RECORD_STATUS, 
      DATE_START)
    VALUES (
      CONTACT_SEQ.NEXTVAL, 
      state_pkg.newCONTACTRows(i).PREFIX,
      state_pkg.newCONTACTRows(i).FIRST_NAME,
      state_pkg.newCONTACTRows(i).MIDDLE_NAME,
      state_pkg.newCONTACTRows(i).LAST_NAME,
      state_pkg.newCONTACTRows(i).OWNER,
      state_pkg.newCONTACTRows(i).LAST_USER,
      state_pkg.newCONTACTRows(i).DATE_CREATED,
      state_pkg.newCONTACTRows(i).DATE_MODIFIED,
      state_pkg.newCONTACTRows(i).VERSION,
      state_pkg.newCONTACTRows(i).ENTITY_ID,
      state_pkg.newCONTACTRows(i).RECORD_STATUS,
      state_pkg.newCONTACTRows(i).DATE_START
    );
  end loop;
END;

定义为(修剪后的完整版只是每个表的副本)的包:
PACKAGE STATE_PKG IS
  TYPE CONTACTArray IS TABLE OF CONTACT%ROWTYPE INDEX BY BINARY_INTEGER; 
  newCONTACTRows CONTACTArray; 
  eCONTACTRows CONTACTArray;
END;

目前的结果

这是一个由此产生的历史样本:
ID    First Last   Ver  Entity_ID  Date_Start              Date_Modified  
1196  John  Smith  5    0          12/11/2009 10:20:11 PM  12/31/9999 12:00:00 AM
1201  John  Smith  0    1196       12/11/2009 09:35:20 PM  12/11/2009 10:16:49 PM
1203  John  Smith  1    1196       12/11/2009 10:16:49 PM  12/11/2009 10:17:07 PM
1205  John  Smith  2    1196       12/11/2009 10:17:07 PM  12/11/2009 10:17:19 PM
1207  John  Smith  3    1196       12/11/2009 10:17:19 PM  12/11/2009 10:20:00 PM
1209  John  Smith  4    1196       12/11/2009 10:20:00 PM  12/11/2009 10:20:11 PM

每个历史记录都有一个 Entity_ID,它是当前行的 ID,新记录上的 Date_Start 与最后一个历史行的 Date_Modified 匹配。这允许我们进行类似 Where Entity_ID = :id Or ID = :id And :myDate < Date_Modified And :myDate >= Date_Start 的查询。 .历史可以通过 Entity_ID = :current_id 获取.

有没有更好的方法,希望更易于维护/灵活来做到这一点? 这个概念很简单,当更新一行时,通过插入旧值将其复制到同一个表,然后更新当前行......但实际上这样做,我还没有找到更简单的方法。我希望 Oracle 中更狡猾/更聪明的人对此有更好的方法。速度并不重要,我们像大多数 Web 应用程序一样 99% 读取 1% 写入,并且所有批量操作都是插入,而不是不会创建任何历史记录的更新。

如果有人有任何想法可以简化对此的维护,我将非常感激,谢谢!

最佳答案

好吧,这是重写。我第一次回复时错过的是应用程序将其历史记录存储在主表中。现在我明白为什么@NickCraver 对代码如此抱歉了。

那么首先要做的是追捕这种设计的肇事者,并确保他们再也不会这样做了。像这样存储历史不会扩展,使正常(非历史)查询更加复杂并破坏关系完整性。显然,有些情况下这些都不重要,也许您的站点就是其中之一,但总的来说,这是一个非常糟糕的实现。

最好的方法是 Oracle 11g Total Recall .这是一个优雅的解决方案,具有完全不可见且高效的实现,并且 - 按照 Oracle 其他收费附加服务的标准 - 价格相当合理。

但是,如果 Total Recall 是不可能的并且您确实必须这样做,则不允许更新。对现有 CONTACT 记录的更改应该是插入。为了完成这项工作,您可能需要使用 INSTEAD OF 触发器构建一个 View 。它仍然令人讨厌,但不像你现在所拥有的那样令人讨厌。

自 Oracle 11.2.0.4 起,Total Recall 已更名为 Flashback Archive,并作为企业许可的一部分包含在内(除非我们购买 Advanced Compress 选项,否则压缩日志表将被删除)。

来自 Oracle 的这种慷慨应该使 FDA 成为存储历史的正常方式:它高效、高效,它是 Oracle 内置的标准语法来支持历史查询。唉,我希望看到半熟的实现,触发器、主键损坏和可怕的性能多年。因为日记似乎是开发人员喜欢的那些干扰之一,尽管它是低级别的管道,与所有业务运营的 99.99% 基本上无关。

关于Oracle - 在更新时触发以创建历史记录行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2238683/

相关文章:

security - npm 审计是否有替代 yarn 的方法?

grails - grails 中有审计跟踪的标准方法吗?

c++ - 使用 C++ 和 OCCI 编写 CLOB

c# - 如何解决此 LINQ 查询中不支持外部应用的问题

创建触发器时的 MySQL 语法错误

Mysql触发器: Tried other suggestions but still get Error #1064

oracle - Oracle 11g 标准版上的 AUDIT 命令

sql - PL/SQL 和 SQL 脚本在一个带有 liquibase 的 sqlFile 中?

c# - 如何在创建新文件夹或文件时触发 Windows 服务启动