历史/可审计数据库

Mar*_*ark 13 sql database database-design temporal-database

这个问题与我在其他问题中可以找到的架构有关. 基本上在我的数据库中,我存储用户,位置,传感器等.所有这些都可以由用户在系统中编辑,并且可以删除.

但是 - 当编辑或删除项目时,我需要存储旧数据; 我需要能够在变更之前看到数据是什么.

数据库中还有不可编辑的项目,例如"读数".他们真的更像是一个日志.读数记录在传感器上,因为它是特定传感器的读数.

如果我生成一个读数报告,我需要能够看到读取时位置或传感器的属性.

基本上我应该能够重建任何时间点的数据.

现在,我之前已经完成了这项工作,并通过在每个可编辑表中添加以下列来使其运行良好:

valid_from
valid_to
edited_by
Run Code Online (Sandbox Code Playgroud)

如果valid_to = 9999-12-31 23:59:59则那是当前记录.如果valid_to等于valid_from,则删除记录.

但是,我对我需要用来强制执行外键一致性的触发器感到满意.

我可以通过使用"PostgreSQL"数据库的扩展来避免触发器.这提供了一个名为"period"的列类型,它允许您存储两个日期之间的一段时间,然后允许您执行CHECK约束以防止重叠周期.这可能是一个答案.

我想知道是否有另一种方式.

我见过人们提到使用特殊的历史表,但我真的不喜欢几乎每1个表维护2个表的想法(尽管它仍然可能).

也许我可以砍掉我的初步实施,以不打扰检查不属于"当前"记录的一致性 - 即只懒得去查制约记录中,其中的失效日期9999-12-31 23:59:59.毕竟,使用历史表的人似乎没有对这些表进行约束检查(出于同样的原因,你需要触发器).

有没有人对此有任何想法?

PS - 标题还提到了可审计的数据库.在我之前提到的系统中,总是有edited_by字段.这允许跟踪所有更改,以便我们始终可以看到谁更改了记录.不确定可能会有多大差异.

谢谢.

Per*_*DBA 31

1月1日修订

好的,所以我所处的位置(提供完全可审计的数据库;您的特定要求)与您所处的位置之间存在差距:基于您的问题和评论.我们可能会在评论中解决这个问题.这是一个从头开始的立场.

  • 为了满足这一要求,根本不需要:触发器; 大规模复制; 破坏诚信; 等等

  • 这也不是经典的时间要求,所以不需要 "期间"能力,但你可以.

  • ValidFrom ValidTo是规范化错误:ValidTo是容易导出的数据; 任何行中的ValidTo都是重复的,位于下一行的ValidFrom中; 你有一个更新异常(当你在一行中更新一列时,你还需要更新下一行中的另一列); 你必须为"当前"使用虚拟值.

    • 所有不必要的,仅使用ValidFrom,并保持db clean和纯5NF.

    • 警告是,如果PostgreSQL无法执行子查询而不会陷入堆(ala Oracle),那么很好,kep ValidTo.

所有这些都可以由用户在系统中编辑,并且可以删除.

好吧,不.它是一个掌握重要信息的数据库; 使用参照完整性,而不是暂存器,因此用户不能只是走到它并"删除"某些东西.它将与维护历史数据的相同用户要求相矛盾(在Reading; Alert; Ack; Action; Download).

  • 不允许级联删除.这些函数是非数据库,MS Access类型的复选框.对于真实数据库,RI约束会阻止父项与子项被删除.

  • 不能(不应该)更改主键.例如.用户身份; LocationId; NetworkSlaveCode永远不会改变; 记住,他们是经过仔细考虑的标识符.PKs的一个特征是它们是稳定的.

  • 您可以添加新用户; 你可以改变当前用户的名字; 但您无法删除在"下载","确认","操作"中包含条目的用户.

基本上如果它是可编辑的,那么它必须是历史的(因此不包括读数和警报).

也不包括:下载; 致谢; 动作.

参考表:SensorType; 警告类型; 操作类型.

新的历史表:它们被插入,但无法更新或删除.

我发现使用isObselete标志的问题是..假设您更改位置,传感器外键现在将指向一个obselete记录,这意味着您将必须复制每个传感器记录.随着层次越来越大,这个问题会呈指数级变差.

  • 好的,所以你现在明白LocationId(FK)Sensor不会改变; 没有大规模重复等?首先没有问题(并且在那本愚蠢的书中!)在第二位出现指数恶化.

  • IsObsolete不适合您的要求.(参见下文)

  • UpdatedDtm任何真正行(Reading等)标识父(FK到Sensor)历史行(它AuditedDtm那是在当时的影响).

  • 完全关系能力; 声明性的指数完整性等

  • 维护IDEF1X,强标识符的关系概念......只有一个当前父行(例如,位置)

  • 历史记录中的行是当前行的图像,在更改之前,在所述行中AuditedDtm.当行(非历史记录)显示更改行时的最后一个UpdatedDtm.

  • AuditedDtm节目的整个系列的UpdatedDtms任何给定的关键; 因此我用它来划分时间意义上的真实密钥.

