Reputation: 826
I would like to add a CHECK
statement that calls a function when inserting new entries into a table.
I have used the following sample code to implement such functionality:
CREATE TABLE customers(
id NUMBER NOT NULL,
PRIMARY KEY(id));
CREATE OR REPLACE FUNCTION totalCustomers
RETURN NUMBER IS
total NUMBER := 0;
BEGIN
SELECT count(*) into total
FROM customers;
RETURN total;
END;
/
ALTER TABLE customers
ADD CHECK (totalCustomers() < 10);
When I run this query in livesql.oracle.com, I get the following error:
ORA-00904: "TOTALCUSTOMERS": invalid identifier
.
What is the right way of calling this function in the check statement?
P.S. Please ignore the contents of the function. I will replace it with the desired contents later.
Upvotes: 1
Views: 1095
Reputation: 59513
I would suggest a trigger for such requirement.
CREATE OR REPLACE TRIGGER AI_customers
AFTER INSERT ON customers
DECLARE
total NUMBER;
BEGIN
SELECT count(*) into total
FROM customers;
IF total > 10 THEN
RAISE_APPLICATION_ERROR(-20001, 'Total number of customers must not exceed 10');
END IF;
END;
/
Note, this is a STATEMENT LEVEL trigger (no FOR EACH ROW
clause), thus you cannot get the famous "ORA-04091: table is mutating, trigger/function may not see it" error.
However, this trigger has some limitations in a multi-user environment. If user_1 inserts records into customers table and user_2 also inserts some records (before user_1 has done a COMMIT) then you may get more than 10 records in your customers table.
Upvotes: 1
Reputation: 1529
In your comment on mathguy's answer, you say "I am also trying to make sure the time period of new entries don't overlap with existing ones." I have done this with "refresh fast on commit" materialized views. Warning: "fast" refreshes can be slow if you are not careful, please refer to this blog http://www.adellera.it/ , especially concerning statistics on the materialized view log.
I am assuming exclusive end dates. If an end date is null, that means the datetime range goes on indefinitely. Many overlaps will be caught immediately by the primary key and unique constraints. The others will be caught at commit time by the constraint on the materialized view. Note that at the end of the transaction the MV will never have any rows.
SQL> create table date_ranges (
2 key1, start_date,
3 primary key(key1, start_date),
4 end_date,
5 unique(key1, end_date),
6 check(start_date < end_date)
7 )
8 as
9 with a as (select date '2000-01-01' dte from dual)
10 select 1, dte, dte+1 from a
11 union all
12 select 1, dte+1, dte+2 from a
13 union all
14 select 1, dte-1, dte from a
15 union all
16 select 2, dte+10, dte+11 from a
17 union all
18 select 2, dte+12, dte+13 from a
19 union all
20 select 2, dte+8, dte+9 from a
21 /
Table DATE_RANGES created.
SQL> create materialized view log on date_ranges
2 with sequence, rowid, primary key, commit scn (end_date) including new values
3 /
Materialized view log DATE_RANGES created.
SQL> create materialized view overlapping_ranges refresh fast on commit
2 as
3 select a.rowid arid, b.rowid brid
4 from date_ranges a, date_ranges b
5 where a.key1 = b.key1
6 and a.rowid != b.rowid
7 and a.start_date < b.end_date
8 and a.end_date > b.start_date;
Materialized view OVERLAPPING_RANGES created.
SQL>
SQL> alter materialized view overlapping_ranges
2 add constraint overlaps_not_allowed check (1=0) deferrable initially deferred
3 /
Materialized view OVERLAPPING_RANGES altered.
SQL> insert into date_ranges select 1, date '1999-12-30', date '2000-01-4' from dual;
1 row inserted.
SQL> commit;
Error starting at line : 42 in command -
commit
Error report -
ORA-02091: transaction rolled back
ORA-02290: check constraint (STEW.OVERLAPS_NOT_ALLOWED) violated
Upvotes: 2
Reputation:
There isn't one.
Straight from the Oracle documentation:
Conditions of check constraints cannot contain the following constructs:
.............
- Calls to user-defined functions
..............
Now: you said "disregard the actual content of the function". That is not a healthy attitude; the content matters too. For one thing, the function would have to be deterministic anyway (yours is not) - this is a problem quite apart from it being a user-defined function. Moreover, conditions in constraints can only refer to values in a single row - they can't be "table" constraints, like yours is.
You may wonder, then - how would one implement a "constraint" like yours? One somewhat common method is to create a materialized view based on "select count(*)....." and put a constraint on the MV. The MV should refresh full on commit. Whenever you modify the base table and you commit. the MV is refreshed - and if the count increases above 10, the changes are rolled back.
Upvotes: 4