我有一个非常简单的表students
,结构如下,其中主键为id
。这个表是大约20个数百万行表的替身,它们很多联合在一起。
+----+----------+------------+ | id | name | dob | +----+----------+------------+ | 1 | Alice | 01/12/1989 | | 2 | Bob | 04/06/1990 | | 3 | Cuthbert | 23/01/1988 | +----+----------+------------+
如果Bob想要改变他的出生日期,那么我有几个选择:
使用新的出生日期更新students
。
肯定: 1 DML操作;可以通过单个主键查找来访问该表。
否定:我失去了Bob曾经认为他出生于04/06/1990的事实
在表格中添加一列created date default sysdate
,然后将主键更改为id, created
。每update
成为:
insert into students(id, name, dob) values (:id, :name, :new_dob)
然后,每当我想要最新信息时,请执行以下操作(Oracle但问题代表每个RDBMS):
select id, name, dob
from ( select a.*, rank() over ( partition by id
order by created desc ) as "rank"
from students a )
where "rank" = 1
肯定:我从不丢失任何信息。
否定:整个数据库上的所有查询都需要更长的时间。如果表格是指示的大小,这无关紧要,但是一旦你使用范围扫描而不是独特的扫描开始产生影响,就会在你的第5 left outer join
开始。
添加一个不同的列,deleted date default to_date('2100/01/01','yyyy/mm/dd')
或任何过早或未来的日期,我的想象。将主键更改为id, deleted
,然后每个update
变为:
update students x
set deleted = sysdate
where id = :id
and deleted = ( select max(deleted) from students where id = x.id );
insert into students(id, name, dob) values ( :id, :name, :new_dob );
以及获取当前信息的查询变为:
select id, name, dob
from ( select a.*, rank() over ( partition by id
order by deleted desc ) as "rank"
from students a )
where "rank" = 1
肯定:我从不丢失任何信息。
否定:两个DML操作;我仍然必须使用额外成本或范围扫描的排名查询,而不是在每个查询中使用唯一索引扫描。
创建第二个表格,例如student_archive
,并将每个更新更改为:
insert into student_archive select * from students where id = :id;
update students set dob = :newdob where id = :id;
肯定:永远不会丢失任何信息。
否定: 2个DML操作;如果您想获得所有信息,则必须使用union
或额外的left outer join
。
为了完整起见,有一个可怕的非规范化数据结构:id, name1, dob, name2, dob2...
等。
如果我不想丢失任何信息并且总是进行软删除,则不能选择数字1。数字5可以安全地丢弃,因为它造成的麻烦超过它的价值。
我留下了选项2,3和4以及随之而来的消极方面。我通常最终使用选项2和可怕的150行(间距很大)多个子选择连接。
tl;博士我意识到我在这里的“非建设性”投票上滑行接近但是:
在从不删除任何数据时,保持逻辑一致性的最佳(单数!)方法是什么?
有没有比我记录的更有效的方式?在这种情况下,我将高效定义为“更少的DML操作”和/或“能够删除子查询”。如果您能够在(如果)回答时想到更好的定义,请随意。
答案 0 :(得分:1)
我会坚持#4进行一些修改。不需要从原始表中删除数据;在更新(或删除之前)原始记录之前,将旧值复制到存档表就足够了。这可以通过行级触发器轻松完成。在我看来,检索所有信息并不是经常的操作,我没有看到额外的join / union有什么问题。此外,您可以定义一个视图,因此从最终用户的角度来看,所有查询都是直截了当的。