所需的只是每个可更改表的历史表.我提供了四个识别表的Hiistory表:位置; 传感器; NetworkSlave; 和用户.

请阅读本文以了解会计意义上的可审计.

数据模型

链接到带有历史记录的传感器数据模型(第2页包含历史记录表和上下文).

不熟悉关系建模标准的读者可能会发现IDEF1X表示法很有用.

对评论的回应

(1)我的第一个问题是与历史数据的参照完整性,因为我不确定是否存在,如果有,我不确定它是如何工作的.例如,在SensoryHistory中,如果您看到我的意思,就可以添加一个记录,该记录具有指示位置本身存在之前的日期时间的UpdatedDtm.这是否实际上是一个我不确定的问题 - 强制执行可能超过顶部.

(你在另一个问题中提出了类似的问题.)你可能经历的dbs实际上并没有实现参考完整性; 关系线只用于文​​档; RI是"在应用程序代码中实现的"(这意味着没有RI).

这是ISO/IEC/ANSI标准SQL数据库.这允许声明参照完整性.每个Relation行都实现为PK :: FK Reference,一个声明的实际约束.例如:

CREATE TABLE Location
    ...
    CONSTRAINT UC_PK
        PRIMARY KEY (LocationId)
    ...
CREATE TABLE Sensor
    ...
    CONSTRAINT UC_PK
        PRIMARY KEY (LocationId, SensorNo)
    CONSTRAINT Location_Sensor_fk
        FOREIGN KEY (LocationId)
        REEFERENCES Location(LocationId)
    ...
CREATE TABLE SensorHistory
    ...
    CONSTRAINT UC_PK
        PRIMARY KEY (LocationId, SensorNo, UpdatedDtm))
    CONSTRAINT Sensor_SensorHistory_fk
        FOREIGN KEY (LocationId, SensorNo)
        REEFERENCES Sensor (LocationId, SensorNo)
    ...
那些声明的约束由服务器强制执行; 不是通过触发器; 不在应用程序代码中.这意味着:

  • 一个Sensor带有LocationId不存在Location无法插入
  • LocationIdLocation,在具有行Sensor不能被删除
  • 一个SensorHistory带有LocationId+SensorNo不存在Sensor无法插入
  • 一个LocationId+SensorNoSensor在具有行SensorHistory不能被删除.

(1.1)所有列都应具有RULE和CHECK约束以约束它们的值范围.除了所有INSERT/UPDATE/DELETE都是程序化的,在存储过程中,因此事故不会发生,并且人们不会走到数据库并对其运行命令(SELECTS除外).

一般来说,我远离触发器.如果您正在使用存储过程和正常权限,那么:

如果你看到我的意思,在SensoryHistory中可以添加一个记录,其中有一个UpdatedDtm表示位置本身存在之前的日期时间

被阻止了.因此,插入一个SensorHistory,其SensorDtm早于Sensor本身.但是,过程不是声明性规则.但是,如果你想要倍加肯定(我的意思是加倍,因为INSERTS都是通过proc,用户直接命令),那么你必须使用触发器.对我来说,这是超过顶部.

(2)我如何表示删除?我想我可以在表的非历史版本中添加一个标志.

还不确定.例如.你是否接受当a Sensor被删除时,它是最终的......(是的,历史是保持的)...然后当一个新的Sensor被添加到Location它时,它会有一个新的SensorNo...没有Sensor被逻辑上替换为新的,有或没有时间间隔?

从最终用户的角度来看,通过软件,他们应该可以随意添加,编辑和删除传感器,没有任何限制.但是,是的,一旦删除它就被删除,不能删除.虽然使用完全相同的参数,但没有什么可以阻止他们重新添加传感器.

并且"删除" Locations, NetworkSlaves,Users以及.

好.然后新的Sensor具有相同的参数,是真正新的,它有一个新的SensorNo,并独立于任何以前的逻辑Sensor.我们可以IsObsolete在四个识别表中添加一个BOOLEAN; 它现在被认为是足够的.删除现在是软删除.

(2.1)对于NetworkSensorLoggerSensor实际上依赖于两个父母的和:如果他们的父母中的任何一个已经过时,它们就会过时.所以没有必要给它们一个IsObsolete具有双重含义的列,它可以从适用的父类派生.

