【问题标题】:Declarative approach to constrain data ranges in table约束表中数据范围的声明性方法
【发布时间】:2013-07-16 16:57:39
【问题描述】:

我想学习一种声明式方法来解决我不时遇到的与专有日期范围相关的数据限制问题。

下面是一个简化的例子。我有这些物品的物品和价格。我希望价格的生效日期范围是互斥的,没有重叠。

根据我对 Oracle 的了解,user-defined functions are not eligible for use in CONSTRAINT declarations - 我什至无法想象如果允许它会表现得多么糟糕。所以我需要一种使用触发器的程序方法。下面还包括典型的触发源。

我对学习更好的在触发器中使用的程序逻辑不感兴趣(这只是一个简单的演示示例)。我有兴趣学习针对我(可能还有其他人)面临的相对流行的数据约束问题的更具声明性的解决方案。

作为一种实践,我想尽可能消除基于触发器的解决方案。但是,如果没有触发器,我似乎无法摆脱这个要求。

create table item ( title varchar2(32) primary key );
create table price ( 
   item           varchar2(32) not null references item (title), 
   price          number(9,2), 
   effective_from date not null, 
   effective_to   date not null, 
   constraint price_from_to_ck check (effective_to > effective_from ));

[REDACTED]
*(A combination of row and statement level triggers inteneded to prevent logical chronological overlap)

insert into item values ('LETTUCE');
insert into item values ('WHISKY');

insert into price values ( 'LETTUCE', 1.05, date '2013-01-01', date '2013-03-31' );
insert into price values ( 'LETTUCE', 1.08, date '2013-04-01', date '2013-06-30' ); 
insert into price values ( 'WHISKY', 33.99, date '2013-01-01', date '2013-05-31' );
insert into price values ( 'WHISKY', 31.15, date '2013-06-01', date '2013-07-31' ); 

-- should fail
insert into price values ( 'WHISKY', 30.55, date '2013-05-15', date '2013-06-05' ); 

【问题讨论】:

  • 很难正确使用触发器强制执行这种“非重叠”约束。您需要某种形式的锁定,因为您无法“看到”其他会话正在进行的更改,并且它们可能与您的发生冲突。请参阅 herehere 进行一些讨论。其他需要考虑的方法可能是:a)仅存储“起始”日期并假设每一行在下一个“起始”日期之前都有效,或者,b)使用物化视图。
  • @BrianCamire AskTom 的第一个链接是一个程序解决方案。而且,我想我看不到您的观点或 Jeff Kemp 文章的观点,即这是一个多用户并发问题。这与非空约束具有相同的并发方面。我只担心单个交易不会违反约束。
  • 我不同意。过程解决方案是一种选择(尽管您仍然需要锁定),但 AskTom 链接主要讨论基于触发器的方法。并发性比非空约束更多是一个问题,因为:a) 非空约束可以声明式地强制执行,并且,b) 非空约束一次仅适用于一行。为了说明并发问题,您发布的解决方案不会阻止两个并发事务同时插入相同(因此重叠)的行。
  • 不能同时插入两行。我在问题中的示例(假设是典型的 READ COMMITTED 事务)不允许第二次插入。但是,是的,它不同于非空约束。我最终想要的是对这种约束的行为,这种约束非常像非空约束一样处理。
  • 您发布的解决方案将允许同时插入重叠行。在两个 SQL*Plus 会话中尝试此操作,例如命名为 A 和 B:a) 在会话 A 中,插入一行,b) 在会话 B 中,插入具有重叠值的行,c) 在会话 A 中,提交事务,并且, d) 在会话 B 中,提交事务。你现在有一个重叠。

标签: sql oracle oracle11g date-range


【解决方案1】:

在等待下一个支持Temporal Validity的Oracle 12c版本时,我仍然使用下一个方法:

create table item ( title varchar2(32) primary key );
create table price ( 
   price_id          number primary key,
   item              varchar2(32) not null references item (title), 
   price             number(9,2), 
   effective_from    date not null, 
   effective_to      date not null, 
   effective_prev_to date,
   constraint price_from_to_ck check ( effective_to > effective_from ),
   constraint price_to_prev_ck check ( effective_from = effective_prev_to + 1 ),
   constraint price_from_uq unique ( item, effective_to ),
   constraint price_dates_chain_fk foreign key ( item, effective_prev_to ) references price ( item, effective_to ) );

insert into item values ('LETTUCE');
insert into item values ('WHISKY');

insert into price values ( 1, 'LETTUCE', 1.05, date '2013-01-01', date '2013-03-31', null );
insert into price values ( 2, 'LETTUCE', 1.08, date '2013-04-01', date '2013-06-30', date '2013-03-31' ); 
insert into price values ( 3, 'WHISKY', 33.99, date '2013-01-01', date '2013-05-31', null );
insert into price values ( 4, 'WHISKY', 31.15, date '2013-06-01', date '2013-07-31', date '2013-05-31' ); 

让我们try:

