how to use triggers to inherit values from another "parent" record in same table - oracle

I feel like this is one of those "if you're careful you can do it" scenarios that Oracle just doesn't want to let me do.
My problem is that I have a single configuration table that I want to enable inheritance via Triggers. Think an Employee table with a SUPERVISOR ID column, and 'inherited' SUPERVISOR NAME that self populates if the ID is changed.
I'd like to do a simple self-lookup to capture a value from another row at time of INS/UPD. But Oracle is rejecting as a mutating trigger error.
My code is essentially:
TRIGGER UPD_CHILD_RECORD
BEFORE INSERT OR UPDATE
ON MYSCHEMA.FAKE_EMPLOYEE
FOR EACH ROW
WHEN (NEW.SUPERVISOR_ID IS NOT NULL)
BEGIN
IF INSERTING OR UPDATING
THEN
:NEW.SUPERVISOR_NAME = (
SELECT MAX(NAME)
FROM MYSCHEMA.FAKE_EMPLOYEE
WHERE EMPLOYEE_ID = :NEW.SUPERVISOR_ID
);
END IF;
END UPD_CHILD_RECORD
;
thanks.

This is a normal behavior. Oracle protects you from inconsistent data that you may get accessing a table which is already being updated.
Imagine this scenario.
You submit two update statements and have a trigger that selects from that same table. Let's assume that the first statement is successfully applied and the data gets changed. Now it's time for the second statement. What output would you expect from the select statement in the trigger? Should it return data as it was before the first update, or should it include the changes made? You probably think that Oracle should return the new data. But first, Oracle does not really know your intentions, and second, that would mean that your query is dependent on row order, which contradicts the relational algebra.
The solution for your problem is quite simple. You do not need the SUPERVISOR_NAME column at all. To get supervisor's name, simply join the table with itself and get the desired result, something like:
select t1.ID, t1.SUPERVISOR_ID, t2.NAME from FAKE_EMPLOYEE t1
left join FAKE_EMPLOYEE t2 on t1.SUPERVISOR_ID = t2.ID;

Related

How to insert while avoiding unique constraints with oracle

We have a process that aggregates some data and inserts the results into another table that we use for efficient querying. The problem we're facing is that we now have multiple aggregators running at roughly the same time.
We use the original records id as the primary key in this new table - a unique constraint. However, if two aggregation processes are running at the same time, one of them will error with a unique constraint violation.
Is there a way to specify some kind of locking mechanism which will make the second writer wait until the first is finished? Alternatively, is there a way to tell oracle to ignore that specific row and continue with the rest?
Unfortunately it's not practical to reduce the aggregation to a single process, as the following procedures rely on an up to date version of the data being available and those procedures do need to scale out.
Edit:
The following is my [redacted] query:
INSERT INTO
agg_table
SELECT
h.id, h.col, h.col2
FROM history h
JOIN call c
ON c.callid = h.callid
WHERE
h.id > (SELECT coalesce(max(id),0) FROM agg_table)
It is possible run an INSERT statement with an error logging clause. The example from the Oracle docs is as follows:
INSERT INTO dw_empl
SELECT employee_id, first_name, last_name, hire_date, salary, department_id
FROM employees
WHERE hire_date > sysdate - 7
LOG ERRORS INTO err_empl ('daily_load') REJECT LIMIT 25
Alternatively, you could try using a [MERGE][2] statement. You would be merging into the summary table with a select from the detail table. If a match is not found, you INSERT and if it is found you would UPDATE. I believe this solution will handle your concurrency issues, but you would need to test it.
have a look at FOR UPDATE clause. If you correctly write the SELECT statement with FOR UPDATE clause within a transaction before your update/insert statements you will be able to "lock" the required records
Serialising the inserts is probably the best way, as there's no method that will get you round the problem of the multiple inserts being unable to see what each one is doing.
DBMS_Lock is probably the appropriate serialisation mechanism.

trigger insert and update oracle error

