I am a new bee on Oracle.
I have to create a Trigger that will allow me to copy data (from a specific fields) for a table X to the table Y before insert of a new record base on their Keys (IPARKEY for X,and IDNUMPARTICIPANT for Y).
CREATE OR REPLACE TRIGGER DATA_IDOCUMENTS_INSERT_TRIGER
BEFORE insert on Y
for each row
DECLARE
CURSOR c IS
SELECT IPARNOM,IPARPRENOM,IPARDATENAISSANCE,IPARNUMSECU
from X WHERE IPARKEY = :NEW.IDNUMPARTICIPANT;
rc c%ROWTYPE;
BEGIN
OPEN c;
LOOP
FETCH c INTO rc;
-- THOSE ARE THE NEEDED fIELDS TO BE POPULATED..
new.IDNOM:= rc.IPARNOM;
new.IPARDATENAISSANCE := rc.IPARDATENAISSANCE
EXIT WHEN c % NOTFOUND;
END LOOP;
END;
it is obvious that i did write wrongly the trigger so i will appreciate your Help to fix it and get the needed business .
Since you already declared the cursor, your loop can be a bit more concise:
create or replace trigger data_idocuments_insert_triger
before insert on y
for each row
declare
cursor c is
select iparnom
,iparprenom
,ipardatenaissance
,iparnumsecu
from x
where iparkey = :new.idnumparticipant;
begin
for r in c loop
-- THOSE ARE THE NEEDED fIELDS TO BE POPULATED..
:new.idnom := r.iparnom;
:new.ipardatenaissance := r.ipardatenaissance
end loop;
end;
Of course, if your query returns more than 1 row you will end up with the last value to be assigned to the columns in Y.
If you are sure there will be at most 1 record you can rewrite to:
create or replace trigger data_idocuments_insert_triger
before insert on y
for each row
begin
select iparnom
,ipardatenaissance
into :new.idnom
,:new.ipardatenaissance
from x
where iparkey = :new.idnumparticipant;
exception
when no_data_found then
null;
end;
You can be only sure the query returns at most 1 record if x.iparkey is a primary or unique key.
Related
I create a oracle package with function where in the input is a whole row and the output is varray. i want to use this package function to apply on all the rows of a table using trigger as soon as new record is inserted. I tried like this but the output is bad bind variable
create or replace trigger custdata_caferrors
before insert or update on customer_data
referencing new as n old as o
for each row
declare
v_remarks varchar(500) :=' ';
t_remarks caf_errors :=caf_errors();
rec customer_data%rowtype;
begin
-- rec = :n;
t_remarks := CUSTDATA_VERIFY.VERIFY_TERMSTATUS(:n);
for x in 1..t_remarks.count
loop
v_remarks :=v_remarks||' , '||t_remarks(x);
end loop;
:n.record_status1 :=v_remarks;
end;
/
Unfortunately there is no way to reference a whole new row in a trigger. I agree it would be neat if this were possible, but alas.
So you will have to explicitly populate your rec variable with the column values you require and pass that to your function, like this
create or replace trigger custdata_caferrors
before insert or update on customer_data
referencing new as n old as o
for each row
declare
v_remarks varchar(500) :=' ';
t_remarks caf_errors :=caf_errors();
rec customer_data%rowtype;
begin
rec.id := :n.id;
rec.col1 := :n.col1;
-- etc
t_remarks := CUSTDATA_VERIFY.VERIFY_TERMSTATUS(rec);
for x in 1..t_remarks.count
loop
v_remarks :=v_remarks||' , '||t_remarks(x);
end loop;
:n.record_status1 :=v_remarks;
end;
/
Do you really need to pass the whole row to CUSTDATA_VERIFY.VERIFY_TERMSTATUS()? If not you might get away with populating just the columns the function actually uses (although that does contravene the Law of Demeter).
Here's my problem:
I have to transform something like this
Into this in the same table
I've tried triggers but I have mutating issues, I have no control about the inserts but I need to do that for every row with value larger than 1.
Any sugestions?
Depending on the number of transactions you're looking at, it may not be a feasible approach, but you might try something along these lines:
CREATE OR REPLACE TRIGGER {TRIGGER NAME}
BEFORE INSERT ON {TABLE HERE}
FOR EACH ROW
DECLARE
l_val INTEGER;
BEGIN
l_val := :new.value - 1;
IF l_val > 0 THEN
:new.value := 1;
FOR i IN 1..l_val LOOP
INSERT INTO {TABLE HERE}
VALUES (:new.name, 1);
END LOOP;
END IF;
END {TRIGGER NAME};
/
You need to use view and instead of trigger.
Creating table create table testing_trig (c char(1), i integer);
Creating view create or replace view testing_trig_view as select * from testing_trig;
Creating trigger
CREATE OR REPLACE TRIGGER testing_trg
INSTEAD OF INSERT
ON testing_trig_view
BEGIN
FOR i IN 1 .. :NEW.i
LOOP
INSERT INTO testing_trig VALUES (:NEW.c, 1);
END LOOP;
END;
/
Insert into view insert into testing_trig_view values ('A', 3);
Validate
SQL> select * from testing_trig;
C I
- ----------
A 1
A 1
A 1
i created a trigger to insert the employeeIDs whenever a position is updated.
In some cases, there are many employees attached to one position so the trigger isnt able to insert all the employees (only 1). i need all the employee Ids
can any1 help me with this code?
Regards
create or replace trigger postn_updt
after update on postn
for each row
declare
cursor m is
select u.login
from user u,
party_per s
where u.row_id=s.person_id
and s.party_id=:new.row_id;
rownum varchar2(10);
begin
if updating ('postn_type_cd') then
open mult;
fetch mult into rownum;
insert into test123
(employee_number,type,request_date,remarks)
values
(( rownum,
'Updated',sysdate,''
);
close m;
end if;
end;
Triggers != Application code
If you embed application code in trigger like this then it will be horrible to maintain and debug and you'll always be encountering situations where a trigger-based approach won't work because of a mutating table error.
You would do much better to keep triggers for only auditing and other non-application activities, and put this kind of logic in the application itself.
to insert multiple rows you will need either a LOOP or some form of "INSERT... SELECT" statement.
eg.
create or replace trigger postn_updt
after update on postn
for each row
declare
cursor m is
select u.login
from user u,
party_per s
where u.row_id=s.person_id
and s.party_id=:new.row_id;
begin
if updating ('postn_type_cd') then
for mult_rec in m LOOP
insert into test123
(employee_number,type,request_date,remarks)
values
(( mult_rec.login,
'Updated',sysdate,''
);
END LOOP;
end if;
end;
OR
create or replace trigger postn_updt
after update on postn
for each row
declare
begin
if updating ('postn_type_cd') then
insert into test123
(employee_number,type,request_date,remarks)
select u.login ,'Updated',sysdate,''
from user u,
party_per s
where u.row_id=s.person_id
and s.party_id=:new.row_id;
end if;
end;
I have a statement level trigger that fires whenever INSERT UPDATE or DELETE operations are performed on a table (called customers). I want to display a message (to DBMS_OUTPUT) containing the number of rows that were inserted/updated/deleted.
I just want one message for each triggering statement, eg
'4 rows were inserted into customers table'.
How can I access the number of rows that are affected by the triggering statement from INSIDE the trigger declaration, ie XXX in the code below:
CREATE OR REPLACE TRIGGER customer_changes_trigger_2
AFTER INSERT OR UPDATE OR DELETE ON customers
DECLARE
v_operation VARCHAR(10);
v_number_rows NUMBER;
BEGIN
v_number := XXX;
IF INSERTING THEN
v_operation := 'inserted';
END IF;
IF UPDATING THEN
v_operation := 'updated';
END IF;
IF DELETING THEN
v_operation := 'deleted';
END IF;
DBMS_OUTPUT.PUT_LINE
(v_number_rows|| ' rows were ' || v_operation || ' from customers.');
END;
Can't find anything in the documentation, any help appreciated!
One way is to use a global variable to track the number of rows as there is no other way to get the row count from a statement level trigger. You would then need three triggers... one statement level to initialise the variable before the statement is run, one row level to add one to the variable for each row, one statement level to use the row count however you wish. First, set up the variable and a few procedures to help it:
create or replace package PKG_ROWCOUNT is
NUMROWS number;
procedure INIT_ROWCOUNT;
procedure ADD_ONE;
function GET_ROWCOUNT
return number;
end PKG_ROWCOUNT;
/
create or replace package body PKG_ROWCOUNT as
procedure INIT_ROWCOUNT is
begin
NUMROWS := 0;
end;
procedure ADD_ONE is
begin
NUMROWS := Nvl(NUMROWS, 0) + 1;
end;
function GET_ROWCOUNT
return number is
begin
return NUMROWS;
end;
end PKG_ROWCOUNT;
/
The first trigger to initialise the variable:
create or replace trigger CUSTOMER_CHANGES_TRIGGER_1
before insert or update or delete
on CUSTOMERS
begin
PKG_ROWCOUNT.INIT_ROWCOUNT;
end;
The second to update per row:
create or replace trigger CUSTOMER_CHANGES_TRIGGER_2
after insert or update or delete
on CUSTOMERS
for each row
begin
PKG_ROWCOUNT.ADD_ONE;
end;
/
The third to display the total:
create or replace trigger CUSTOMER_CHANGES_TRIGGER_3
after insert or update or delete
on CUSTOMERS
begin
Dbms_output.
PUT_LINE(PKG_ROWCOUNT.GET_ROWCOUNT || ' rows were affected.');
end;
I'm not 100$ sure if it's available inside AFTER trigger body, but you can try examining sql%rowcount
Basically I need to make a for loop that will loop though the amount of rows. In each row I need to check a value and change it if it meets the requirements.
I'm new to Oracle, so I just started building it one step at a time and I'm stuck on looping through the table rows. I need to first get the number count of the rows that a Boolean flag set to 0 (false). So then I can loop through only those rows, not every row in the table. Once I'm done with whatever I need to change in that row, set the flag to 1 (true), so when I run the procedure again it won't include that row.
Here's what I have so far:
My table:
CREATE TABLE test_table_results (
name varchar,
account number,
address varchar,
database_search NUMBER(1) DEFAULT 0 NOT NULL
CONSTRAINT searched_in_database CHECK (database_search IN (0,1))
);
The table in my database:
CREATE TABLE test_table_accounts (
name varchar,
account number,
address varchar,
);
Now the procedure will go though the results table and see if the address match, if they do it will copy the account number from the database table into the results account number, then change the flag from 0 to 1, so the next time I search though the table it won't include it because it was already searched.
create or replace PROCEDURE FIND_MATCH_ADDRESS AS
BEGIN
DECLARE
v_cnt NUMBER;
BEGIN
FOR i IN (SELECT rowid, r.* FROM test_table
WHERE database_searched = 0)
LOOP
LOOP
SELECT COUNT(1) INTO v_cnt
FROM test_table
WHERE database_searched = 0;
DBMS_OUTPUT.PUT_LINE(v_cnt);
END LOOP;
END LOOP;
END;
END FIND_MATCH_ADDRESS;
EDIT: Added the two tables in hopes to make my question/task more understandable.
Again thank you for your time!!
In your example I see some mistakes.
In the procedure you do not need Declare. Declaration block is between as and begin
create or replace PROCEDURE proc
AS
-- here variable declaration or local function or procedures
BEGIN
-- here you can write a business logic
END proc;
You can iterate over the records of a table with a For loop. In your example, you also tried to use them. You can iterate over the records of a table with a For loop. In your example, you also tried to use them.
FOR record IN (cursor)
LOOP
{...statements...}
END LOOP;
I did not quite understand why you used another loop in the loop. a loop statement is an endless loop.
loop
...
end loop;
In the loop you can now implement your logic. If you really want to use a loop, then your solution might look like this
create or replace PROCEDURE FIND_MATCH_ADDRESS
AS
v_cnt NUMBER;
BEGIN
FOR rec IN (SELECT r.name
,r.address
,r.account
,a.account as new_account
FROM test_table_results r
join test_table_accounts a on a.address = r.address
WHERE r.database_searched = 0)
LOOP
update test_table_results
set account = rec.new_account
, database_searched = true
where account = rec.account
and name = rec.name
and adress = rec.adress;
END LOOP;
END FIND_MATCH_ADDRESS;
Alternatively, you can also do that with an update. Since I do not know your tables, you should then optimize the where condition.
update test_table_results t
set database_searched = true
, account = (select account
from test_table_accounts a
where a.account = t.account
and a.name = t.name
and a.adress = t.adress)
where database_searched = false
and exists(select 1
from test_table_accounts a
where a.account = t.account
and a.name = t.name
and a.adress = t.adress);