insert into price values ( 5, 'WHISKY', 30.55, date '2013-05-15', date '2013-06-05', date '2013-05-14' ); 

ORA-02291: integrity constraint (USER_4_E7DF1.PRICE_DATES_CHAIN_FK) violated - parent key not found : insert into price values ( 'WHISKY', 30.55, date '2013-05-15', date '2013-06-05', date '2013-05-14' )

但是现在更新和删除链中间的日期是一件很痛苦的事情。它需要使用MERGE 更改一条语句中的前后行。这就是我添加price_id 列的原因,因为您无法更新MERGE 中的键——因此,您需要另一个键而不是(item, Effective_%)。

【讨论】:

  • +1 表示 12c 链接。我正在研究这种可能性。 “PERIOD”的 12c 概念是我想要的方向。我认为我完全有可能将类似的东西拼凑起来。让我修补并发布我的想法(对于我们这些在可预见的未来使用 11g 的人)。
  • 我的修补不断引导我回到触发器。 (叹)。在我看来,这比触发器要好得多。而且它并不太神秘。此外,将 DAL 包裹在 MERGE 语句周围似乎足够优雅。谢谢。
  • 仅供参考。您在问题中提供的解决方案有两个主要缺陷。首先,它在并发环境中不起作用:一个会话在提交之前不会看到其他会话所做的任何更改。其次,它不会立即工作:当您已经有损坏的数据时,您需要插入一些东西来触发触发。第三,它根本不起作用:你忘了从p子查询中删除一行到p2,所以你可以只在price表中插入一行,然后它将在exists 部分中加入。
  • 触发源肯定是并发故障(Brian 终于通过我的厚厚的脑袋明白了这一点),实际代码并没有被设计/测试为有用 - 只是对一般方法有指导性,所以不用担心。
  • @CleverIdeaWidgetry ,请在您的问题中注意代码只是一个示例,而不是一个有效的解决方案。拯救一些新手程序员的脑袋。
【解决方案2】:

您可以使用物化视图以声明方式执行此操作,正如 Brian Camire 首次建议的那样。这是一个例子:

--Original tables (with an extra primary key on PRICE)
create table item ( title varchar2(32) primary key );
create table price ( 
   id             number primary key,
   item           varchar2(32) not null references item (title), 
   price          number(9,2), 
   effective_from date not null, 
   effective_to   date not null, 
   constraint price_from_to_ck check (effective_to > effective_from ));

create materialized view log on price with rowid;

--Items with overlapping dates
create materialized view price_no_overlap_mv
refresh fast on commit as
select 'overlapping row' as dummy, price1.rowid rowid1, price2.rowid rowid2
from price price1, price price2
where
    --Same item
    price1.item = price2.item
    --Overlapping dates
    and (price1.effective_from <= price2.effective_to and price1.effective_to >= price2.effective_from) 
    --Don't compare the same row
    and price1.id <> price2.id
;

--Throw an error if any rows ever get created.
alter table price_no_overlap_mv
add constraint price_no_overlap_mv_ck check (dummy = 'no rows allowed');


insert into item values ('LETTUCE');
insert into item values ('WHISKY');

insert into price values (1, 'LETTUCE', 1.05, date '2013-01-01', date '2013-03-31' );
insert into price values (2, 'LETTUCE', 1.08, date '2013-04-01', date '2013-06-30' ); 
insert into price values (3, 'WHISKY', 33.99, date '2013-01-01', date '2013-05-31' );
insert into price values (4, 'WHISKY', 31.15, date '2013-06-01', date '2013-07-31' ); 
commit;

-- should fail
insert into price values (5, 'WHISKY', 30.55, date '2013-05-15', date '2013-06-05' ); 
commit;
ORA-12008: error in materialized view refresh path
ORA-02290: check constraint (JHELLER.PRICE_NO_OVERLAP_MV_CK) violated

这种声明式方法既并发又一致。但是有很多缺点:

  1. 只有企业版支持快速刷新所需的实体化视图日志。
  2. 您的表需要一个主键,虽然您可能已经有一个,但只是没有在示例中包含它。
  3. 尽管是声明性的,但解决方案仍然不是直截了当的。您必须声明 相反 条件,然后检查它是否永远不存在。
  4. FAST REFRESH 工作可能不仅仅是最简单的查询的噩梦。即使对于这个简单的示例,我也必须使用旧式联接并且必须添加无用的 ROWID。
  5. 直到COMMIT 才强制执行该约束。尽管这可能是一件积极的事情,因为许多类型的更改会暂时产生重叠的结果。如果您从不允许重叠结果,则必须按特定顺序修改表格。

【讨论】:

  • 这是我过去使用的一种方法,它不够直观,无法在我生活的世界中维护。
猜你喜欢
  • 2013-01-18
  • 2017-04-11
  • 2012-06-28
  • 2012-09-28
  • 1970-01-01
  • 2019-04-26
  • 2019-03-21
  • 1970-01-01
相关资源
最近更新 更多