问题
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' );
回答1:
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_%).
回答2:
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:
- Materialized view logs, which are required for a fast refresh, are only supported in Enterprise Edition.
- Your table needs a primary key, although you probably already have one but just didn't include it in the example.
- Although declarative, the solution is still not straight-forward. You have to declare the opposite condition, and then check that it never exists.
- Getting
FAST REFRESHto 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. - 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.
来源:https://stackoverflow.com/questions/17682628/declarative-approach-to-constrain-data-ranges-in-table