Declarative approach to constrain data ranges in table

前端 未结 2 755
终归单人心
终归单人心 2021-01-14 07:23

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

2条回答
  •  没有蜡笔的小新
    2021-01-14 08:23

    You can do this declaritively with a materialized view, as first suggested by Brian Camire. Here's an example:

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

    This declarative approach is both concurrent and consistent. But there are a lot of draw backs:

    1. Materialized view logs, which are required for a fast refresh, are only supported in Enterprise Edition.
    2. Your table needs a primary key, although you probably already have one but just didn't include it in the example.
    3. Although declarative, the solution is still not straight-forward. You have to declare the opposite condition, and then check that it never exists.
    4. Getting FAST REFRESH to work can be a nightmare for more than the simplest of queries. Even for this simple example, I had to use the old-style joins and had to add useless ROWIDs.
    5. The constraint is not enforced until a COMMIT. Although that could be a positive thing, as many types of changes would temporarily create overlapping results. If you never allow overlapping results, you have to modify the table in a specific order.

提交回复
热议问题