Oracle After update trigger for bulk update - oracle

Is there any way to program an oracle after update trigger to insert only the most recent record in a history table, when a bulk update is performed.

Suppose you leave out the for each row clause. Than you can achieve a statement trigger, that only fires after the commit of one transaction. Software could be something like this:
create or replace trigger ai_test
after insert on orders
declare
my_test_row orders%rowtype;
begin
select o.*
into my_test_row
from orders o
where o.order_date = (select max(order_date) -- must be the identifying attribute
from orders);
insert into orders_his (id, cust_id, prod_id, order_date)
values
( my_test_row.id
, my_test_row.cust_id
, my_test_row.prod_id
, my_test_row.order_date);
end;

Related

Something is wrong in my oracle trigger

I need help in one of my Oracle Trigger syntax
CREATE OR REPLACE TRIGGER "TRG_TRIGGER" AFTER INSERT
ON T_TABLE
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
BEGIN
INSERT INTO T_TABLE_TEST
(DOC_ID , DOC_NAME)
VALUES
(:NEW.DOC_ID, SELECT DOC_NAME FROM OTHER_TABLE WHERE DOC_ID=DOC_ID);
Need DOC_NAME from other table. I think only my select query needs to be modified?
As others have suggested, you could rewrite the insert...values statement to be an insert...as select statement, and that would make the most sense.
Just fyi, there are two problems with your original insert statement that leap out at me:
You specified doc_id = doc_id in your select statement, whereas I think you meant doc_id = :new.doc_id. Your query would return all rows from the other table, and you'd get ORA-01427: single-row subquery returns more than one row, if your query was able to run.
You didn't enclose the select statement within brackets. It should be:
--
INSERT INTO T_TABLE_TEST
(DOC_ID , DOC_NAME)
VALUES
(:NEW.DOC_ID, (SELECT DOC_NAME FROM OTHER_TABLE WHERE DOC_ID=:NEW.DOC_ID));
The insert must be like this:
INSERT INTO T_TABLE_TEST
(DOC_ID , DOC_NAME)
SELECT :NEW.DOC_ID, DOC_NAME FROM OTHER_TABLE where doc_id = :NEW.DOC_ID;
Just a note, you can skip REFERENCING NEW AS NEW OLD AS OLD since OLD and NEW are defaults.
You need to specify which doc_name to select, so I think you are missing a where clause.. try this:
INSERT INTO T_TABLE_TEST
(DOC_ID , DOC_NAME)
SELECT t.doc_id, t.DOC_NAME FROM OTHER_TABLE t where t.doc_id = :NEW.DOC_ID;
Of course I guessed that other table have column named doc_id, change it to the relation column.

Update same table after Insert trigger

I am working on a product in which I have to send SMS to concerned person when someone waits for more than 15 minutes for being served.
For that I have written a procedure that watches a table and stores CUST_ID, CUST_CATEGORY, DURATION in a separate table when the Duration exceeds 15. The table structure of this table is:
Some_Table
CUST_ID CUST_CATEGORY DURATION SMS_STATUS
I wrote a trigger as:
Trigger
create or replace trigger kiosk_sms_trg
after insert on Some_Table
referencing new as new old as old
for each row
BEGIN
SMS_Proc#My_Server; --Procudure that generates SMS
update Some_Table set status = 'Y' where id = (select max(id) id from Some_Table where status = 'N'); --Update Table that SMS has been sent
select 'Y' into :new.status from dual;
END;
But it creates Mutation Problem. How do I resolve it? Any help would be highly appreciated. I'm using Oracle 11G.
I don't think that UPDATE is allowed on SOME_TABLE as it is currently mutating.
Why not place it right after the INSERT statement which fired the trigger in the first place?.
INSERT INTO SOME_TABLE ...
update Some_Table set status = 'Y' where id = (select max(id) id from Some_Table where status = 'N'); --Update Table that SMS has been sent
I guess this would be the right approach considering you aren't doing anything row specific in that UPDATE.
As I mentioned in the comment, Is there any particular use for this last statement in the AFTER INSERT trigger? It does have meaning in the BEFORE INSERT trigger.
select 'Y' into :new.status from dual;
You cannot update the same table in Row-Level AFTER Trigger.
Change your Row-Level AFTER INSERT trigger to row-level BFEORE INSERT trigger.
But you UPDATE stmt inside the trigger will not effect the new record being inserted.
Wonder how it can be done, this is tricky.