Friend, I have question about cascade trigger.
I have 2 tables, table data that has 3 attributes (id_data, sum, and id_tool), and table tool that has 3 attributes (id_tool, name, sum_total). table data and tool are joined using id_tool.
I want create trigger for update info sum_total. So , if I inserting on table data, sum_total on table tool where tool.id_tool = data.id_tool will updating too.
I create this trigger, but error ora-04090.
create or replace trigger aft_ins_tool
after insert on data
for each row
declare
v_stok number;
v_jum number;
begin
select sum into v_jum
from data
where id_data= :new.id_data;
select sum_total into v_stok
from tool
where id_tool=
(select id_tool
from data
where id_data= :new.id_data);
if inserting then
v_stok := v_stok + v_jum;
update tool
set sum_total=v_stok
where id_tool=
(select id_tool
from data
where id_data= :new.id_data);
end if;
end;
/
please give me opinion.
Thanks.
The ora-04090 indicates that you already have an AFTER INSERT ... FOR EACH ROW trigger on that table. Oracle doesn't like that, because the order in which the triggers fire is unpredictable, which may lead to unpredictable results, and Oracle really doesn't like those.
So, your first step is to merge the two sets of code into a single trigger. Then the real fun begins.
Presumably there is only one row in data matching the current value of id_data (if not your data model is rally messed up and there's no hope for your situation). Anyway, that means the current row already gives you access to the values of :new.sum and :new.id_tool. So you don't need those queries on the data table: removing those selects will remove the possibility of "mutating table" errors.
As a general observation, maintaining aggregate or summary tables like this is generally a bad idea. Usually it is better just to query the information when it is needed. If you really have huge volumes of data then you should use a materialized view to maintain the summary, rather than hand-rolling something.

ORACLE Mutating Table error in one trigger, but not another; Why?

Okay, I have two tables - ORDERS and ORDERLINES - which have essentially the same problem, with triggers on each to address the issue. The issue is that in addition to the PK with table-level uniqueness, on a field called RECID, there is another field, RECNO, which needs to be unique with relation to another field.
The tables are FK related as follows:
ORDERS.WAREHOUSEID > WAREHOUSES.CUSTOMERID > CUSTOMERS
and
ORDERSLINES.ORDERID > ORDERS
On ORDERS and ORDERSLINES I have BEFORE INSERT triggers to assign the realm-specific unique RECNO.
In ORDERS, RECNO needs to be unique within the realm of a CUSTOMERS record.
In ORDERLINES, RECNO needs to be unique within the realm of an ORDERS record.
The trigger on ORDERS works perfectly fine. When a new order is inserted, it is assigned the next unique RECNO within the customer it belongs to.
The trigger on ORDERLINES on the other hand, which should assign the next unique RECNO within the order it belongs to, throws the dreaded {ORA-04091: table ORDERLINES is mutating, trigger/function may not see it} exception.
Here is the trigger that works:
CREATE OR REPLACE TRIGGER ORDERS_BI
BEFORE INSERT ON ORDERS
FOR EACH ROW
DECLARE
CUSTID WAREHOUSES.CUSTOMERID%TYPE;
BEGIN
SELECT MIN(CUSTOMERID) INTO CUSTID FROM WAREHOUSES
WHERE NVL(WARE_ID, '-') = NVL(:NEW.WAREHOUSEID, '-');
SELECT NVL(MAX(RECNO), 0) + 1
INTO :NEW.RECNO
FROM deploy.ORDERS O
LEFT JOIN deploy.WAREHOUSES W
ON NVL(W.REC, '-') = NVL(O.WAREHOUSEID, '-')
WHERE NVL(W.CUSTOMERID, '-') = NVL(CUSTID, '-');
END;
And here is the trigger that does NOT work:
CREATE OR REPLACE TRIGGER ORDERLINES_BI
BEFORE INSERT ON ORDERLINES
FOR EACH ROW
DECLARE
nORDERID ORDERLINES.ORDERID%TYPE;
BEGIN
SELECT MIN(ORDERID) INTO nORDERID FROM REVORDERS
WHERE ORDERID = :NEW.ORDERID;
SELECT NVL(MAX(RECNO), 0) + 1
INTO :NEW.RECNO
FROM deploy.ORDERLINES L
LEFT JOIN deploy.ORDERS O
ON O.ORDERID = L.ORDERID
WHERE O.ORDERID = nORDERID;
END;
Can SOMEONE please explain WHY the first one works, and the second one doesn't?
And is there some way I can re-write the second to make it work?
I looked at your code first, rather than your explanation. My first thought was "this person is trying to fake a sequence." This obviously isn't the answer to your question but it's the reason you're getting into trouble in the first place.
The obvious solution when you're having problems faking sequences is to use a real one.
As Nicholas has already noted ORA-04091 occurs when you try to read from the table from which a trigger is fired. There are various ways to avoid this, most of which avoid trying to do something slightly funky. However, they don't influence the root cause of the error; that is you're doing something wrong. This error is normally indicative of one or both of two things:
You're putting far too much logic into a trigger
Your data-model is flawed.
The solution to the first is to move the logic to a package, which has the added benefit of removing a layer of obfuscation. The solution to the second is to normalise your database properly.
In your case, from what information you've provided, your data-model seems to be okay, though as I've said I disagree with the implementation.
This leaves you with four options to solve your problem, which I detail in order I would do them
Remove your triggers.
Replace your current logic with a sequence.
Remove all your trigger logic into a procedure.
Hack around your error.
I'm not going to discuss point 3 as you can do that yourself. Nicholas has partially covered point 4 and I'm not going to advocate something I disagree with. This leaves points 1 and 2. You say
In ORDERS, RECNO needs to be unique within the realm of a CUSTOMERS
record.
This is not how you've implemented it. Your code makes RECNO consecutive within the realm of a CUSTOMERS record. The primary key of both ORDERS and ORDERLINES are by definition unique within the realm of a CUSTOMERS record.
In itself, this implies that option 1 is best for you. Remove the triggers entirely; the primary keys of the table are already doing everything you need. This also invalidates option 2; if you add a sequence then it will basically be a separate primary key.
There is no reason I can think of that you would need an order to be consecutively unique within each customer; why bother doing so?
You are getting that error because the second trigger is trying to read table while it is being modified. This can also happen when a trigger on a parent table causes an insert on a child table referencing a foreign key.
As a quick work around create view and try to use instead of trigger.
Also take a look at Tom's example of how to deal with mutating issues.
Besides, if leave the second trigger as it is, any inserts into your_table select .. from table will raise mutating error. For example:
This insert will work
insert into ORDERLINES(column1, column2... columnN)
values(val1, val2,..., valN)
But this one wont.
insert into ORDERLINES(column1, column2... columnN)
select val, val..val from table

Oracle: complex constraint involving other records

Is there any way to make oracle checks other records of a table by a constraint?
Let's take an example:
I've got a table called ENI_TRASC_VOCI_PWR_FATT and I want that every record having tvp_regione not null has a similar record having tvp_regione = null.
For similar record I need to check it has the same value on the TVP_CODICE_ASSOGGETAMEN colum.
The only method I can think of is to use a fast commit on refresh materialised view, defined with a query something like:
select
tvp_codice_assoggetamen,
count(*) rows_per_tca,
count(tvp_regione) tvp_regione_per_tca
from
eni_trasc_voci_pwr_fatt
group by
tvp_codice_assoggetamen
/
Place regular check constraints on the MV table, such that tvp_regione_per_tca = 1 if rows_per_tca = 2 (your requirement is not quite clear to me).
That is generally the only safe way of implementing such a multi-row constraint in Oracle, short of locking the table for changes before modifying it and using code to check.
According to Oracle docs :
Conditions of check constraints cannot contain the following
constructs:
Subqueries and scalar subquery expressions
So You'll probably have to use a trigger instead:
create or replace trigger trg
before insert or update on ENI_TRASC_VOCI_PWR_FATT
for each row
begin
-- do whatever queries you need - I didn't understand what you want
if <some condition> then
raise_application_error(-20000,'no good');
end if;
end;
But be carefull ! a trigger is not like a constraint - think what will happen if two users update the table and so on ....

ORA-30926: unable to get a stable set of rows in the source tables

I am getting
ORA-30926: unable to get a stable set of rows in the source tables
in the following query:
MERGE INTO table_1 a
USING
(SELECT a.ROWID row_id, 'Y'
FROM table_1 a ,table_2 b ,table_3 c
WHERE a.mbr = c.mbr
AND b.head = c.head
AND b.type_of_action <> '6') src
ON ( a.ROWID = src.row_id )
WHEN MATCHED THEN UPDATE SET in_correct = 'Y';
I've ran table_1 it has data and also I've ran the inside query (src) which also has data.
Why would this error come and how can it be resolved?
This is usually caused by duplicates in the query specified in USING clause. This probably means that TABLE_A is a parent table and the same ROWID is returned several times.
You could quickly solve the problem by using a DISTINCT in your query (in fact, if 'Y' is a constant value you don't even need to put it in the query).
Assuming your query is correct (don't know your tables) you could do something like this:
MERGE INTO table_1 a
USING
(SELECT distinct ta.ROWID row_id
FROM table_1 a ,table_2 b ,table_3 c
WHERE a.mbr = c.mbr
AND b.head = c.head
AND b.type_of_action <> '6') src
ON ( a.ROWID = src.row_id )
WHEN MATCHED THEN UPDATE SET in_correct = 'Y';
You're probably trying to to update the same row of the target table multiple times. I just encountered the very same problem in a merge statement I developed. Make sure your update does not touch the same record more than once in the execution of the merge.
A further clarification to the use of DISTINCT to resolve error ORA-30926 in the general case:
You need to ensure that the set of data specified by the USING() clause has no duplicate values of the join columns, i.e. the columns in the ON() clause.
In OP's example where the USING clause only selects a key, it was sufficient to add DISTINCT to the USING clause. However, in the general case the USING clause may select a combination of key columns to match on and attribute columns to be used in the UPDATE ... SET clause. Therefore in the general case, adding DISTINCT to the USING clause will still allow different update rows for the same keys, in which case you will still get the ORA-30926 error.
This is an elaboration of DCookie's answer and point 3.1 in Tagar's answer, which from my experience may not be immediately obvious.
How to Troubleshoot ORA-30926 Errors? (Doc ID 471956.1)
1) Identify the failing statement
alter session set events ‘30926 trace name errorstack level 3’;
or
alter system set events ‘30926 trace name errorstack off’;
and watch for .trc files in UDUMP when it occurs.
2) Having found the SQL statement, check if it is correct (perhaps using explain plan or tkprof to check the query execution plan) and analyze or compute statistics on the tables concerned if this has not recently been done. Rebuilding (or dropping/recreating) indexes may help too.
3.1) Is the SQL statement a MERGE?
evaluate the data returned by the USING clause to ensure that there are no duplicate values in the join. Modify the merge statement to include a deterministic where clause
3.2) Is this an UPDATE statement via a view?
If so, try populating the view result into a table and try updating the table directly.
3.3) Is there a trigger on the table? Try disabling it to see if it still fails.
3.4) Does the statement contain a non-mergeable view in an 'IN-Subquery'? This can result in duplicate rows being returned if the query has a "FOR UPDATE" clause. See Bug 2681037
3.5) Does the table have unused columns? Dropping these may prevent the error.
4) If modifying the SQL does not cure the error, the issue may be with the table, especially if there are chained rows.
4.1) Run the ‘ANALYZE TABLE VALIDATE STRUCTURE CASCADE’ statement on all tables used in the SQL to see if there are any corruptions in the table or its indexes.
4.2) Check for, and eliminate, any CHAINED or migrated ROWS on the table. There are ways to minimize this, such as the correct setting of PCTFREE.
Use Note 122020.1 - Row Chaining and Migration
4.3) If the table is additionally Index Organized, see:
Note 102932.1 - Monitoring Chained Rows on IOTs
Had the error today on a 12c and none of the existing answers fit (no duplicates, no non-deterministic expressions in the WHERE clause). My case was related to that other possible cause of the error, according to Oracle's message text (emphasis below):
ORA-30926: unable to get a stable set of rows in the source tables
Cause: A stable set of rows could not be got because of large dml activity or a non-deterministic where clause.
The merge was part of a larger batch, and was executed on a live database with many concurrent users. There was no need to change the statement. I just committed the transaction before the merge, then ran the merge separately, and committed again. So the solution was found in the suggested action of the message:
Action: Remove any non-deterministic where clauses and reissue the dml.
SQL Error: ORA-30926: unable to get a stable set of rows in the source tables
30926. 00000 - "unable to get a stable set of rows in the source tables"
*Cause: A stable set of rows could not be got because of large dml
activity or a non-deterministic where clause.
*Action: Remove any non-deterministic where clauses and reissue the dml.
This Error occurred for me because of duplicate records(16K)
I tried with unique it worked .
but again when I tried merge without unique same proble occurred
Second time it was due to commit
after merge if commit is not done same Error will be shown.
Without unique, Query will work if commit is given after each merge operation.
I was not able to resolve this after several hours. Eventually I just did a select with the two tables joined, created an extract and created individual SQL update statements for the 500 rows in the table. Ugly but beats spending hours trying to get a query to work.
As someone explained earlier, probably your MERGE statement tries to update the same row more than once and that does not work (could cause ambiguity).
Here is one simple example. MERGE that tries to mark some products as found when matching the given search patterns:
CREATE TABLE patterns(search_pattern VARCHAR2(20));
INSERT INTO patterns(search_pattern) VALUES('Basic%');
INSERT INTO patterns(search_pattern) VALUES('%thing');
CREATE TABLE products (id NUMBER,name VARCHAR2(20),found NUMBER);
INSERT INTO products(id,name,found) VALUES(1,'Basic instinct',0);
INSERT INTO products(id,name,found) VALUES(2,'Basic thing',0);
INSERT INTO products(id,name,found) VALUES(3,'Super thing',0);
INSERT INTO products(id,name,found) VALUES(4,'Hyper instinct',0);
MERGE INTO products p USING
(
SELECT search_pattern FROM patterns
) o
ON (p.name LIKE o.search_pattern)
WHEN MATCHED THEN UPDATE SET p.found=1;
SELECT * FROM products;
If patterns table contains Basic% and Super% patterns then MERGE works and first three products will be updated (found). But if patterns table contains Basic% and %thing search patterns, then MERGE does NOT work because it will try to update second product twice and this causes the problem. MERGE does not work if some records should be updated more than once. Probably you ask why not update twice!?
Here first update 1 and second update 1 are the same value but only by accident. Now look at this scenario:
CREATE TABLE patterns(code CHAR(1),search_pattern VARCHAR2(20));
INSERT INTO patterns(code,search_pattern) VALUES('B','Basic%');
INSERT INTO patterns(code,search_pattern) VALUES('T','%thing');
CREATE TABLE products (id NUMBER,name VARCHAR2(20),found CHAR(1));
INSERT INTO products(id,name,found) VALUES(1,'Basic instinct',NULL);
INSERT INTO products(id,name,found) VALUES(2,'Basic thing',NULL);
INSERT INTO products(id,name,found) VALUES(3,'Super thing',NULL);
INSERT INTO products(id,name,found) VALUES(4,'Hyper instinct',NULL);
MERGE INTO products p USING
(
SELECT code,search_pattern FROM patterns
) s
ON (p.name LIKE s.search_pattern)
WHEN MATCHED THEN UPDATE SET p.found=s.code;
SELECT * FROM products;
Now first product name matches Basic% pattern and it will be updated with code B but second product matched both patterns and cannot be updated with both codes B and T in the same time (ambiguity)!
That's why DB engine complaints. Don't blame it! It knows what it is doing! ;-)

Resources