3

I would like to learn a declarative approach for a data constraint issue I have had from time to time related to exclusive date ranges.

Below is a simplified example. I have items and prices on those items. I want the effective date range of the prices to be mutually exclusive with no overlap.

As I understand things with Oracle, user-defined functions are not eligible for use in CONSTRAINT declarations - and I can't even imagine how poorly it would perform if it were allowed. So I require a procedural approach using triggers. Typical trigger source is also included below.

I am not interested in learning better procedural logic for use within the trigger (it is just a simple demonstrative example). I am interested in learning a more declarative solution to a relatively popular data constraint issue I (and likely others) face.

I want to eliminate trigger-based solutions whenever I can, as a practice. However I can't seem to find my way out of this requirement without a trigger.

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' ); 
4

2 回答 2

2

在等待下一个支持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' ); 

让我们试试

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_%)。

于 2013-07-16T17:37:16.577 回答
0

正如 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. 直到 a 才强制执行该约束COMMIT。尽管这可能是一件积极的事情,但许多类型的更改会暂时产生重叠的结果。如果您不允许重叠结果,则必须按特定顺序修改表。
于 2013-07-17T19:28:47.147 回答