Oracle-触发在更新时创建历史记录行 [英] Oracle - Triggers to create a history row on update

查看:72
本文介绍了Oracle-触发在更新时创建历史记录行的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

首先,我们目前具有所需的行为,但是当需要对数据库进行任何更改时,维护它并不是一件容易的事.我正在寻找更简单,更有效或更易于维护的东西(最欢迎执行这三个条件中的任何一个).当我们执行更新时,将创建一个历史行,该历史行是当前行的副本,然后更新当前行的值.结果是,我们拥有有关行在更新之前的状态的历史记录.

First, we currently have the behavior that's desired, but it's not trivial to maintain when any changes to the database are needed. I'm looking for anything simpler, more efficient, or easier to maintain (anything that does any of those 3 would be most welcome). When we perform an update, a history row is created that is a copy of the current row, and the current row's values are then updated. The result being that we have a history record of how the row was before it was updated.

理由:我们必须遵守多项联邦法规,并且采用这种方式来拥有所有内容的完整审核历史记录,并且我们可以随时查看数据库并查看情况(未来要求). 出于类似原因,我无法更改历史记录的记录方式 ...任何解决方案都必须产生与当前触发器创建的数据相同的数据.

Reasoning: We have to be compliant with a number of federal rules, and went this route to have a full audit history of everything, as well as we can look at the database at any point in time and see how things looked (future requirement). For similar reasons, I cannot change how history is recorded...any solution must result in the same data as the current triggers create.

Contact表的当前触发器如下所示:
(为简洁起见,删除了无用的字段,字段的数量无关紧要)

Here's what the current triggers look like for the Contact Table:
(stripped useless fields for brevity, the number of fields doesn't matter)

更新前(每行):

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;

更新前(所有行一次):

Before update (once for all rows):

BEGIN
  state_pkg.newCONTACTRows := state_pkg.eCONTACTRows;
END;

更新后(所有行一次):

After update (once for all rows):

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;

定义为(修整后的完整版本,只是每个表的副本)的软件包:

The package defined as (trimmed, full version is just copy of this per table):

PACKAGE STATE_PKG IS
  TYPE CONTACTArray IS TABLE OF CONTACT%ROWTYPE INDEX BY BINARY_INTEGER; 
  newCONTACTRows CONTACTArray; 
  eCONTACTRows CONTACTArray;
END;

当前结果

以下是产生的历史记录样本:

The current result

Here's a resulting history sample:

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获取历史记录.

Each history record has an Entity_ID that's the ID of the current row, the Date_Start on the new record matches the Date_Modified of the last history row. This allows us to do queries like Where Entity_ID = :id Or ID = :id And :myDate < Date_Modified And :myDate >= Date_Start. History can be fetched by Entity_ID = :current_id.

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

Is there a better approach, hopefully more maintainable/flexible to do this? The concept is simple, when updating a row, copy it to the same table via an insert with the old values, then update the current row...but actually doing that, I have yet to find a simpler way. I'm hoping someone much trickier/wiser in Oracle has a better approach to this. Speed doesn't matter much, we're 99% reads 1% writes like most web applications, and all bulk operations are inserts, not updates which wouldn't create any history.

如果有人有什么想法可以简化维护,谢谢,谢谢!

If anyone has any ideas to simplify the maintenance on this, I'd be extremely appreciative, thanks!

推荐答案

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

Okay, this is a rewrite. What I missed when I first responded is that the application is storing its History in the main table. Now I understand why @NickCraver is so apologetic about the code.

首先要做的是找出这种设计的实施者,并确保他们再也不会这样做.这样的历史记录无法扩展,使常规(非历史记录)查询更加复杂,破坏了关系完整性.显然,在某些情况下,这些都不重要,也许您的站点就是其中之一,但是总的来说,这是一个非常糟糕的实现.

Well the first thing to do is to hunt down the perpetrators of this design and make sure they never do it again. Storing history like this doesn't scale, makes normal (non-historical) queries more complicated and sabotages relational integrity. Obviously there are scenarios where none of that matters, and perhaps your site is one of them, but in general this is a very bad implementation.

最好的方法是 Oracle 11g总召回次数.这是一个优雅的解决方案,具有完全不可见和有效的实现,并且-按照Oracle其他应收费项目的标准-价格相当合理.

The best way of doing this is Oracle 11g Total Recall. It's an elegant solution, with a completely invisible and effcient implementation, and - by the standards of Oracle's other chargeable extras - quite reasonably priced.

但是,如果完全召回是不可能的,而您确实必须这样做,则不允许更新.对现有CONTACT记录的更改应为插入内容.为了完成这项工作,您可能需要使用INSTEAD OF触发器来构建视图.它仍然令人讨厌,但不像您现在所拥有的那样令人讨厌.

But if Total Recall is out of the question and you really must do it this, don't allow updates. A change to an existing CONTACT record should be an insert. In order to make this work you may need to build a view with an INSTEAD OF trigger. It's still yucky but not quite as yucky as what you have now.

从Oracle 11.2.0.4开始,Total Recall已被重新命名为Flashback Archive,并作为企业许可证的一部分包含(尽管压缩日志表中有很多内容,除非我们购买了Advanced Compress选项).

As of Oracle 11.2.0.4 Total Recall has been rebranded Flashback Archive and is included as part of the Enterprise License (although shorn of the compressed journal tables unless we purchase the Advanced Compress option).

Oracle的这一大手笔应该使FDA成为存储历史记录的常规方法:它高效,高效,是Oracle的内置标准语法以支持历史查询. las,我希望在过去很多年中都能看到半熟的实现,其中包括spatchcocked触发器,损坏的主键和糟糕的性能.因为日志记录似乎是开发人员喜欢的那些干扰因素之一,尽管事实上它是低级管道,实际上与所有业务运营的99.99%无关.

This largesse from Oracle ought to make FDA the normal way of storing history: it's efficient, it's performative, it's an Oracle built-in with standard syntax to support historical queries. Alas I expect to see half-cooked implementations with spatchcocked triggers, broken primary keys and horrible performance for many years yet. Because journalling seems to be one of those distractions which developers delight in, despite the fact that it's low-level plumbing which is largely irrelevant to 99.99% of all business operations.

这篇关于Oracle-触发在更新时创建历史记录行的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