Insert in target table and then update the source table field in oracle

In Oracle, I have a requirement where in I need to insert records from Source to Target and then update the PROCESSED_DATE field of source once the target has been updated.
1 way is to use cursors and loop row by row to achieve the same.
Is there any other way to do the same in an efficient way?
No need for a cursor. Assuming you want to transfer those rows that have not yet been transfered (identified by a NULL value in processed_date).
insert into target_table (col1, col2, col3)
select col1, col2, col3
from source_table
where processed_date is null;
update source_table
set processed_date = current_timestamp
where processed_date is null;
commit;
To avoid updating rows that were inserted during the runtime of the INSERT or between the INSERT and the update, start the transaction in serializable mode.
Before you run the INSERT, start the transaction using the following statement:
set transaction isolation level SERIALIZABLE;
For more details see the manual:
http://docs.oracle.com/cd/E11882_01/server.112/e26088/statements_10005.htm#i2067247
http://docs.oracle.com/cd/E11882_01/server.112/e25789/consist.htm#BABCJIDI
A trigger should work. The target table can have a trigger that on update, updates the source table's column with the processed date.
My preferred solution in this sort of instance is to use a PL/SQL array along with batch DML, e.g.:
DECLARE
CURSOR c IS SELECT * FROM tSource;
TYPE tarrt IS TABLE OF c%ROWTYPE INDEX BY BINARY_INTEGER;
tarr tarrt;
BEGIN
OPEN c;
FETCH c BULK COLLECT INTO tarr;
CLOSE c;
FORALL i IN 1..tarr.COUNT
INSERT INTO tTarget VALUES tarr(i);
FORALL i IN 1..tarr.COUNT
UPDATE tSource SET processed_date = SYSDATE
WHERE tSource.id = tarr(i).id;
END;
The above code is an example only and makes some assumptions about the structure of your tables.
It first queries the source table, and will only insert and update those records - which means you don't need to worry about other sessions concurrently inserting more records into the source table while this is running.
It can also be easily changed to process the rows in batches (using the fetch LIMIT clause and a loop) rather than all-at-once like I have here.
Got another answer from some one else. Thought that solution seems much more reasonable than enabling isolation level as all my new records will have the PROCESSED_DATE as null (30 rows which inserted with in the time the records got inserted in Target table)
Also the PROCESSED_DATE = NULL rows can be updated only by using my job. No other user can update these records at any point of time.
declare
date_stamp date;
begin
select sysdate
into date_stamp
from dual;
update source set processed_date = date_stamp
where procedded_date is null;
Insert into target
select * from source
where processed_date = date_stamp;
commit;
end;
/
Let me know any further thoughts on this. Thanks a lot for all your help on this.

Update or insert based on if employee exist in table

Do want to create Stored procc which updates or inserts into table based on the condition if current line does not exist in table?
This is what I have come up with so far:
PROCEDURE SP_UPDATE_EMPLOYEE
(
SSN VARCHAR2,
NAME VARCHAR2
)
AS
BEGIN
IF EXISTS(SELECT * FROM tblEMPLOYEE a where a.ssn = SSN)
--what ? just carry on to else
ELSE
INSERT INTO pb_mifid (ssn, NAME)
VALUES (SSN, NAME);
END;
Is this the way to achieve this?
This is quite a common pattern. Depending on what version of Oracle you are running, you could use the merge statement (I am not sure what version it appeared in).
create table test_merge (id integer, c2 varchar2(255));
create unique index test_merge_idx1 on test_merge(id);
merge into test_merge t
using (select 1 id, 'foobar' c2 from dual) s
on (t.id = s.id)
when matched then update set c2 = s.c2
when not matched then insert (id, c2)
values (s.id, s.c2);
Merge is intended to merge data from a source table, but you can fake it for individual rows by selecting the data from dual.
If you cannot use merge, then optimize for the most common case. Will the proc usually not find a record and need to insert it, or will it usually need to update an existing record?
If inserting will be most common, code such as the following is probably best:
begin
insert into t (columns)
values ()
exception
when dup_val_on_index then
update t set cols = values
end;
If update is the most common, then turn the procedure around:
begin
update t set cols = values;
if sql%rowcount = 0 then
-- nothing was updated, so the record doesn't exist, insert it.
insert into t (columns)
values ();
end if;
end;
You should not issue a select to check for the row and make the decision based on the result - that means you will always need to run two SQL statements, when you can get away with one most of the time (or always if you use merge). The less SQL statements you use, the better your code will perform.
BEGIN
INSERT INTO pb_mifid (ssn, NAME)
select SSN, NAME from dual
where not exists(SELECT * FROM tblEMPLOYEE a where a.ssn = SSN);
END;
UPDATE:
Attention, you should name your parameter p_ssn(distinguish to the column SSN ), and the query become:
INSERT INTO pb_mifid (ssn, NAME)
select P_SSN, NAME from dual
where not exists(SELECT * FROM tblEMPLOYEE a where a.ssn = P_SSN);
because this allways exists:
SELECT * FROM tblEMPLOYEE a where a.ssn = SSN

