How to update multiple rows with the same sequence value - sql

I'm using DB2 and want to update several rows that meet my condition with the same next value from my sequence.
Here is what I tried but this doesn't work as the next value is fetched for each row:-
update dependency dep set vid=NEXT VALUE FOR seq_VID where id in ('8371','8372','8373')
id is the the primary key and seq_VID is a sequence. So what I had hoped was that say the next sequence value was 99, that 99 would be set for all 3 rows (and not 99,100,101 as is the case with this).
My workaround is to break it into separate statements for each id in my list, i.e.
update dependency dep set vid=NEXT VALUE FOR seq_VID where id= ('8371')
update dependency dep set vid=PREVIOUS VALUE FOR seq_VID where id= ('8372')
update dependency dep set vid=PREVIOUS VALUE FOR seq_VID where id= ('8373')
But I'd like to execute this in one SQL statement if possible - any ideas?

If you always knew that you wanted to put the 'previous' sequence value on the two rows after the one you updated with the 'next' next value you might be able to use a compound trigger, similar to the following (Oracle syntax, please forgive):
CREATE OR REPLACE TRIGGER DEPENDENCY_COMPOUND
FOR UPDATE ON DEPENDENCY
COMPOUND TRIGGER
TYPE tDependency_row_table IS TABLE OF DEPENDENCY%ROWTYPE;
tblDependency_rows tDependency_row_table := tDependency_row_table();
AFTER EACH ROW IS
BEGIN
tblDependency_rows.EXTEND;
tblDependency_rows(tblDependency_rows.LAST).ID = NEW.ID+1;
tblDependency_rows(tblDependency_rows.LAST).VID = seq_VID.CURRVAL;
tblDependency_rows.EXTEND;
tblDependency_rows(tblDependency_rows.LAST).ID = NEW.ID+2;
tblDependency_rows(tblDependency_rows.LAST).VID = seq_VID.CURRVAL;
END;
AFTER STATEMENT IS
BEGIN
FOR i IN tblDependency_rows.FIRST..tblDependency_rows.LAST LOOP
UPDATE DEPENDENCY
SET VID = tblDependency_rows(i).VID
WHERE ID = tblDependency_rows(i).ID;
END LOOP;
END;
END DEPENDENCY_AU;
Then you'd issue your update statement as
UPDATE DEPENDENCY
SET VID = seq_VID.NEXTVAL
WHERE ID = 8371;
and the trigger should take care of updating the other two rows.
The compound trigger is useful in Oracle 11+ to help work around the 'mutating table' error, which occurs when a trigger attempts to SELECT, INSERT, UPDATE, or DELETE data in the same table which the trigger is on.
This is a rather contrived situation and makes some huge assumptions about which rows should be updated, but perhaps it will prove useful.
Share and enjoy.

Related

ORA-04091 Error on "after update" trigger, how to get around?

I have a trigger set to fire after an update of a specific column. I want to update the value of a specific column after a specific value from another column changes to another specific value.
I'm getting the errors:
ORA-04091: table tableName is mutating, trigger/function may not see it
ORA-06512: at "triggerName", line 14
ORA-04088: error during execution of trigger 'triggerName'
I've rewritten this as a before update and after update, as well as tried storing the logic in a function, and using "pragma autonomous_transaction", but the error still gets thrown.
BEFORE update of columnName1 ON tableName
FOR EACH ROW
BEGIN
if :new.columnName1 = 3 AND :old.columnName1 = 1 then
update tablename
set columnName2= MOD(sequenceName.NextVal, 5) + 1
where tableName.columnName2 = :old.columnName2;
end if;
END;
/
I don't understand why the entire table is labelled as "mutating" when I am not updating the column affected by the update the trigger is responding to. Surely, you should be able to update the value of a column in a table if another value in the table changes, or am I crazy here?
Note: Only one entry would be affected at a time. In my application logic, I update the status of some person in the database. I want the database to do some logic only on a specific status change, and I want to avoid using API calls for the logic here, as you can see, it is simply one line of logic in PLSQL.
Thanks
Perhaps you don't really want to update the table but just change a value in the specific row being updated.
If so, eschew the update and just set the value:
BEFORE update of columnName1 ON tableName
FOR EACH ROW
BEGIN
if :new.columnName1 = 3 AND :old.columnName1 = 1 then
:new.columnName2 := MOD(sequenceName.NextVal, 5) + 1 ;
end if;
END;

