与日期的唯一约束

时间:2015-10-20 07:33:35

标签: sql oracle

我需要为Oracle数据库表添加一个唯一约束,如果其他2个日期列不重叠,则外键引用只能存在多次

e.g。

car_id  start_date  end_date
3       01/10/2012  30/09/2013  
3       01/10/2013  30/09/2014  -- okay no overlap
3       01/10/2014  30/09/2015  -- okay no overlap
4       01/10/2012  30/09/2013  -- okay different foregin key
3       01/11/2013  01/01/2014  -- * not allowed overlapping dates for this car.

有什么建议吗?提前谢谢。

2 个答案:

答案 0 :(得分:0)

上次我看到了这方面的要求和解决方案,我已经看到了这个:

创建一个after语句触发器。在这个触发器中,在你的桌子上进行自我加入,如下所示:

select count(*)
from your_table a 
join your_table b
on a.car_id = b.car_id and
  (a.start_date between b.start_date and b.end_date
   or 
   b.start_date between a.start_date and a.end_date)

如果count为零,则一切正常。如果计数> 0然后引发异常,该语句将被回滚。

OBS:这不适用于带>的表格数百万行和许多插入。 它适用于小型查找表,如果你有一个大表,则可以使用大表和很少插入(批量插入)。

答案 1 :(得分:0)

我认为通过某种过程跟踪汽车,每个日期记录状态变化。例如,您显示汽车#3于2012年10月1日再次于2013年10月1日和2014年10月1日再次进行状态更改。最终条目意味着该州于2015年10月1日再次更改。条目显示在?或者状态总是持续一年 - 这使得有可能在州开始后立即指定州的结束?如果是这样,那么在2013年11月1日显示状态变化的条目是完全错误的。但是一年的规范可能只是一个巧合。您可以为示例数据选择简单的数据点。

此时您的担忧是严格识别来自准确数据的有效数据。我们设计数据库(或应该),重点是数据完整性或有效性。这意味着我们尽可能地限制每一段数据,以便与该数据的规格保持一致。

例如,car id字段是外键 - 通常是定义 car 实体的每个实例的表。所以我们知道至少有两辆汽车的id为3和4.否则这些值在您显示的示例中不存在。

但是准确性还是正确性呢?假设在你的例子的最后一个条目中,汽车ID 3应该真的是4?无法从数据库中分辨出来。这说明了不同之处。 3和4都是有效值,我们能够将这些值限制为仅有效值。但只有一个是正确的 - 假设他们是迄今为止定义的唯一两辆车。关键是,没有测试,没有办法将值约束到正确的值。我们可以检查有效性 - 而不是准确性。

您要做的是通过有效性测试检查准确性。您可以声明"没有重叠"限制成为有效性检查,但这只是一种准确性检查。我们有时可以执行测试以发信号通知异常,这些异常表明某处存在不准确性。例如,重叠可能意味着2014年9月30日(第二行)的结束日期错误或2013年11月1日(最后一行)的开始日期错误或两者都可能出错。我们不知道这代表了哪种情况。因此,我们不能阻止最后一行输入数据库 - 第二行可能不正确。

无效数据本身无效。假设试图为car id 15插入一行,并且CARS表中没有car 15的条目。然后值15无效,并且可以(并且应该)阻止行进入表。但是日期重叠是由错误的数据某处引起的 - 我们无法准确知道在哪里。我们可以向用户发出不一致的信号,或者在某个地方创建日志条目以让某人查看问题,但我们不应该拒绝导致"引起的行。当它很可能是包含错误数据的现有行时重叠。

与数据本身一样,准确性源自数据库之外。如果我们足够幸运能够检测到不准确的实例,那么解决方案也位于数据库之外。我们能做的最好的事情是标记它并让某人调查以确定哪些数据是正确的,哪些是不正确的(并且希望)纠正不准确性。

更新:在讨论了数据完整性和准确性的概念以及它们之间的差异之后,这是一个可能有所改进的设计理念。

注意:这是基于这样的假设:从第一个条目到最后一个条目,每个汽车的日期范围形成一个不间断的范围。也就是说,没有差距。

简单:完全取消end_date字段。汽车的第一个条目设置该汽车的当前状态,没有指定结束日期。明确的含义是,国家将无限期地继续进入未来,直到插入下一个州的变化。然后,第二个状态变化的开始日期变为第一个状态变化的结束日期。根据需要继续。

create table Car_States(
    Car_ID     int not null,
    Start_Date date not null,
    ...,      -- other info
    constraint FK_Car_States_Car foreign key( Car_ID )
        references Cars( ID ),
    constraint PK_Car_States primary key( Car_ID, Start_Date )
);

现在让我们看一下数据

car_id  start_date
3       01/10/2012
3       01/10/2013  -- okay no overlap
3       01/10/2014  -- okay no overlap
4       01/10/2012  -- okay different foreign key
3       01/11/2013  -- What does this mean???

在输入最后一行之前,这里是如何读取id为3的汽车的数据:汽车3在2012年10月1日开始在特定状态下生活,在2013年10月1日更改为另一个州然后再次开启2014年10月1日,它仍然存在。

现在进入最后一排:2012年10月1日,汽车3在特定州开始生活,2013年10月1日更改为另一州,2013年11月1日更改为另一州,然后在2014年10月1日再次停留

正如我们所看到的,我们能够轻松地将新数据吸收到模型中。该设计使得不可能有间隙或重叠。

但这真的是一种改善吗?如果最后一个条目是一个错误怎么办 - 可能意味着不同的汽车而不是汽车3?或者输入了错误的日期。新模型只是接受了错误的数据,没有任何投诉,我们不知道表格中有不正确的数据。

这是事实。但它与原始场景有何不同?最后一行代表"错误"数据。问题是,"我如何防止这种情况?"答案是,在这两种情况下,"你不能!遗憾&#34。设计中最好的方法就是检测出差异并将其引起别人的注意。

有人可能会认为,对于原始设计,开始日期和结束日期在同一行中,很容易确定新期间是否与先前定义的任何时期重叠。但这也很容易通过仅限开始日期的设计来确定。重要的是,在将数据写入表之前发现这种可能的不准确性的测试主要在于应用程序,而不仅仅在于数据库中。

由用户和/或某些自动过程来验证新数据和现有数据,并确定是否存在任何不准确之处。仅使用一个日期的优点是,在显示警告消息后显示"您确定吗?"响应,可以插入新记录并完成操作。如果有两个日期,则必须找到其他记录并重新同步其日期以匹配新期间。