Is it possible to have a "deferred check constraint" in Oracle? - oracle

I was thinking that I'd like to have a "deferred check constraint" in Postgres, but that is apparently not supported at this time (Postgres 9.3)
Then I saw that Oracle seems to broadly have "deferred" for its constraints, documented here. Therefore, is it true that Oracle 10g+ supports having a "deferred check constraint"?
I might have missed further documentation to the contrary, so I figured to ask here as a double-check, trusting that there are people who actively use Oracle who would know the answer - thus avoiding trial-and-error, wasted hours messing around with Oracle servers.

Yes, though I'm not sure why you'd want to:
create table t42 (id number,
constraint check_id check (id > 0) initially deferred deferrable);
table T42 created.
insert into t42 (id) values (-1);
1 rows inserted.
commit;
Error report -
SQL Error: ORA-02091: transaction rolled back
ORA-02290: check constraint (STACKOVERFLOW.CHECK_ID) violated
02091. 00000 - "transaction rolled back"
*Cause: Also see error 2092. If the transaction is aborted at a remote
site then you will only see 2091; if aborted at host then you will
see 2092 and 2091.
*Action: Add rollback segment and retry the transaction.
You can update it before committing of course:
insert into t42 (id) values (-1);
1 rows inserted.
update t42 set id = 1 where id = -1;
1 rows updated.
commit;
committed.
... but I'm not sure why you would put the invalid value in the table in the first place if you planned to update it. Presumably there is some scenario where this is useful.
More on constraint deferral in the documentation.

Yes, you can define Constraint as
"DEFERRABLE" or "NOT DEFERRABLE"
and then
"INITIALLY DEFERRED" or "INITIALLY IMMEDIATE"
For example:
ALTER TABLE T
ADD CONSTRAINT ck_t CHECK (COL_1 > 0)
DEFERRABLE INITIALLY DEFERRED;
Check Oracle documentation for details...

Related

ORA-00001: unique constraint violated error in oracle jdbc

I have this 2 jdbc statement (Oracle 10g with isolation level read committed)
delete from emp where emp_id = 1;
insert into emp (emp_id,address,.....) value (1,'newyork',.....);
emp_id,address is the primary key
Unfortunately in a multithreaded environment I get ORA-00001: unique constraint violated while inserting the record.
When I run in a single thread I don't see any issue.
After investigation I found that
first session deletes and then inserts a record but is not yet committed.
second session deletes it
first session now commits it
second session now tries to insert it and throws the error .
Let me know if I am missing anything.
How to solve this problem in oracle.I don't like the solution of single threaded ,I also don't like the solution of retry.
Any clean solution?
Make sure you have enabled AUTOCOMMIT in your program, then check whether the row exists or not before inserting it to prevent ORA-00001. Here's the PL/SQL block.
declare
v_rows_count number;
begin
select count(*) into v_rows_count from emp where emp_id = 1;
if v_rows_count = 0 then
-- Insert the row
else
-- Do not insert the row
end if;
end;
/
Don't forget to commit the change later.

Trigger cant read the table, after being fired by the same table

