Mysql 具有时间间隔的M:N表的数据库设计

Mysql 具有时间间隔的M:N表的数据库设计,mysql,sql,Mysql,Sql,我想问你一个设计问题: 我正在设计一个让我挠头的桌子,不确定最好的方法是什么,我觉得我错过了一些东西: 有两个表A和B,它们之间有一个M:N关系表。关系表现在具有以下值: A.ID,B.ID,From,To 业务要求: 在任何时候,A:B关系只能是1:1 A:B可以按From和To datetime值定义的时间重复,这两个值指定了一个间隔 例如:汽车/司机。 任何一辆车在任何时候都只能有一名司机 任何驾驶员在任何时候只能驾驶一辆车(这不是topgear,好吗?:) 司机可以在一段时间后换车,也可

我想问你一个设计问题:

我正在设计一个让我挠头的桌子,不确定最好的方法是什么,我觉得我错过了一些东西:

有两个表A和B,它们之间有一个M:N关系表。关系表现在具有以下值: A.ID,B.ID,From,To

业务要求: 在任何时候,A:B关系只能是1:1 A:B可以按From和To datetime值定义的时间重复,这两个值指定了一个间隔

例如:汽车/司机。 任何一辆车在任何时候都只能有一名司机 任何驾驶员在任何时候只能驾驶一辆车(这不是topgear,好吗?:) 司机可以在一段时间后换车,也可以回到同一辆车上

现在,我不确定: -我应该和什么一起去?A、 B是不够的,添加From和To感觉不对,可能是一个自动增量PK? -有没有办法通过DB设计来强化业务需求? -出于商业原因,我不希望它出现在历史表格中。为什么?好吧,让我们假设这辆车是租来的,我想知道,给定一个日期,谁在那个日期租了什么车。将其拆分为历史表将需要更多的joinst:(

我觉得我错过了一些东西,一些一般的模式…或者我不知道


谢谢你的帮助,所以谢谢你:)

我不认为你错过了什么。我想你已经掌握了问题所在

我读过几篇关于如何在关系数据库中处理“时态”数据的文章

基本共识是,传统的关系模型没有任何内置机制来支持时态数据

有几种方法,有些方法比其他方法更适合特定的需求,但所有这些方法都感觉像是“管道胶带”

(我本想说“上紧了”,但我想在红绿灯的帽尖处是这样的:“……杂务工的秘密武器,管道胶带”,以及“如果女人们觉得你不帅,她们至少应该觉得你很在行。”)


对于表的主键或唯一键,您可以使用
(a\u id,b\u id,from)的组合
。这将为该行提供一个唯一标识符

但是,这并不能防止“时间”范围重叠

MySQL表没有声明性约束,可以防止存储为“开始”、“结束”或“开始”、“持续时间”等的日期时间范围“重叠”。(至少,在一般情况下。如果您有非常明确的范围,并且触发器将
四舍五入到甚至四小时的边界,并且持续时间精确到四小时,则可以使用唯一约束。在更一般的情况下,对于
的任何ol'值,唯一约束对u不起作用。)美国

CHECK
约束是不够的(因为您需要查看其他行),即使可能,MySQL实际上也不会强制执行CHECK约束

(据我所知)让数据库强制执行此类约束的唯一方法是使用
触发器
,它查找是否存在受影响(插入/更新)行冲突的另一行

插入前需要一个
触发器,更新前需要一个
触发器。触发器需要查询表,以检查是否存在与新的/修改的行“重叠”的行

SELECT 1
  FROM mytable t
 WHERE t.a_id = NEW.a_id
   AND t.b_id = NEW.b_id
   AND t.from <> OLD.from
   AND < (t.from, t.to) overlaps (NEW.from,NEW.to) >
选择1
来自MyT表
其中t.a_id=新的a_id
t.b_id=新的b_id
和t
和<(t.from,t.to)重叠(NEW.from,NEW.to)>
显然,最后一行是所需实际语法的伪代码

之前的行只需要在更新之前的
触发器中使用,因此我们找不到(作为“匹配”)正在更新的行。那里的实际检查实际上取决于
主键(或
唯一键)的选择

在MySQL 5.5中,如果发现新的/更新的行会违反约束,我们可以使用
SIGNAL
语句返回错误。在MySQL的早期版本中,我们可以通过执行导致实际错误发生的操作来“抛出”错误,例如对已知不存在的表名运行查询

最后,这种类型的功能不一定要在数据库触发器中实现;这可以在客户端处理。

三个表如何: TCar、TDriver、TLog

TCar pkCarID FKDridrid 名字

一个独特的司机索引确保司机只在一辆车里。转动外键 我们的关系是1:1

河 pkDriverID 名字

TLog pkLogID(代理pk) fkCarID FKDridrid 从…起 到


通过2个连接,您将获得您描述的任何信息。如果您只需要通过driverID或cardid查找汽车数据,您只需一个连接即可。

感谢大家的输入,到目前为止,我正在考虑此方法,如果您有任何批评/指出缺陷,我将不胜感激: 表(伪sqlcode):

当然,CarID是FK-to-Car(ID),DriverID是FK-to-Driver(ID)

下一个阶段是两个触发器(我希望这可以在mysql中完成(在MSSQL上工作,但我现在手头没有mysql db可供测试):

!!!警告,MSSQL暂时停止

create trigger Assignment _Update on Assignment instead of update as
    delete Assignment 
      from Assignment 
           join inserted 
             on (    inserted.CarID= Assignment .CarID
                  or inserted.DriverID= Assignment .DriverID)
                and ( inserted.CarID<> omem.CarID or inserted.DriverID<> omem.DriverID)
    insert into Assignment 
    select * from inserted;



create trigger Assignment _Insert on Assignment  after delete as
    insert into Assignment_History
    select CarID,DriverID,from,NOW() from deleted;
create trigger Assignment\u分配时更新,而不是更新为
删除分配
从分配
连接插入
on(inserted.CarID=Assignment.CarID
或插入。DriverID=Assignment.DriverID)
和(inserted.CarID omem.CarID或inserted.DriverID omem.DriverID)
插入到分配中
选择*从插入;
创建触发器分配\u在删除后插入分配作为
在作业历史记录中插入
从删除中选择CarID、DriverID、from、NOW();
我测试了一个bi
create trigger Assignment _Update on Assignment instead of update as
    delete Assignment 
      from Assignment 
           join inserted 
             on (    inserted.CarID= Assignment .CarID
                  or inserted.DriverID= Assignment .DriverID)
                and ( inserted.CarID<> omem.CarID or inserted.DriverID<> omem.DriverID)
    insert into Assignment 
    select * from inserted;



create trigger Assignment _Insert on Assignment  after delete as
    insert into Assignment_History
    select CarID,DriverID,from,NOW() from deleted;