UPSERT inserts duplicate null entry into table (ORACLE)

I am trying to make an upsert trigger on ORACLE via PL/SQL by checking some examples, i am doing fine, i think it is the last step i should only configure. My requirement is that :
A system that will insert to that field will remain one column always null, so i will read column value from another table, then upsert it with inclusion of that value.
d2c_region_locale_config holds d2c_is_active value, so i firstly read that value regarding to locale condition then trigger inserts or updates to table with addition of this value on active_for_d2c column.(for update i am using locale and country columns as it is shown on where clause, they are not PK but has not null condition)
So i've created this trigger:
CREATE OR REPLACE TRIGGER BL_PIM_LOCALE_COUNTRY
BEFORE INSERT OR UPDATE ON PIM_LOCALE_COUNTRY REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
DECLARE
l_active_for_d2c INTEGER;
BEGIN
if :NEW.active_for_d2c is null then
DELETE from pim_locale_country where active_for_d2c is null;
select distinct(d2c_isactive) into l_active_for_d2c from d2c_region_locale_config where d2c_locale= :NEW.locale;
UPDATE pim_locale_country
SET locale = :NEW.locale, locale_name = :NEW.locale_name,
country = :NEW.country, country_name = :NEW.country_name, isdummy = :NEW.isdummy,
active_for_d2c = l_active_for_d2c, itextpos = :NEW.itextpos, locale_charset = :NEW.locale_charset,
fallback_locale = :NEW.fallback_locale, default_for_lang = :NEW.default_for_lang, opeclang = :NEW.opeclang
where locale = :NEW.locale and country = :NEW.country;
IF ( sql%notfound ) THEN
INSERT INTO PIM_LOCALE_COUNTRY (locale,locale_name,country,country_name,isdummy,active_for_d2c,itextpos,locale_charset,fallback_locale,default_for_lang,opeclang)
VALUES (:NEW.locale, :NEW.locale_name,:NEW.country,:NEW.country_name,:NEW.isdummy,l_active_for_d2c,:NEW.itextpos,:NEW.locale_charset,:NEW.fallback_locale,:NEW.default_for_lang,:NEW.opeclang);
END IF;
end if;
END;
It currently does the job, reads value and inserts or updates the existing locale-country couple for other values. But critical thing is that, table always has one "null" value(Please check screenshot), even that i run delete statement at the beginning on my trigger. So my question would be how to delete, or how to make this approach on trigger side ?
Many thanks for answers!
Trigger before insert doesn't block insert itself, so you insert that record twice. That is, once your trigger done its work (inserted or updated record), oracle will proceed with insert (or update) using values that stand in NEW record of your trigger. If trigger modifies NEW., it will be stored as you changed it, but if trigger inserts something itself, you can get more records.
You can use instead of insert or update triggers, and then oracle will not run its own inserts/updates after trigger finishes.
But more common way for 1-record triggers is to modify fields in NEW, for this case field NEW.d2c_is_active.
It looks like this (possible typos, please check)
CREATE OR REPLACE TRIGGER BL_PIM_LOCALE_COUNTRY
BEFORE INSERT OR UPDATE ON PIM_LOCALE_COUNTRY REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
BEGIN
if :NEW.active_for_d2c is null then
select d2c_isactive
into :NEW.active_for_d2c
from d2c_region_locale_config
where d2c_locale= :NEW.locale and rownum<=1;
end if;
END;

update same table with condition in oracle

Unable to execute the trigger, Can anyone explain where i am doing wrong.
CREATE OR REPLACE TRIGGER HK_WS_ED_CT_T1
BEFORE INSERT OR UPDATE OF CI On HK_WS_ED_CT
FOR EACH ROW
BEGIN
IF (:new.CI = CI) THEN
UPDATE HK_WS_ED_CT
SET (TRANS_OTHER, TRANS_OTHER_DESC) =
(SELECT TRANS_OTHER, TRANS_OTHER_DESC
FROM HK_WS_ED_CT
where CI = :NEW.CI and rownum <=1 order by DATE)
end if;
END;
You are updating the table the trigger is on. This is not allowed.
You should put :new.trans_other equal to something and :new.trans_other_desc to something.
The update of the field will be ok then.
If you really want to update all the rows of that same table you should take a look at compound triggers. But I doubt this is what you want.
The main problem in DB triggers is you can't select, insert, update, delete from the table the trigger is firing on. The table is locked at this moment.

Compound trigger: collecting mutating rows into nested table

