元组版本控制和复合主键

时间:2011-05-20 14:09:39

标签: sql sql-server

我必须创建一个数据库,并且必须确保我们可以按特定日期加载数据,所以我决定使用tuple versioning

假设我们有以下两个表:

CREATE TABLE Author
(
  Id UNIQUEIDENTIFIER NOT NULL,
  Firstname VARCHAR(100) NOT NULL,
  Surname VARCHAR(200) NOT NULL,
  ValidFrom DATETIME NOT NULL,
  ValidUntil DATETIME NULL,
  PRIMARY KEY (ID, ValidFrom)
)

CREATE TABLE Book
(
  Id UNIQUEIDENTIFIER NOT NULL,
  Title VARCHAR(100) NOT NULL,
  ISBN VARCHAR(100) NOT NULL,
  AuthorId UNIQUEIDENTIFIER NOT NULL,
  ValidFrom DATETIME NOT NULL,
  ValidUntil DATETIME NULL,
  PRIMARY KEY (Id, ValidFrom)
)

第一次进入新作者时,我将生成一个新的GUID。我也在书表中使用这个GUID来引用作者。

如果作者有更新,我创建一个具有相同GUID的新记录,但是将当前日期定义为“ValidFrom”,并将“ValidUntil”从原始记录设置为当前日期。

我不必更改book表,因为Author.Id没有更改。

我现在面临的问题是我想在Book上添加一个外键约束.AuthorId = Author.Id

不幸的是,这不起作用,因为我使用复合主键。我不想将Author.ValidFrom添加到我的Book表中,因为我只想引用最新的而不是特定的版本。

关于如何解决这个问题的任何想法?我想我可以添加一个触发器,确保如果已经记录了一本书就不能删除作者,但我没有允许级联删除的解决方案。

我很感激每一个提示或建议。

1 个答案:

答案 0 :(得分:3)

这适用于2008(依赖于使用MERGE语句来更改Book自动引用哪一行)。它确实引入了新列,您可能希望将它们隐藏在视图后面:

CREATE TABLE Author
(
  Id UNIQUEIDENTIFIER NOT NULL,
  Firstname VARCHAR(100) NOT NULL,
  Surname VARCHAR(200) NOT NULL,
  ValidFrom DATETIME NOT NULL,
  ValidUntil DATETIME NULL,
  Active as CASE WHEN ValidUntil is null THEN CONVERT(datetime,'99991231',112) ELSE ValidUntil END Persisted
  PRIMARY KEY (ID, ValidFrom),
  UNIQUE (ID,Active)
)
go
CREATE TABLE Book
(
  Id UNIQUEIDENTIFIER NOT NULL,
  Title VARCHAR(100) NOT NULL,
  ISBN VARCHAR(100) NOT NULL,
  AuthorId UNIQUEIDENTIFIER NOT NULL,
  ValidFrom DATETIME NOT NULL,
  ValidUntil DATETIME NULL,
  PRIMARY KEY (Id, ValidFrom),
  FK_Link as CONVERT(datetime,'99991231',112) persisted,
  Foreign key (AuthorID,FK_Link) references Author (Id,Active) on delete cascade
)
go
declare @AuthorId uniqueidentifier
set @AuthorId = NEWID()
insert into Author(Id,Firstname,Surname,ValidFrom)
select @AuthorId,'Boris','McBoris',CURRENT_TIMESTAMP
insert into Book(Id,Title,ISBN,AuthorId,ValidFrom)
select NEWID(),'How to use tuple versioning','12345678',@AuthorId,CURRENT_TIMESTAMP

;with newAuthorInfo as (
    select @AuthorId as Id,'Steve' as Firstname,'McBoris' as Surname,t.Dupl
    from (select 0 union all select 1) t(Dupl)
)
merge into Author a
using newAuthorInfo nai
on
    a.Id = nai.Id and
    a.ValidUntil is null and
    nai.Dupl = 0
when matched then update set ValidUntil = CURRENT_TIMESTAMP
when not matched then insert (Id,Firstname,Surname,ValidFrom)
values (nai.Id,nai.Firstname,nai.Surname,CURRENT_TIMESTAMP);

;with newAuthorInfo as (
    select @AuthorId as Id,'Steve' as Firstname,'Sampson' as Surname,t.Dupl
    from (select 0 union all select 1) t(Dupl)
)
merge into Author a
using newAuthorInfo nai
on
    a.Id = nai.Id and
    a.ValidUntil is null and
    nai.Dupl = 0
when matched then update set ValidUntil = CURRENT_TIMESTAMP
when not matched then insert (Id,Firstname,Surname,ValidFrom)
values (nai.Id,nai.Firstname,nai.Surname,CURRENT_TIMESTAMP);

go
select * from Author
select * from Book

delete from Author where ValidUntil is not null

select * from Author
select * from Book

delete from Author

select * from Author
select * from Book

对于2008年之前的解决方案,我认为你不能比触发器做得更好。您可以引入第二个只有Id列(唯一)的Author表,您可以从Book中反对它,并从该表级联删除到Book。然后你只需要在Author上有一个删除触发器,这样如果你从Author中删除特定作者ID的最后一行,你就删除了这个新表中的行