I would like to ask a question about triggers on mutating table and I would appreciate if you could kindly give me a hand. As I understand, if a trigger is fired based on a DML statement on a given table, then this trigger cannot query(SELECT) or modify the same table (INSERT, UPDATE, MERGE). And according to oracle online documentation:
http://docs.oracle.com/cd/E11882_01/ser ... sthref1926
ORA-04091: table string.string 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.
While I was googling I found an interesting article on asktom
http://asktom.oracle.com/pls/asktom/ASK ... 9097816936
Apparently the methods suggested in this article, allow to deal with this problem. Well, I was interested by the topic so I decided to create my own test case and as an exercise apply the methods indicated in the document, in order to better understand and learn the topic. Here are the information about my test case:
- Code: Select all
SQL> SELECT * FROM v$version;
BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production
PL/SQL Release 11.2.0.1.0 - Production
CORE 11.2.0.1.0 Production
TNS for Linux: Version 11.2.0.1.0 - Production
NLSRTL Version 11.2.0.1.0 - Production
SQL>
OS: Fedora Core 17 (X86_64)
My testcase includes a pair of tables: constructors and articles in a stock
- Code: Select all
SET SQLBLANKLINES ON;
DROP TABLE myarticles;
DROP TABLE myconstructors;
-- Table representing article constructors with the following
-- column description:
--
-- consid Unique constructor id
-- conslab constructor label
--
CREATE TABLE myconstructors
(
consid VARCHAR2(30) NOT NULL,
conslab VARCHAR2(30) NOT NULL
);
ALTER TABLE myconstructors ADD CONSTRAINT PK_MYCONSTRUCTORS
PRIMARY KEY(consid);
-- Table representing articles with the following
-- column description:
--
-- artid Unique reference of an article
-- artlab Label of the article
-- consid constructor id (foreign key to myconstructors)
--
CREATE TABLE myarticles
(
artid VARCHAR2(30) NOT NULL,
artlab VARCHAR2(30) NOT NULL,
consid VARCHAR2(30) NOT NULL
);
ALTER TABLE myarticles ADD CONSTRAINT PK_MYARTICLES
PRIMARY KEY(artid);
ALTER TABLE myarticles ADD CONSTRAINT FK1_MYARTICLES
FOREIGN KEY(consid) REFERENCES myconstructors(consid)
ON DELETE CASCADE;
-- Values for the table myconstructors
INSERT ALL
INTO myconstructors(consid, conslab) VALUES('const-00001', 'Cisco')
INTO myconstructors(consid, conslab) VALUES('const-00002', 'IBM')
INTO myconstructors(consid, conslab) VALUES('const-00003', 'HP')
INTO myconstructors(consid, conslab) VALUES('const-00004', 'BULL')
SELECT * FROM DUAL;
-- Values for the table myarticles
INSERT ALL
INTO myarticles(artid, artlab, consid)
VALUES('artid-00001', 'Keyboard', 'const-00003')
INTO myarticles(artid, artlab, consid)
VALUES('artid-00002', 'Mouse', 'const-00003')
INTO myarticles(artid, artlab, consid)
VALUES('artid-00003', 'Monitor', 'const-00003')
INTO myarticles(artid, artlab, consid)
VALUES('artid-00004', 'router', 'const-00001')
INTO myarticles(artid, artlab, consid)
VALUES('artid-00005', 'switch', 'const-00001')
SELECT * FROM DUAL;
SELECT * FROM myconstructors;
SELECT * FROM myarticles;
CONSID CONSLAB
------------------------------ ------------------------------
const-00001 Cisco
const-00002 IBM
const-00003 HP
const-00004 BULL
ARTID ARTLAB CONSID
------------------ ------------------- -------------------------
artid-00001 Keyboard const-00003
artid-00002 Mouse const-00003
artid-00003 Monitor const-00003
artid-00004 router const-00001
artid-00005 switch const-00001
Now imagine that we want deliberately to create a wrong trigger on the articles (mutating) table, in order to get error and then use the methods indicated in the asktom article to deal with it.
Let's say, we want to make sure that there is at most three articles from each constructor in the stock. For example the
following insert must raise an exception
- Code: Select all
INSERT INTO myarticles(artid, artlab, consid) VALUES('artid-00006', 'switch', 'const-00003');
Because, there already three articles built by the constructor 'const-00003'(HP) in the articles table.
Here is how I defined the trigger
- Code: Select all
CREATE OR REPLACE PACKAGE exceptions_pkg
AS
MAX_CONSTRCUTOR_LIMIT_REACHED EXCEPTION;
END exceptions_pkg;
/
CREATE OR REPLACE TRIGGER tr_articles
BEFORE INSERT ON myarticles
FOR EACH ROW
DECLARE
l_count PLS_INTEGER := 0;
BEGIN
-- So here we check to see that
-- how many articles which was built
-- by the constructor :NEW.consid
-- are already in the stock (myarticles table)
SELECT COUNT(*) INTO l_count
FROM myarticles
WHERE consid = :NEW.consid;
IF l_count >= 3
THEN
RAISE exceptions_pkg.MAX_CONSTRCUTOR_LIMIT_REACHED;
END IF;
END;
/
Now the problem is that oracle accepted this trigger and I didn't get any oracle error message complaining that the trigger was acting upon a mutating table. Whereas this is actually how it works, for each INSERT statement for each row, the trigger is fired and it tries to do a SELECT (SELECT COUNT(*) INTO l_count) on the very same table.
Why I don't get any error message? Isn't that a mutating table?
Thanks in advance,
Kind regards,
Dariyoosh