Lets say I have a table as follows--
create table employees
(
eno number(4) not null primary key,
ename varchar2(30),
zip number(5) references zipcodes,
hdate date
);
I've created a trigger using the following code block
create or replace TRIGGER COPY_LAST_ONO
AFTER INSERT ON ORDERS
FOR EACH ROW
DECLARE
ID_FROM_ORDER_TABLE VARCHAR2(10);
BEGIN
SELECT MAX(ORDERS.ONO)INTO ID_FROM_ORDER_TABLE from ORDERS ;
DBMS_OUTPUT.PUT_LINE(ID_FROM_ORDER_TABLE);
INSERT INTO BACKUP_ONO VALUES( VALUE1, VALUE2,VALUE3, ID_FROM_ORDER_TABLE);
END;
The trigger fires after insertion and attempts to read from the table that fired it(logically duhh!) but oracle is giving me an error and asking me to modify the trigger so that it doesnt read the table. Error code-
Error report -
SQL Error: ORA-04091: table TEST1.ORDERS is mutating, trigger/function may not see it
ORA-06512: at "TEST1.COPY_LAST_ONO", line 8
ORA-04088: error during execution of trigger 'TEST1.LOG_INSERT'
04091. 00000 - "table %s.%s is mutating, trigger/function may not see it"
*Cause: A trigger (or a user defined plsql function that is referenced in
this statement) attempted to look at (or modify) a table that was
in the middle of being modified by the statement which fired it.
*Action: Rewrite the trigger (or function) so it does not read that table.
What I'm trying to achieve with this trigger is to copy the last INSERTED ONO (which is a primary key for the ORDER table) immediately to a different table after being INSERTED. What I don't get is, why oracle complaining? The trigger is attempting to read AFTER the insertion!
Ideas? Solution?
MANY THANKS
If you are trying to log the ONO you just inserted, use :new.ono and skip the select altogether:
INSERT INTO BACKUP_ONO VALUES( VALUE1, VALUE2,VALUE3, :new.ono);
I don't believe you can select from the table you are in the middle of inserting into as the commit has not been issued yet, hence the mutating table error.
P.S. Consider not abbreviating. Make it clear for the next developer and call it ORDER_NUMBER or at least a generally accepted abbreviation like ORDER_NBR, whatever your company's naming standards are. :-)
FYI - If you are updating, you can access :OLD.column as well, the value before the update (of course if the column is not a primary key column).
Amplifying #Gary_W's answer:
Oracle does not allow a row trigger (one with FOR EACH ROW in it) to access the table on which the trigger is defined in any way - you can't issue a SELECT, INSERT, UPDATE, or DELETE against that table from within the trigger or anything it calls (so, no, you can't dodge around this by calling a stored procedure which does the dirty work for you - but good thinking! :-). My understanding is that this is done to prevent what you might call a "trigger loop" - that is, the triggering condition is satisfied and the trigger's PL/SQL block is executed; that block then does something which causes the trigger to be fired again; the trigger's PL/SQL block is invoked; the trigger's code modifies another row; etc, ad infinitum. Generally, this should be taken as a warning that your logic is either really ugly, or you're implementing it in the wrong place. (See here for info on the evil of business logic in triggers). If you find that you really seriously need to do this (I've worked with Oracle and other databases for years - I've really had to do it once - and may Cthulhu have mercy upon my soul :-) you can use a compound trigger which allows you to work around these issues - but seriously, if you're in a hole like this your best option is to re-work the data so you don't have to do this.
Best of luck.
Modify your trigger to use PRAGMA AUTONOMOUS_TRANSACTION
create or replace TRIGGER COPY_LAST_ONO
AFTER INSERT ON ORDERS
FOR EACH ROW
DECLARE
ID_FROM_ORDER_TABLE VARCHAR2(10);
PRAGMA AUTONOMOUS_TRANSACTION; -- Modification
BEGIN
.
.
.

how to make a trigger like primary key constraint?

