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

时间:2009-04-13 20:22:28

标签: sql mysql database-design

假设的例子:

我有汽车和车主。每辆车在给定时间属于一个(且仅一个)所有者,但所有权可能被转移。车主可以随时拥有零辆或多辆车。我想要的是将历史关系存储在MySQL数据库中,这样,在给定任意时间的情况下,我可以查找当前的车辆分配给业主。

即。在时间X(X可以现在或过去的任何时间):

  • 谁拥有车Y?
  • Z拥有哪些车(如果有的话)?

在SQL中创建M:N表(带有时间戳)很简单,但是我想避免使用相关的子查询,因为这个表会变大(因此性能会受到影响)。有任何想法吗?我觉得有一种方法可以通过加入这样一个表来实现这一点,但我对数据库的经验并不十分。

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

6 个答案:

答案 0 :(得分:9)

创建一个名为CarOwners的第三个表,其中包含carid,ownerid和start_date以及end_date的字段。 购买汽车时填写前三个并检查表格以确保没有其他人被列为所有者。如果有,则将该数据更新为end_date。

寻找当前所有者:

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

答案 1 :(得分:2)

我发现处理此类要求的最佳方法是仅维护VehicleEvents的日志,其中一个是ChangeOwner。在实践中,您可以得出这里提出的所有问题的答案 - 至少与收集事件时一样准确。

每条记录都有一个时间戳,表明事件发生的时间。

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

此外,通过时间戳,可以在事后添加事件(只要时间戳准确反映事件发生的时间。

试图以任何其他方式维持这种事物的历史状态我试过会导致疯狂。 (也许我还在恢复。:D)

顺便说一句,这里的区别特征可能是它是时间序列或事件日志,而不是它是1:m。

答案 2 :(得分:1)

为什么没有交易表?其中包含车辆ID,FROM所有者,TO所有者以及交易发生的日期。

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

要在3月1日找到所有者253所拥有的汽车:

SELECT * FROM transactions WHERE ownerToId = 253 AND date&gt; '2009-03-01'

答案 3 :(得分:0)

cars table可以有一个名为ownerID的id,然后你可以简单地

1.从car.ownerid = owner.ownerid中选择汽车内部联盟所有者的车辆所有者= y

2.从拥有者= z

的汽车中选择汽车

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

答案 4 :(得分:0)

根据您的业务规则,每辆车至少属于一个所有者(即在分配给汽车之前存在所有者)以及该表可能变大的操作约束,我将按如下方式设计架构: / p>

(通用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 )
)

我个人不会从我的客户端应用程序触及第三个表,而只是让数据库完成工作 - 并保持数据完整性 - 使用Cars表上的ON UPDATE AND ON DELETE触发器来填充每当汽车改变所有者时(即每当在OwnerId列上提交UPDATE时)或汽车被删除时HistoricalCarOwners表。

通过上述架构,选择当前车主是微不足道的,选择历史车主很简单

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,Vince

答案 5 :(得分: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 

这些解决方案在功能上等同于Javier的建议,但根据您使用的数据库,一个解决方案可能比另一个更快。

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