I have two tables in my project: accounts and transactions (one-to-many relationship). In every transaction I store the balance of the associated account (after the transaction is executed). Additionally in every transaction I store a value of the transaction.
So I needed a trigger fired when someone adds new transaction. It should check whether new account balance will be correct (old account balance + transaction value = new account balance stored in transaction).
So I was suggested, I should use a compound trigger which would:
in before each row section: save a row's PK (made of two columns) somewhere,
in after statement section: check whether all inserted transactions where correct.
Now I can't find anywhere how could I implement the first point.
What I already have:
CREATE OR REPLACE TRIGGER check_account_balance_is_valid
FOR INSERT
ON Transactions
COMPOUND TRIGGER
TYPE Modified_transactions_T IS TABLE OF Transactions%ROWTYPE;
Modified_transactions Modified_transactions_T;
BEFORE STATEMENT IS BEGIN
Modified_transactions := Modified_transactions_T();
END BEFORE STATEMENT;
BEFORE EACH ROW IS BEGIN
Modified_transactions.extend;
Modified_transactions(Modified_transactions.last) := :NEW;
END BEFORE EACH ROW;
AFTER STATEMENT IS BEGIN
NULL; -- I will write something here later
END AFTER STATEMENT;
END check_account_balance_is_valid;
/
However, I got that:
Warning: execution completed with warning
11/58 PLS-00049: bad bind variable 'NEW'
Could someone tell me, how to fix it? Or maybe my whole "compound trigger" idea is wrong and you have better suggestions.
Update 1
Here is my ddl script: http://pastebin.com/MW0Eqf9J
Maybe try this one:
TYPE Modified_transactions_T IS TABLE OF ROWID;
Modified_transactions Modified_transactions_T;
BEFORE STATEMENT IS BEGIN
Modified_transactions := Modified_transactions_T();
END BEFORE STATEMENT;
BEFORE EACH ROW IS BEGIN
Modified_transactions.extend;
Modified_transactions(Modified_transactions.last) := :NEW.ROWID;
END BEFORE EACH ROW;
or this
TYPE PrimaryKeyRecType IS RECORD (
Col1 Transactions.PK_COL_1%TYPE, Col2 Transactions.PK_COL_2%TYPE);
TYPE Modified_transactions_T IS TABLE OF PrimaryKeyRecType;
...
Modified_transactions(Modified_transactions.last) := PrimaryKeyRecType(:NEW.PK_COL_1, :NEW.PK_COL_2);
Your immediate problem is that :new is not a real record so it is not of type Transactions%ROWTYPE. If you're really going to go down this path, you would generally want to declare a collection of the primary key of the table
TYPE Modified_transactions_T IS TABLE OF Transactions.Primary_Key%TYPE;
and then put just the primary key in the collection
BEFORE EACH ROW IS BEGIN
Modified_transactions.extend;
Modified_transactions(Modified_transactions.last) := :NEW.Primary_Key;
END BEFORE EACH ROW;
The fact that you are trying to work around a mutating table exception in the first place, however, almost always indicates that you have an underlying data modeling problem that you should really be solving. If you need to query other rows in the table in order to figure out what you want to do with the new rows, that's a pretty good indication that you have improperly normalized your data model and that one row has some dependency on another row in the same table rather than being an autonomous fact. Fixing the data model is almost always preferrable to working around the mutating table exception.

mutating table when writing a trigger

I have been bogged in this problem for quite a long time....Can anyone help me out?
Here is the thing I want to implement:
I have a table A, A has attributes: id, count, total. Here I am required to implement such a trigger: IF the count in table A is updated, the trigger will set the total to 1.
My initial code is like this:
CREATE OR REPLACE TRIGGER tri_A AFTER UPDATE OF count ON A
FOR EACH ROW
BEGIN
UPDATE A SET total = 1 WHERE id = :new.id;
END;
/
The problem with this is the mutating table. When the table is updated, the table will be locked. I searched for the answers, I tried pragma autonomous_transaction, but I got an invalid trigger specification error. And there are other comments saying that we should try to use a combination of triggers to do this....I can't figure out how to do this
Assuming id is the primary key, you don't want to UPDATE the table. You just want to set :new.total. You'll also need to do this in a BEFORE UPDATE trigger not an AFTER UPDATE trigger.
CREATE OR REPLACE TRIGGER tri_A
BEFORE UPDATE OF count ON A
FOR EACH ROW
BEGIN
:new.total := 1;
END;