如何最好地管理数据库中的历史查找值?

Sul*_*phy 3 database oracle database-design

概述

一个事件数据库,它将有许多列保存查找表中保存的记录的 ID。

我试图解决的问题

我需要提出一个强大的解决方案来管理某些字段保存查找 ID 的历史数据。我已经列出了我提出的解决方案以及替代方案。我想从其他开发人员那里知道他们是否在他们的项目中以类似的方式管理这些场景。也许你有更好的方法?


数据库:Oracle 10g

:部门名称

场景:部门名称在一年中可以更改 X 次。企业需要报告其所有部门的数据,但希望在其各自部门名称下查看事件,就像事件发生时一样。

建议的解决方案:在部门名称查找表中设置条目时,设置开始和结束日期值。使用视图,根据事件日期创建一个计算字段,以便在任何给定时间点访问正确的部门名称。

优点:通过一些防御性编码,它可以使选定用户的自助服务能够通过 GUI 管理他们的静态数据,而无需任何额外的数据库更改。可以即时更改,例如完全更改名称。不需要 DBA 支持。

缺点:考虑到在大型数据集上进行的查找/计算量,这可能是一项昂贵的操作。

替代解决方案:只需使用并插入部门名称的纯文本值。这里的缺点是临时请求需要 DBA 来更改/更新值,可能针对特定日期范围并错误地丢失一些记录。表空间消耗也会增加。


:Assigned_Technician_ID

场景:事件将分配一名技术人员,技术人员的 ID 将存储在该位置。查找表将保存所有可用技术人员的“当前”列表。当人们离开公司时,必须更新列表并删除过时的技术人员。这是为了将下拉列表中的值数量保持在最低限度。企业仍希望查看分配给哪些技术人员处理所有事件数据。

解决方案:不要从技术人员查找表中删除条目,而是使用表示“已归档/已删除”的标志来标记该条目。此标志将作为 GUI 下拉菜单上的过滤器,以删除不需要的条目。

优点:查找表仅包含员工表中技术人员的 UID。因此,如果业务需求发生变化,很容易在主视图中呈现技术人员的任何属性,例如全名或员工编号等。

缺点:与前面的示例一样,查找可能是对大型数据集的昂贵操作。GUI 方面需要在业务逻辑和设计方面进行额外的工作。特别是在原始条目已“存档”时如何管理下拉列表。

替代解决方案:与上面的示例一样,只需使用纯文本值。这里的缺点是会消耗更多的表空间,并且随着不断变化的业务需求的灵活性降低。

Tom*_*att 7

有一种称为版本控制的技术已经存在多年,但由于多种原因在很大程度上是行不通的。然而,有一种类似的技术,我称之为版本范式,我发现它非常有用。这是一个使用员工表的示例。

首先,创建静态表。这是主要的实体表,它包含有关实体的静态数据。静态数据是在实体的生命周期内预计不会发生变化的数据,例如出生日期。

create table Employees(
  ID        int  auto_generated primary key,
  FirstName varchar( 32 ),
  Hiredate  date not null,
  TermDate  date,            -- last date worked
  Birthdate date,
  ...              -- other static data
);
Run Code Online (Sandbox Code Playgroud)

重要的是要意识到每个员工都有一个条目,就像任何此类表一样。

然后是关联的版本表。这与静态表建立了 1 米关系,因为一个员工可能有多个版本。

create table Employee_versions(
  ID         int   not null,
  EffDate    date  not null,
  char( 1 )  IsWorking not null default true,
  LastName   varchar( 32 ),    -- because employees can change last name
  PayRate    currency not null,
  WorkDept   int   references Depts( ID ),
  ...,              -- other changable data
  constraint PK_EmployeeV primary key( ID, EffDate )
);
Run Code Online (Sandbox Code Playgroud)

在版本表注释中有一个生效日期,但没有匹配的不再有效的字段。这是因为一旦一个版本生效,它就会一直有效,直到被后续版本取代。ID 和 EffDate 的组合必须是唯一的,这样同一员工不能有两个同时处于活动状态的版本,也不能在一个版本结束的时间和下一个版本开始的时间之间存在间隔。

大多数查询都想知道员工数据的当前版本。这是通过将员工的静态行与现在有效的版本连接来提供的。这可以通过以下查询找到:

select  ...
from    Employees e
join    Employee_versions v1
    on  v1.ID = e.ID
    and v1.EffDate =(
        select  Max( v2.EffDate )
        from    EmployeeVersions v2
        where   v2.ID = v1.ID
            and v2.EffDate <= NOW()
    )
where  e.ID = :EmpID;
Run Code Online (Sandbox Code Playgroud)

这将返回最近一次开始的唯一一个版本。在日期检查 ( v2.EffDate <= NOW()) 中使用不等式 <=允许将来的生效日期。假设您知道新员工将在下个月的第一天开始工作,或者计划在下个月的 13 日加薪,则可以提前插入此数据。此类“预加载”条目将被忽略。

不要让子查询找到你。所有搜索字段都已编入索引,因此结果非常快。

这种设计具有很大的灵活性。上面的查询返回所有员工的最新数据,现在和过去。您可以检查该TermDate字段以获取当前员工。事实上,由于您的应用程序中有很多地方只对当前员工的当前信息感兴趣,因此该查询将是一个很好的视图(省略最后一个where子句)。应用程序甚至不需要知道此类版本的存在。

如果您有一个特定的日期,并且想要查看当时有效的数据,则将v2.EffDate <= NOW()子查询中的更改为v2.EffDate <= :DateOfInterest

更多的细节可以在幻灯片演示文稿中找到这里和一个不很完成文档在这里

为了展示设计的一些可扩展性,请注意IsWorking版本表中有一个指示符,静态表中有一个终止日期。当员工离开公司的最后日期插入静态表,并与最新版本的副本IsWorking设置为false被插入到版本表。

员工离开公司一段时间然后再次被雇用是相当普遍的。仅使用静态表中的日期,只需将该日期设置回 NULL 即可再次激活该条目。但是,当此人不再是雇员时,任何时候的“回顾”查询都会返回结果。没有迹象表明他们已经离开了公司。但是,IsWorking当离开公司时带有= false 和IsWorking返回公司时带有= true的版本将允许在感兴趣的时候检查该值,并在员工不再是员工时忽略他们,即使他们稍后返回。