Okay, in the specific example, I'd go for redundantly storing redundant data. Through a combination of CHECKs and FKs (and super keys), we ensure that the data is always correct, then we wrap a view and triggers around this to hide the implementation details:
create table dbo.Parents (
ParentID int IDENTITY(1,1) not null,
ValidFrom datetime not null,
ValidTo datetime not null,
/* Natural Key column(s) */
CONSTRAINT PK_dbo_Parents PRIMARY KEY (ParentID),
CONSTRAINT UQ_dbo_Parents_DRI UNIQUE (ParentID, ValidFrom, ValidTo),
/* Unique constraint on Natural Key */
CONSTRAINT CK_dbo_Parents_ValidDates CHECK (ValidFrom <= ValidTo) /* Semi-open interval */
)
go
alter table dbo.Parents add constraint DF_dbo_Parents_ValidFrom DEFAULT (CURRENT_TIMESTAMP) for ValidFrom
go
alter table dbo.Parents add constraint DF_dbo_Parents_ValidTo DEFAULT (CONVERT(datetime,'99991231')) for ValidTo
go
create table dbo._Children (
ChildID int IDENTITY(1,1) not null, /* We'll need this in the update trigger */
ParentID int not null,
ChildDate datetime not null,
_ParentValidFrom datetime not null,
_ParentValidTo datetime not null,
CONSTRAINT PK_dbo__Children PRIMARY KEY (ChildID),
CONSTRAINT FK_dbo__Children_Parents FOREIGN KEY (ParentID,_ParentValidFrom,_ParentValidTo) REFERENCES dbo.Parents (ParentID,ValidFrom,ValidTo) ON UPDATE CASCADE,
CONSTRAINT CK_dbo__Children_ValidDate CHECK (_ParentValidFrom <= ChildDate and ChildDate < _ParentValidTo) /* See, semi-open */
)
go
alter table dbo._Children add constraint DF_dbo__Children_ChildDate DEFAULT (CURRENT_TIMESTAMP) for ChildDate
go
create view dbo.Children (ChildID,ParentID,ChildDate)
with schemabinding
as
select ChildID,ParentID,ChildDate from dbo._Children
go
create trigger dbo.T_Children_I on dbo.Children instead of insert
as
begin
set nocount on
insert into dbo._Children (ParentID,ChildDate,_ParentValidFrom,_ParentValidTo)
select i.ParentID,i.ChildDate,p.ValidFrom,p.ValidTo
from
inserted i
inner join
dbo.Parents p
on
i.ParentID = p.ParentID
end
go
create trigger dbo.T_Children_U on dbo.Children instead of update
as
begin
set nocount on
if UPDATE(ChildID)
begin
RAISERROR('Updates to ChildID are not allowed',16,1)
return
end
update c
set
ParentID = i.ParentID,
ChildDate = i.ChildDate,
_ParentValidFrom = p.ValidFrom,
_ParentValidTo = p.ValidTo
from
inserted i
inner join
dbo._Children c
on
i.ChildID = c.ChildID
inner join
dbo.Parents p
on
i.ParentID = p.ParentID
end
go
insert into dbo.Parents(ValidFrom,ValidTo)
select '20081201','20090101' union all
select '20090201','20090301'
/* (2 row(s) affected) */
go
insert into dbo.Children (ParentID,ChildDate)
select 1,'20081215'
/* (1 row(s) affected) */
go
insert into dbo.Children (ParentID,ChildDate)
select 1,'20090115'
/*
Msg 547, Level 16, State 0, Procedure T_Children_I, Line 6
The INSERT statement conflicted with the CHECK constraint "CK_dbo__Children_ValidDate". The conflict occurred in database "Play", table "dbo._Children".
The statement has been terminated.
*/
go
update dbo.Parents set ValidTo = '20090201' where ParentID = 1
/* (1 row(s) affected) */
go
insert into dbo.Children (ParentID,ChildDate)
select 1,'20090115'
/* (1 row(s) affected) */
go
update dbo.Parents set ValidTo = '20090101' where ParentID = 1
/*
Msg 547, Level 16, State 0, Line 1
The UPDATE statement conflicted with the CHECK constraint "CK_dbo__Children_ValidDate". The conflict occurred in database "Play", table "dbo._Children".
The statement has been terminated.
*/
go
insert into dbo.Children (ParentID,ChildDate)
select 2,'20090215'
/* (1 row(s) affected) */
go
update dbo.Children set ChildDate = '20090115' where ParentID=2 and ChildDate = '20090215'
/*
Msg 547, Level 16, State 0, Procedure T_Children_U, Line 11
The UPDATE statement conflicted with the CHECK constraint "CK_dbo__Children_ValidDate". The conflict occurred in database "Play", table "dbo._Children".
The statement has been terminated.
*/
go
delete from dbo.Children
/* (3 row(s) affected) */
go
/* Clean up after testing */
drop view dbo.Children
drop table dbo._Children
drop table dbo.Parents
go
This is for SQL Server. Tested on 2005, but should work on at least 2000 and 2008 too. A bonus here is that even if the trigger is disabled (e.g. nested triggers are turned off), You cannot end up with wrong data in the base tables