(2.2)为了清楚起见,用户无法从任何Transaction和History表中删除任何行,对吧?

(3)更新表时,最好在历史表中插入新行并更新主表的方法是什么?只是事务中的普通SQL语句可能吗?

是.这是交易的经典用法,根据ACID Properties,它是Atomic; 它要么成功,要么在托托失败(以后在问题解决时重试).

(4)参考书

最终和开创性的文本是时间数据和关系模型 CJ日期,H Darwen,NA Lorentzos.同样,我们这些拥抱RM的人都熟悉扩展,以及RM的继任者需要什么; 而不是其他一些方法.

引用的书是可怕的,而且是免费的.PDF不是PDF(无搜索;无索引).打开我的MS和Oracle正在讲述; 一些好的东西充满了许多绒毛.许多虚假陈述.不值得回复(如果你想进行适当的审查,请打开一个新问题).

(4.1)ValidTo除了ValidFrom.本书所犯的严重错误(如我的答案顶部所述); 然后费力地解决了.首先不要犯错误,第二位你没有什么可以解决的.据我了解,这将消除你的触发器.

(4.2)简单规则,同时考虑标准化和时间要求.首先,您需要深入了解(a)时间要求和(b)数据类型,正确的用法和限制.始终存储:

  • 即时日期,例如.UpdatedDtm

  • 间隔为INTEGER,清楚地标识列名称中的单位,例如.IntervalSec

  • 期.取决于合取或分离.

    • 对于这个要求的合取,(4.1)适用:使用一个DATETIME; 期间的结束可以从下一行的期间的开始得出.
    • 对于分离期,是的,您需要2 x DATETIME,例如,RentedFroma RentedTo和之间存在间隙.

(4.3)它们混淆了"时间主键",这使代码复杂化(除了需要触发器来控制更新异常).我已经提供了一个干净的(经过试验和测试的)临时主键.

(4.4)它们混淆了虚拟值,非实数值和"现在"的空值.我不允许在数据库中使用这些东西.由于我没有存储重复的ValidTo,我没有问题,没有什么可以解决的.

(4.5)人们不禁要问为什么528页的"教科书"可以在网上免费获得,格式不佳.

(5)我[一个用户]可以安静地删除所有的LocationHistory行,(只留下Location表中的当前版本) - 即使可能存在一个SensorHistory行,它在概念上"属于"以前版本的位置,如果这是有道理的.

这对我来说没有意义,我们必须关闭的沟通仍然存在差距.请保持互动直至关闭.

  • 在真实的(标准ISO/IEC/ANSI SQL)数据库,我们就不会 GRANT INSERT/UPDATE/DELETE权限的用户.我们授予SELECT和REFERENCES (对于选定的用户)所有INSERT/UPDATE/DELETE都在Transactions中编码,这意味着存储过程.然后我们在每个存储过程中向所选用户授予EXEC(使用ROLES来减少管理).

    • 因此,没有人可以在不执行proc的情况下从任何表中删除.

    • 不要编写要从任何历史记录表中删除的proc.不应删除这些行.在这种情况下,代码的非许可和不存在约束.

    • 从技术上讲,所有历史记录行都是有效的,没有可以关注自己的期间.最旧的LocationHistory行包含更改前的原始Location行的前映像.最年轻的LocationHistory行是当前Location行的前映像.因此,介于两者之间的每个LocationHistory行都有效并适用于中间的Period.

    • 无需"修剪"或查找可以删除的一些LocationHistory行,因为它们适用于未使用的Period:它们全部被使用.(最终,无需检查Location子映射到任何LocationHistory行的任何映射,以证明它.)

    • 底线:用户无法从任何历史(或交易)表中删除.

    • 或者你的意思是不同的东西?

    • 注意我上面添加了(1.1).

(6)纠正了DM中的一个错误.一个Alert是表达Reading,而不是Sensor.

(7)更正了其他问题/答案中的业务规则以反映; 并且在这个问题中暴露了新的规则.

(8)您是否理解/欣赏,因为我们有一个完全符合IDEF1X的模型,所以标识符:

  • 标识符通过整个数据库传输,保留其功能.例如.上市时Acknowledgements,可以直接加入LocationSensor; 不必读取中间的表(如果使用了Id键,则必须使用它们).这就是为什么在关系数据库中需要较少的连接(以及非规范化连接中需要更多连接)的原因.

  • 当特定上下文相关时,需要导航子类型等.

  • 如果仅仅因为这个答案非常庞大,那就+1. (4认同)
  • @orangepips.谢谢.(这不是很大; [**这**](http://stackoverflow.com/questions/4310769/)是巨大的!) (2认同)