i need to define a trigger which i want to apply on a column of table. The trigger should restrict the user to input duplicate and not null values. Or you can say, i need to know the logic of primary key.
Just because you seem intent on seeing this fail, and not to take anything away from APC's points, this appears to work at first glance as long as it's a before trigger:
create table t42 (id number);
create trigger trig42
before insert or update on t42
for each row
declare
c number;
begin
if :new.id is null then
raise_application_error(-20001, 'ID is null');
end if;
select count(*) into c from t42 where id = :new.id;
if c > 0 then
raise_application_error(-20002, 'ID is not unique');
end if;
end;
/
It compiles and if you insert data you get the behaviour you seem to want:
insert into t42 values (1);
1 rows inserted.
insert into t42 values (1);
Error starting at line 20 in command:
insert into t42 values (1)
Error report:
SQL Error: ORA-20002: ID is not unique
ORA-06512: at "STACKOVERFLOW.TRIG42", line 9
ORA-04088: error during execution of trigger 'STACKOVERFLOW.TRIG42'
insert into t42 values (null);
Error starting at line 22 in command:
insert into t42 values (null)
Error report:
SQL Error: ORA-20001: ID is null
ORA-06512: at "STACKOVERFLOW.TRIG42", line 5
ORA-04088: error during execution of trigger 'STACKOVERFLOW.TRIG42'
select * from t42;
ID
----------
1
Which seems to do what you want. But not if you have more than one session. I haven't committed in this session; in another session I can do:
insert into t42 values (1);
1 row created.
select * from t42;
ID
----------
1
1 row selected.
Hmm, that's strange. Well, maybe it's deferred... let's commit them both:
commit;
select * from t42;
ID
----------
1
1
2 rows selected.
Oops. Once session can't see another session's uncommitted data, so this will never work.
Also, the mutating table problem exhibits itself when we insert multiple rows in a single statement:
SQL> insert into t42 select level+1 from dual connect by level <= 5;
insert into t42 select level+1 from dual connect by level <= 5
*
ERROR at line 1:
ORA-04091: table STACKOVERFLOW.T42 is mutating, trigger/function may not see it
ORA-06512: at "STACKOVERFLOW.TRIG42", line 7
ORA-04088: error during execution of trigger 'STACKOVERFLOW.TRIG42'
SQL>
Double oops.
Even with an after trigger and a package to work around the mutating table issue, you'd still have this problem (I think), unless you lock the whole table for every insert or update. As APC said the constraint is implemented deep in the bowels of the database, not at this level.
is it not possible to define a trigger, which checks the value before
insertion that it should not be null and unique as well?
Not when you have more than one session, no. And even within one session, unless you have an index on the column the performance won't scale as the count(*) will get progressively slower. And if you do have an index, well, why not make it a unique index in the first place?
Finally, from the trigger design guidelines:
Do not create triggers that duplicate database features.
For example, do not create a trigger to reject invalid data if you can
do the same with constraints (see "How Triggers and Constraints
Differ").
" i want to learn, how primary key is made(it is a trigger of course)"
There is no "of course" about it. A constraint is not a trigger. It is an internal process which uses an index and a lot of low level activity to enforce relational constraints in a reliable and efficient manner.
If you want to learn the rules are quite straightforward: not null, uniqueness, serialization. So just try to implement a primary key in triggers. You'll find you can't (spoiler alert!) because of the "mutating table" problem. And if you don't understand what that means, well there's a good topic to read about.
there is a question "is it not possible to define a trigger, which
checks the value before insertion that it should not be null and
unique as well? "
The answer to that question is, No. Well, you could code a trigger-based implementation but like other "mutating table" workarounds it would require a package and AFTER statement triggers (so technically not before insertion).
But seriously, what would be the point? You won't learn anything about how primary keys actually work. And mutating tables almost always point to a poor data model, and that would certainly be the case here.
Primary key is not a trigger. It is a key, because it identifies the whole row, that's why it should be unique (and implicitly not null). It is "primary", because it is the candidate key that is most appropriate - by your decision - to be the main reference key for your table. You can add it as ALTER TABLE your_table_name ADD CONSTRAINT PK_your_table_name PRIMARY KEY (your_key_column).
If you do not want to add a primary key like that (which is a bad idea), but want to add a unique index to that table: CREATE UNIQUE INDEX UQ_IX_your_table_your_column ON your_table_name (unique_column_name).
The NOT NULL constraint should be put on the column.

How can i ignore dupkey with sqlplus?

I want to write a sql plus error for when the oracle find a record, or more records, that already exist and just ignore it/them. This is a example:
sqlError=`egrep "ORA-[0-9][0-9][0-9][0-9][0-9]" ${FILE_SPOOL_DAT} | awk '{print $0}';`
if test ! -f ${FILE_SPOOL_DAT}
then
echo "Error request " >> ${FILE_SPOOL_DAT}
else
if [ ! "$sqlError" = "" ] #controls if the variable $sqlError contains a value different from spaces, i think this is the point to change
then
echo "Error $sqlError" >> ${FILE_SPOOL_DAT}
fi
fi
In this example sqlplus controls if the variable $sqlError contains a value different from spaces. How can i change this condition put the DUPKEY error? Thanks
If you're using 11g, the IGNORE_ROW_ON_DUPKEY_INDEX hint can help.
SQL> create table table1(a number primary key);
Table created.
SQL> insert into table1 values(1);
1 row created.
SQL> insert into table1 values(1);
insert into table1 values(1)
*
ERROR at line 1:
ORA-00001: unique constraint (JHELLER.SYS_C00810741) violated
SQL> insert /*+ IGNORE_ROW_ON_DUPKEY_INDEX(table1(a))*/ into table1 values(1);
0 rows created.
Yikes, that's dangerous! If Oracle finds one or more records that already exists, it will normally rollback the transaction. Suppressing the relevant error messages is way to late.
A much better place is to instruct Oracle to ignore duplicate records directly during the INSERT, for instance by using the MERGE command:
http://docs.oracle.com/cd/E11882_01/server.112/e26088/statements_9016.htm#i2081218
When you get a duplicate key error it really means you have violated a constraint set on a table.
Assuming whoever designed the table understood the data model, what you want to do is a BAD idea.
If it is a bad design, change the table's metadata by removing the constraint.
The MERGE command may let you work around it, which I doubt, but with the subsequent data mess left behind I do not believe it is worth the risk.
If you are just playing, remove the constraint anyway. I don't know how your table is set up,
but you will probably have to ALTER or DROP and CREATE an index, or ALTER the table.
If this is development for production and thus paid work, don't try to get around the constraint without talking to the person(s) who designed the table to start with.

ORA-04091: table [blah] is mutating, trigger/function may not see it

I recently started working on a large complex application, and I've just been assigned a bug due to this error:
ORA-04091: table SCMA.TBL1 is mutating, trigger/function may not see it
ORA-06512: at "SCMA.TRG_T1_TBL1_COL1", line 4
ORA-04088: error during execution of trigger 'SCMA.TRG_T1_TBL1_COL1'
The trigger in question looks like
create or replace TRIGGER TRG_T1_TBL1_COL1
BEFORE INSERT OR UPDATE OF t1_appnt_evnt_id ON TBL1
FOR EACH ROW
WHEN (NEW.t1_prnt_t1_pk is not null)
DECLARE
v_reassign_count number(20);
BEGIN
select count(t1_pk) INTO v_reassign_count from TBL1
where t1_appnt_evnt_id=:new.t1_appnt_evnt_id and t1_prnt_t1_pk is not null;
IF (v_reassign_count > 0) THEN
RAISE_APPLICATION_ERROR(-20013, 'Multiple reassignments not allowed');
END IF;
END;
The table has a primary key "t1_pk", an "appointment event id"
t1_appnt_evnt_id and another column "t1_prnt_t1_pk" which may or may
not contain another row's t1_pk.
It appears the trigger is trying to make sure that nobody else with the
same t1_appnt_evnt_id has referred to the same one this row is referring to a referral to another row, if this one is referring to another row.
The comment on the bug report from the DBA says "remove the trigger, and perform the check in the code", but unfortunately they have a proprietary code generation framework layered on top of Hibernate, so I can't even figure out where it actually gets written out, so I'm hoping that there is a way to make this trigger work. Is there?
I think I disagree with your description of what the trigger is trying to
do. It looks to me like it is meant to enforce this business rule: For a
given value of t1_appnt_event, only one row can have a non-NULL value of
t1_prnt_t1_pk at a time. (It doesn't matter if they have the same value in the second column or not.)
Interestingly, it is defined for UPDATE OF t1_appnt_event but not for the other column, so I think someone could break the rule by updating the second column, unless there is a separate trigger for that column.
There might be a way you could create a function-based index that enforces this rule so you can get rid of the trigger entirely. I came up with one way but it requires some assumptions:
The table has a numeric primary key
The primary key and the t1_prnt_t1_pk are both always positive numbers
If these assumptions are true, you could create a function like this:
dev> create or replace function f( a number, b number ) return number deterministic as
2 begin
3 if a is null then return 0-b; else return a; end if;
4 end;
and an index like this:
CREATE UNIQUE INDEX my_index ON my_table
( t1_appnt_event, f( t1_prnt_t1_pk, primary_key_column) );
So rows where the PMNT column is NULL would appear in the index with the inverse of the primary key as the second value, so they would never conflict with each other. Rows where it is not NULL would use the actual (positive) value of the column. The only way you could get a constraint violation would be if two rows had the same non-NULL values in both columns.
This is perhaps overly "clever", but it might help you get around your problem.
Update from Paul Tomblin: I went with the update to the original idea that igor put in the comments:
CREATE UNIQUE INDEX cappec_ccip_uniq_idx
ON tbl1 (t1_appnt_event,
CASE WHEN t1_prnt_t1_pk IS NOT NULL THEN 1 ELSE t1_pk END);
I agree with Dave that the desired result probalby can and should be achieved using built-in constraints such as unique indexes (or unique constraints).
If you really need to get around the mutating table error, the usual way to do it is to create a package which contains a package-scoped variable that is a table of something that can be used to identify the changed rows (I think ROWID is possible, otherwise you have to use the PK, I don't use Oracle currently so I can't test it). The FOR EACH ROW trigger then fills in this variable with all rows that are modified by the statement, and then there is an AFTER each statement trigger that reads the rows and validate them.
Something like (syntax is probably wrong, I haven't worked with Oracle for a few years)
CREATE OR REPLACE PACKAGE trigger_pkg;
PROCEDURE before_stmt_trigger;
PROCEDURE for_each_row_trigger(row IN ROWID);
PROCEDURE after_stmt_trigger;
END trigger_pkg;
CREATE OR REPLACE PACKAGE BODY trigger_pkg AS
TYPE rowid_tbl IS TABLE OF(ROWID);
modified_rows rowid_tbl;
PROCEDURE before_stmt_trigger IS
BEGIN
modified_rows := rowid_tbl();
END before_each_stmt_trigger;
PROCEDURE for_each_row_trigger(row IN ROWID) IS
BEGIN
modified_rows(modified_rows.COUNT) = row;
END for_each_row_trigger;
PROCEDURE after_stmt_trigger IS
BEGIN
FOR i IN 1 .. modified_rows.COUNT LOOP
SELECT ... INTO ... FROM the_table WHERE rowid = modified_rows(i);
-- do whatever you want to
END LOOP;
END after_each_stmt_trigger;
END trigger_pkg;
CREATE OR REPLACE TRIGGER before_stmt_trigger BEFORE INSERT OR UPDATE ON mytable AS
BEGIN
trigger_pkg.before_stmt_trigger;
END;
CREATE OR REPLACE TRIGGER after_stmt_trigger AFTER INSERT OR UPDATE ON mytable AS
BEGIN
trigger_pkg.after_stmt_trigger;
END;
CREATE OR REPLACE TRIGGER for_each_row_trigger
BEFORE INSERT OR UPDATE ON mytable
WHEN (new.mycolumn IS NOT NULL) AS
BEGIN
trigger_pkg.for_each_row_trigger(:new.rowid);
END;
With any trigger-based (or application code-based) solution you need to
put in locking to prevent data corruption in a multi-user environment.
Even if your trigger worked, or was re-written to avoid the mutating table
issue, it would not prevent 2 users from simultaneously updating
t1_appnt_evnt_id to the same value on rows where t1_appnt_evnt_id is not
null: assume there are currenly no rows where t1_appnt_evnt_id=123 and
t1_prnt_t1_pk is not null:
Session 1> update tbl1
set t1_appnt_evnt_id=123
where t1_prnt_t1_pk =456;
/* OK, trigger sees count of 0 */
Session 2> update tbl1
set t1_appnt_evnt_id=123
where t1_prnt_t1_pk =789;
/* OK, trigger sees count of 0 because
session 1 hasn't committed yet */
Session 1> commit;
Session 2> commit;
You now have a corrupted database!
The way to avoid this (in trigger or application code) would be to lock
the parent row in the table referenced by t1_appnt_evnt_id=123 before performing the check:
select appe_id
into v_app_id
from parent_table
where appe_id = :new.t1_appnt_evnt_id
for update;
Now session 2's trigger must wait for session 1 to commit or rollback before it performs the check.
It would be much simpler and safer to implement Dave Costa's index!
Finally, I'm glad no one has suggested adding PRAGMA AUTONOMOUS_TRANSACTION to your trigger: this is often suggested on forums and works in as much as the mutating table issue goes away - but it makes the data integrity problem even worse! So just don't...
I had similar error with Hibernate. And flushing session by using
getHibernateTemplate().saveOrUpdate(o);
getHibernateTemplate().flush();
solved this problem for me. (I'm not posting my code block as I was sure that everything was written properly and should work - but it did not until I added the previous flush() statement). Maybe this can help someone.

Resources