Keeping track of all values created by a sequence for multiple inserts

In PL SQL, I'm writing a stored procedure that uses a DB link:
CREATE OR REPLACE PROCEDURE Order_Migration(us_id IN NUMBER, date_id in DATE)
as
begin
INSERT INTO ORDERS(order_id, company_id)
SELECT ORDER_ID_SEQ.nextval, COMPANY_ID
FROM ORDERS#SOURCE
WHERE USER_ID = us_id AND DUE_DATE = date_ID;
end;
It takes all orders done on a certain day, by a certain user and inserts them in the new database. It calls a sequence to makes sure there are no repeat PKs on the orders, and it works well.
However, I want the same procedure to do a second INSERT into another table that has order_id as a foreign key. So I need to add all the order_id's just created, and the data from SOURCE that matches:
INSERT INTO ORDER_COMPLETION(order_id, completion_dt)
SELECT ????, completion_dt
FROM ORDER_COMPLETION#SOURCE
How can I keep track of which order_id that was just created matches up to the one whose data I need to pull from the source database?
I looked into making a temporary table, but you can't create those in a procedure.
Other info: I'll be calling this procedure from a C# app I'm writing
I'm not sure that I follow the question. If there is an ORDERS table and an ORDER_COMPLETION table in the remote database, wouldn't there be some key on the source system that related those two tables? If that key is the ORDER_ID, why would you want to re-assign that key in your procedure? Wouldn't you want to maintain the ORDER_ID from the source system?
If you do want to re-assign the ORDER_ID locally, I would tend to think that you'd want to do something like
CREATE OR REPLACE PROCEDURE order_migration( p_user_id IN orders.user_id%type,
p_due_date IN orders.due_date%type )
AS
TYPE order_rec IS RECORD( new_order_id NUMBER,
old_order_id NUMBER,
company_id NUMBER,
completion_dt DATE );
TYPE order_arr IS TABLE OF order_rec;
l_orders order_arr;
BEGIN
SELECT order_id_seq.nextval,
o.order_id,
o.company_id,
oc.completion_dt
BULK COLLECT INTO l_orders
FROM orders#source o,
order_completion#source oc
WHERE o.order_id = oc.order_id
AND o.user_id = p_user_id
AND o.due_date = p_due_date;
FORALL i IN l_orders.FIRST .. l_orders.LAST
INSERT INTO orders( order_id, company_id )
VALUES( l_orders(i).new_order_id, l_orders(i).company_id );
FORALL i IN l_orders.FIRST .. l_orders.LAST
INSERT INTO order_completion( order_id, completion_dt )
VALUES( l_orders(i).new_order_id, l_orders(i).completion_dt );
END;
You could also do a single FOR loop with two INSERT statements rather than two FORALL loops. And if you're pulling a lot of data each time, you probably want to pull the data in chunks from the remote system by adding a loop and a LIMIT to the BULK COLLECT
There must be some link between the rows in ORDERS#SOURCE and ORDERS, and between ORDERS#SOURCE and ORDER_COMPLETION#SOURCE, so can you not use a join?
Something like:
INSERT INTO ORDER_COMPLETION(order_id, completion_dt)
SELECT o.order_id, ocs.completion_dt
FROM ORDER_COMPLETION#SOURCE ocs
JOIN ORDERS o ON o.xxx = ocs.xxx

Resources