2009-04-13 89 views
4

假设的例子:在关系数据库中存储(和访问)历史1:M关系的最佳方式是什么?

我有车和业主。每辆车在给定时间属于一个(且只有一个)车主,但所有权可能会转移。业主可以随时拥有零或更多的汽车。我想要的是将历史关系存储在MySQL数据库中,在给定任意时间的情况下,我可以查看Cars to Owners的当前任务。

I.e.在时间X(其中X可以是现在或过去的任何时间):

  • 谁拥有汽车Y?
  • 拥有者Z拥有哪辆车(如果有的话)?

创建M:N表中的SQL(带有时间戳)是很简单的,但我想,以避免相关子查询作为该表将得到大(和,因此,性能将受到影响)。有任何想法吗?我有一种感觉,有办法通过自己加入这样一张表来实现这一点,但我对数据库并不熟悉。

更新:我想避免每行都使用“start_date”和“end_date”字段,因为每次插入新行时都需要(可能)昂贵的查找。 (另外,这是多余的)。

回答

9

制作第三张名为CarOwners的表,用于carid,ownerid的字段,并开始_日期和结束日期为_。 购买汽车时,请填写前三位并查看表格以确保没有其他人被列为车主。如果存在,则用该数据更新记录,作为结束日期_。

要查找当前所有者:

select carid, ownerid from CarOwner where end_date is null 

要找到一个点所有者时间:

select carid, ownerid from CarOwner where start_date < getdate() 
and end_date > getdate() 

GETDATE()是MS SQL Server特定的,但每个数据库都有一些函数,返回当前日期 - 只需替换。

当然,如果你还想从其他表中获得更多信息,你也可以加入他们。

select co.carid, co.ownerid, o.owner_name, c.make, c.Model, c.year 
from CarOwner co 
JOIN Car c on co.carid = c.carid 
JOIN Owner o on o.ownerid = co.ownerid 
where co.end_date is null 
+0

这个问题是它需要一个(可能是昂贵的)插入查找。我宁愿不使用“end_date”字段出于这个原因(也是多余的)。 – 2009-04-13 20:45:58

0

汽车表可以称为OWNERID一个id,那么你可以简单地

从汽车内1.select车车主一起在car.ownerid = owner.ownerid其中OWNERID = Y

2.select车来自汽车的所有者= z

不是确切的语法,而是简单的伪代码。

+0

这很好的状态的现在,但如果我想在一周前看到这个作业,那该怎么办? – 2009-04-13 20:31:44

0

鉴于你的业务规则,每辆车属于至少一个所有者(即。存在业主,他们被分配到前车AA)和您的运营约束,该表可能变大,如下我设计的架构:

(一般SQL 92语法:)

CREATE TABLE Cars 
(
CarID integer not null default autoincrement, 
OwnerID integer not null, 
CarDescription varchar(100) not null, 
CreatedOn timestamp not null default current timestamp, 
Primary key (CarID), 
FOREIGN KEY (OwnerID) REFERENCES Owners(OwnerID) 

) 

CREATE TABLE Owners 
(
OwnerID integer not null default autoincrement, 
OwnerName varchar(100) not null, 
Primary key(OwnerID) 
) 

CREATE TABLE HistoricalCarOwners 
(
CarID integer not null, 
OwnerID integer not null, 
OwnedFrom timestamp null, 
Owneduntil timestamp null, 
primary key (cardid, ownerid), 
FOREIGN KEY (OwnerID) REFERENCES Owners(OwnerID), 
FOREIGN KEY (CarID) REFERENCES Cars(CarID) 
) 

我个人不会碰到我的客户端应用程序中的第三张表格,但只需让数据库完成工作并保持数据完整性 - 每当汽车改变所有者时(即,只要UPDATE是UPDATE,汽车表格上的ON UPDATEON DELETE触发器就会填充HistoricalCarOwners表格在OwnerId列上承诺)或汽车被删除。

通过上述模式,选择目前的车主是微不足道的,选择历史车主是一个简单的

select ownerid, ownername from owners o inner join historicalcarowners hco 
             on hco.ownerid = o.ownerid 
             where hco.carid = :arg_id and 
               :arg_timestamp between ownedfrom and owneduntil 
order by ... 

HTH,文斯

0

如果你真的不希望有一个开始和结束日期,您可以只使用一个日期并执行类似下面的查询。

SELECT * FROM CarOwner co 
WHERE co.CarId = @CarId 
     AND co.TransferDate <= @AsOfDate 
     AND NOT EXISTS (SELECT * FROM CarOwner co2 
       WHERE co2.CarId = @CarId 
        AND co2.TransferDate <= @AsOfDate 
        AND co2.TransferDate > co.Transferdate) 

或略有变化

SELECT * FROM Car ca 
    JOIN CarOwner co ON ca.Id = co.CarId 
    AND co.TransferDate = (SELECT MAX(TransferDate) 
        FROM CarOwner WHERE CarId = @CarId 
              AND TransferDate < @AsOfDate) 
WHERE co.CarId = @CarId 

这些解决方案在功能上等同于哈维尔的建议,但具体取决于所使用一种解决方案可能比其他快数据库。

但是,根据您的读写比例,如果冗余更新关联实体中的结束日期,您可能会发现性能更好。

2

我发现处理这种需求的最好方法就是维护一个VehicleEvent的日志,其中一个是ChangeOwner。在实践中,您可以从这里提出的所有问题的答案 - 至少与您收集事件一样准确。

每条记录​​都有一个时间戳,指示事件发生的时间。

这样做的一个好处是可以在每个事件中添加最少量的数据,但有关车辆的信息可以积累和发展。

而且,与时间戳,可事后当事件发生时添加的事件(只要时间戳准确反映。

试图保持我有任何其他方式这样的事情历史状态试图导致疯狂(也许我还在恢复中:d)。

顺便说一句,这里的显着特征可能是,它是一个时间序列或事件日志,而不是它的1:M

1

为什么不有一个交易表?它将包含汽车ID,FROM所有者,TO所有者和交易发生的日期。

然后,您所要做的就是在所需日期之前查找汽车的第一笔交易。

要查找3月1日由业主253拥有的汽车:

SELECT * FROM transactions WHERE ownerToId = 253和date> '2009-03-01'

相关问题