Declarative approach to constrain data ranges in table

前端 未结 2 732
终归单人心
终归单人心 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:21

    While waiting for next Oracle 12c version, which supports Temporal Validity, I still use next approach:

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

    Let's 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' )
    

    But now updating and deleting dates in the middle of the chain is pain in the ass. It needs to change preceding and following rows in one statement with MERGE. That's why I've added price_id column, because you can't update a key in MERGE -- so, you need another key instead of (item, effective_%).

    0 讨论(0)
  • 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.
    0 讨论(0)
提交回复
热议问题