Can I execute a dynamic sql statement in an after update trigger? - oracle

I am using the following statement:
query_str='SELECT :NEW.FIRST_NAME||:NEW.LAST_NAME INTO HostID
FROM INPUT_TABLE WHERE INPUT_ID='
The trigger has the following code:
EXECUTE IMMEDIATE query_str ||:NEW.INPUT_ID;
I'm getting the following error when updating INPUT_TABLE:
ORA-01008: not all variables bound
If I'm updating a record where input_id=111, I would think Oracle would just execute the following statement:
SELECT :NEW.FIRST_NAME||:NEW.LAST_NAME INTO HostID
FROM INPUT_TABLE WHERE INPUT_ID=111
Why is it having binding issues?
I'm using SQLDeveloper.
create or replace
TRIGGER DATA_DETAIL_TRIG
AFTER INSERT OR UPDATE
ON INPUT_TABLE
FOR EACH ROW
DECLARE
DATA_SOURCE_ID_RET NUMBER;
resultcount NUMBER;
query_str VARCHAR2(512);
using_cl VARCHAR2(512);
HostId VARCHAR2(256);
DATA_SOURCE_ID NUMBER;
INPUT_ID NUMBER(38,0);
AUTOGENERATE_IND VARCHAR2(1);
autogen_const varchar(200);
pragma autonomous_transaction;
CURSOR C_DATA_SOURCES IS
SELECT DATA_SOURCE_ID
FROM DATA_SOURCE_DETAIL
WHERE AUTOGENERATE_IND='Y'
AND DATA_SOURCE_REF.ACTIVE_IND='Y';
BEGIN
OPEN C_DATA_SOURCES;
LOOP
FETCH C_DATA_SOURCES INTO DATA_SOURCE_ID_RET;
EXIT WHEN C_DATA_SOURCES%NOTFOUND;
query_str:=getHostQuery( DATA_SOURCE_ID_RET);
--SELECT :FIRST_NAME||:LAST_NAME||to_char(:DOB,'yyyy/mm/dd')
From INPUT_TABLE WHERE INPUT_ID=:INPUT_ID
using_cl:=getHostUsing(DATA_SOURCE_ID_RET);
--:NEW.FIRST_NAME, :NEW.LAST_NAME, :NEW.DOB, :NEW.INPUT_ID
EXECUTE IMMEDIATE query_str INTO HostId USING using_cl;
IF INSERTING THEN
INSERT INTO DETAIL_TABLE
(
DETAIL_ID,
INPUT_ID,
HOST_ID,
DATA_SOURCE_ID,
NOTE,
DATE_MODIFIED
) VALUES
(
DETAIL_SEQ.NEXTVAL,
:NEW.INPUT_ID,
HostId,
DATA_SOURCE_ID_RET,
'Autogenerate Data Source Insert for Insert Input',
SYSDATE
);
ELSIF UPDATING THEN
SELECT COUNT(DATA_SOURCE_ID) INTO resultcount FROM DETAIL_TABLE WHERE
INPUT_ID=:NEW.INPUT_ID AND DATA_SOURCE_ID=DATA_SOURCE_ID_RET;
IF resultcount>0 THEN
UPDATE DETAIL_TABLE
SET
HOST_ID = HostId,
NOTE ='Autogenerate Data Source Update for Update Input',
DATE_MODIFIED =SYSDATE
WHERE INPUT_ID=:NEW.INPUT_ID
AND DATA_SOURCE_ID=DATA_SOURCE_ID_RET;
ELSE
INSERT INTO DETAIL_TABLE
(
DETAIL_ID,
INPUT_ID,
HOST_ID,
DATA_SOURCE_ID,
NOTE,
DATE_MODIFIED
) VALUES
(
DETAIL_SEQ.NEXTVAL,
:NEW.INPUT_ID,
HostId,
DATA_SOURCE_ID_RET,
'Autogenerate Data Source Insert for Update Input ',
SYSDATE
);
END IF;
--end if insert or update inside update
END IF;
--end IF UPDATING
END LOOP;
Close C_DATA_SOURCES;
COMMIT;
END DATA_DETAIL_TRIG;
--end trigger

Once you you placed your :new variables in quotes, Oracle looses their meaning as special trigger variables and interprets them as regular bind variables. You probably need to use USING clause with your dynamic sql. Something like
query_str:='SELECT :FIRST_NAME||:LAST_NAME
FROM INPUT_TABLE WHERE INPUT_ID=:ID';
EXECUTE IMMEDIATE query_str INTO HostID USING
:NEW.FIRST_NAME,:NEW.LAST_NAME,:NEW.INPUT_ID;
Update.
Now as more details emerge, I wonder why do you need to query at all? From what I can see, you just compose HostID from some values in INPUT_TABLE. But since you already have have trigger attached to the same table, you already have all values you need and can simply do
HostID := :NEW.FIRST_NAME||:NEW.LAST_NAME;
Your approach would only make sense if you query different table. In this case, as I mentioned, you can't quote :NEW or :OLD when you place them in USING. You may compose other value from them, though.

#Alex: Below is the trigger.
create or replace
TRIGGER DATA_DETAIL_TRIG
AFTER INSERT OR UPDATE
ON INPUT_TABLE
FOR EACH ROW
DECLARE
DATA_SOURCE_ID_RET NUMBER;
resultcount NUMBER;
query_str VARCHAR2(512);
using_cl VARCHAR2(512);
HostId VARCHAR2(256);
DATA_SOURCE_ID NUMBER;
INPUT_ID NUMBER(38,0);
AUTOGENERATE_IND VARCHAR2(1);
autogen_const varchar(200);
pragma autonomous_transaction;
CURSOR C_DATA_SOURCES IS
SELECT DATA_SOURCE_ID
FROM DATA_SOURCE_DETAIL
WHERE AUTOGENERATE_IND='Y'
AND DATA_SOURCE_REF.ACTIVE_IND='Y';
BEGIN
OPEN C_DATA_SOURCES;
LOOP
FETCH C_DATA_SOURCES INTO DATA_SOURCE_ID_RET;
EXIT WHEN C_DATA_SOURCES%NOTFOUND;
query_str:=getHostQuery( DATA_SOURCE_ID_RET);
--SELECT :FIRST_NAME||:LAST_NAME||to_char(:DOB,'yyyy/mm/dd')
From INPUT_TABLE WHERE INPUT_ID=:INPUT_ID
using_cl:=getHostUsing(DATA_SOURCE_ID_RET);
--:NEW.FIRST_NAME, :NEW.LAST_NAME, :NEW.DOB, :NEW.INPUT_ID
EXECUTE IMMEDIATE query_str INTO HostId USING using_cl;
IF INSERTING THEN
INSERT INTO DETAIL_TABLE
(
DETAIL_ID,
INPUT_ID,
HOST_ID,
DATA_SOURCE_ID,
NOTE,
DATE_MODIFIED
) VALUES
(
DETAIL_SEQ.NEXTVAL,
:NEW.INPUT_ID,
HostId,
DATA_SOURCE_ID_RET,
'Autogenerate Data Source Insert for Insert Input',
SYSDATE
);
ELSIF UPDATING THEN
SELECT COUNT(DATA_SOURCE_ID) INTO resultcount FROM DETAIL_TABLE WHERE
INPUT_ID=:NEW.INPUT_ID AND DATA_SOURCE_ID=DATA_SOURCE_ID_RET;
IF resultcount>0 THEN
UPDATE DETAIL_TABLE
SET
HOST_ID = HostId,
NOTE ='Autogenerate Data Source Update for Update Input',
DATE_MODIFIED =SYSDATE
WHERE INPUT_ID=:NEW.INPUT_ID
AND DATA_SOURCE_ID=DATA_SOURCE_ID_RET;
ELSE
INSERT INTO DETAIL_TABLE
(
DETAIL_ID,
INPUT_ID,
HOST_ID,
DATA_SOURCE_ID,
NOTE,
DATE_MODIFIED
) VALUES
(
DETAIL_SEQ.NEXTVAL,
:NEW.INPUT_ID,
HostId,
DATA_SOURCE_ID_RET,
'Autogenerate Data Source Insert for Update Input ',
SYSDATE
);
END IF;
--end if insert or update inside update
END IF;
--end IF UPDATING
END LOOP;
Close C_DATA_SOURCES;
COMMIT;
END DATA_DETAIL_TRIG;
--end trigger

Related

Oracle trigger is giving error - active autonomous transaction detected and rolled back

I wrote a trigger which calls a procedure at the end , procedure is having one commit statement .
When i execute trigger then its giving me error "cannot commit in Trigger".
Then i declared PRAGAMA AUTONOMUS_TRANSACTION but that is also giving error "active autonomous transaction detected and rolled back" , "error during execution of trigger"
CREATE OR REPLACE TRIGGER Test_Ord_Update
AFTER
insert or update on test1
for each row
declare
PRAGMA AUTONOMOUS_TRANSACTION;
l_exst number(1);
v_id NUMBER (5);
begin
merge into test2 b
USING dual a
on (:new.id = b.id)
when matched then update set
b.price1 = :new.price1,
b.price2 = :new.price2
when not matched then insert (id, price1, price2)
values (:new.id, :new.price1, :new.price2);
select id into v_id from test2 where price1 = :new.price1;
LOAD_PRICE(v_id);
end;
Whenever we are calling a sub program in a trigger which has commit statement , we must need to write a commit after the line where we called sub program. like below
CREATE OR REPLACE TRIGGER Test_Ord_Update
AFTER
insert or update on test1
for each row
declare
PRAGMA AUTONOMOUS_TRANSACTION;
l_exst number(1);
v_id NUMBER (5);
begin
merge into test2 b
USING dual a
on (:new.id = b.id)
when matched then update set
b.price1 = :new.price1,
b.price2 = :new.price2
when not matched then insert (id, price1, price2)
values (:new.id, :new.price1, :new.price2);
select id into v_id from test2 where price1 = :new.price1;
LOAD_PRICE(v_id);
COMMIT;
end;

Oracle ddl trigger on database

I have question related to my ddl trigger:
create or replace trigger audit_ddl_db_trg after ddl on database
begin
DECLARE
n number;
stmt varchar2(4000);
sql_text ora_name_list_t;
begin
dbms_output.put_line(ora_sql_txt(sql_text));
n := ora_sql_txt(sql_text);
IF nvl(n,200)=200 THEN
Raise_application_error(-20001, 'ora_sql_txt does not catch any statement. sql_txt is not initialized');
ELSE FOR i IN 1..n LOOP
stmt := stmt || sql_text(i);
END LOOP;
dbms_output.put_line(stmt);
END IF;
IF ora_sysevent <> 'TRUNCATE'
THEN
if (ora_sysevent='TRUNCATE')
then
null; -- I do not care about truncate
else
insert into audit_ddl(date_of_change,osuser,current_user,host,terminal,owner,object_type,object_name,ora_sysevent,sqltext)
values(
systimestamp,
sys_context('USERENV','OS_USER') ,
sys_context('USERENV','CURRENT_USER') ,
sys_context('USERENV','HOST') ,
sys_context('USERENV','TERMINAL') ,
ora_dict_obj_owner,
ora_dict_obj_type,
ora_dict_obj_name,
ora_sysevent,
stmt
);
end if;
end if;
end;
end;
Trigger catches everything correctly, i do have everything populated well in my table, but i do have questions related to column current_user. No matter which user schema i use in database to do alter it writes SYS. How to populate this with correct user which has done alter on table?
Thanks!
sys_context('USERENV','CURRENT_USER') returns The name of the database user whose privileges are currently active, as per the oracle documentation.
You can use the SESSION_USER instead of CURRENT_USER.

If exist then update in oracle forms 11g

I am trying to write a code block where record insert if record already exist then update
table. i am trying If (sql%rowcount = 0) then but it's not working in cursor and more one records.
What I tried so far as the code block is
declare
remp_id varchar2(60);
remp_name varchar2(100);
rdesig varchar2(100);
rdept_no number;
rdesig_no number;
rdept_name varchar2(60);
cursor alfa is
select emp_code, emp_name, desig, dept_name, dept_no, desig_no
from emp
where emp_code between :first_code and :second_code;
begin
open alfa;
loop
fetch alfa
into remp_id, remp_name, rdesig, rdept_name, rdept_no, rdesig_no;
exit when alfa%notfound;
update att_reg_mo
set emp_code = remp_id,
emp_name = remp_name,
desig = rdesig,
dept_name = rdept_name,
dept_no = rdept_no,
desig_no = rdesig_no,
att_date = :att_date,
emp_att = :emp_att,
att_type = 'MA',
reg_date = :reg_date
where emp_code between :first_code and :second_code
and reg_date = :reg_date
and att_date = :att_date;
commit;
if (sql%rowcount = 0) then
insert into att_reg_mo
(emp_code,
emp_name,
desig,
dept_name,
att_date,
emp_att,
att_type,
reg_date,
dept_no,
desig_no)
values
(remp_id,
remp_name,
rdesig,
rdept_name,
:att_date,
:emp_att,
'MA',
:reg_date,
rdept_no,
rdesig_no);
end if;
commit;
end loop;
close alfa;
end;
when i am fire the trigger then record is insert but where need to update record it's update with null values
Or you could use something like that:
DECLARE
cursor test is
select 1 as v from dual
union
select 2 as v from dual;
n_var NUMBER;
BEGIN
for rec in test loop
BEGIN
select 1 into n_var from dual where rec.v=2;
DBMS_OUTPUT.PUT_LINE('Please Update Any Table');
EXCEPTION
WHEN no_data_found THEN
DBMS_OUTPUT.PUT_LINE('Please Insert Any Table');
END;
end loop;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE ('Unexpected error');
END;
SQL%attribute always refers to the most recently run SELECT or DML statement. It refreshes as to start from zero after any of transaction statement such as COMMIT, ROLLBACK or SAVEPOINT is issued, for a session in which AUTOCOMMIT is presumed to be OFF by default. Therefore, you always get zero from SQL%ROWCOUNT which is just before the INSERT statement, and keeping inserting to the concerned table during every run of code block.
So, remove the f i r s t COMMIT, removing also keeps the atomicity of the whole transaction, from your code block.
Demo

How to extract value inside with statement?

I want to initialize variable in pl/sql In the following way. But the value of the variable is null. how can I modify this code?
CREATE OR REPLACE PROCEDURE P_TEMP
(
pList0 OUT SYS_REFCURSOR
)
AS
TEMP_COL1 VARCHAR2(100);
TEMP_COL2 VARCHAR2(100);
BEGIN
OPEN cv_1 FOR
WITH TEMP_TABLE(COLUMN_1, COLUMN_2 ,...., COLUMN_N)
AS (SELECT COL_1, COL_2, ... , COL_N)
SELECT COL_1, COL_2
INTO TEMP_COL1, TEMP_COL2
FROM TEMP_TABLE;
DBMS_OUTPUT.PUT_LINE(TEMP_COL1); -- null!!
DBMS_OUTPUT.PUT_LINE(TEMP_COL2); -- null!!
END;
Don't open a cursor; just use the SELECT:
CREATE OR REPLACE PROCEDURE P_TEMP
(
pList0 OUT SYS_REFCURSOR
)
AS
TEMP_COL1 VARCHAR2(100);
TEMP_COL2 VARCHAR2(100);
BEGIN
WITH TEMP_TABLE(COLUMN_1, COLUMN_2 ,...., COLUMN_N) AS (
SELECT COL_1, COL_2, ... , COL_N
FROM your_table -- Need to select from a table.
)
SELECT COLUMN_1, COLUMN_2 -- Use the aliases you set in the sub-query factoring clause.
INTO TEMP_COL1, TEMP_COL2
FROM TEMP_TABLE
WHERE ROWNUM = 1 -- Only want a single row returned into the variables;
-- Or simpler
SELECT COL_1, COL_2
INTO TEMP_COL1, TEMP_COL2
FROM your_table
WHERE ROWNUM = 1 -- Only want a single row returned into the variables;
DBMS_OUTPUT.PUT_LINE(TEMP_COL1);
DBMS_OUTPUT.PUT_LINE(TEMP_COL2);
END;
/
You are missing the FETCH loop from the cursor. The easiest way to do this to use an implicit cursor:
CREATE OR REPLACE PROCEDURE P_TEMP AS
BEGIN
FOR rec IN
(
WITH temp_table(column_1, column_2 ,...., column_n) AS
(SELECT col_1, col_2, ... , col_n)
SELECT col_1, col_2
FROM temp_table
) LOOP
DBMS_OUTPUT.PUT_LINE(rec.col1);
DBMS_OUTPUT.PUT_LINE(rec.col2);
END LOOP;
END;
If, on the other hand, you only want to return the opened cursor, then you must not fetch from it yourself.
If you want to do both, i.e. fetch data yourself and return the cursor, then you must open the cursor twice for the same query. Once to process it yourself, once to give it to the caller to process it.
CREATE OR REPLACE PROCEDURE P_TEMP
(
out_cursor OUT SYS_REFCURSOR
)
AS
v_col1 VARCHAR2(100);
v_col2 VARCHAR2(100);
v_sql VARCHAR2(1000) := 'WITH temp_table ... SELECT col_1, col_2 FROM temp_table';
BEGIN
OPEN out_cursor FOR v_sql;
LOOP
FETCH out_cursor INTO v_col1, v_col2;
EXIT WHEN out_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('col_1: ' || v_col1);
DBMS_OUTPUT.PUT_LINE('col_2: ' || v_col2);
END LOOP;
OPEN out_cursor FOR v_sql;
END;

Oracle: Insert rowtype data into another table

I have one table called event, and created another global temp table tmp_event with the same columns and definition with event. Is it possible to insert records in event to tmp_event using this ?
DECLARE
v_record event%rowtype;
BEGIN
Insert into tmp_event values v_record;
END;
There are too many columns in event table, I want to try this because I don't want to list all the columns.
Forget to mention: I will use this in the trigger, can this v_record be the object :new after insert on EVENT table ?
To insert one row-
DECLARE
v_record event%rowtype;
BEGIN
SELECT * INTO v_record from event where rownum=1; --or whatever where clause
Insert into tmp_event values v_record;
END;
Or a more elaborate version to insert all rows from event-
DECLARE
TYPE t_bulk_collect_test_tab IS TABLE OF event%ROWTYPE;
l_tab t_bulk_collect_test_tab;
CURSOR c_data IS
SELECT *
FROM event;
BEGIN
OPEN c_data;
LOOP
FETCH c_data
BULK COLLECT INTO l_tab LIMIT 10000;
EXIT WHEN l_tab.count = 0;
-- Process contents of collection here.
Insert into tmp_event values v_record;
END LOOP;
CLOSE c_data;
END;
/
In a trigger, yes it is possible but its like the chicken or the egg. You have to initialize every field of the rowtype with the :new column values like-
v_record.col1 := :new.col1;
v_record.col2 := :new.col2;
v_record.col3 := :new.col3;
....
Apparently, the PLSQL examples above cannot be used in a trigger since it would throw a mutating trigger error. And there is no other way for you to get the entire row in the trigger other than accessing each column separately as I explain above, so if you do all this why not directly use :new.col in the INSERT into temp_event itself, will save you a lot of work.
Also since you say it's a lot of work to mention all the columns, (in Oracle 11gR2) here's a quick way of doing that by generating the INSERT statement and executing it dynamically (although not tested for performance).
CREATE OR REPLACE TRIGGER event_air --air stands for "after insert of row"
AFTER INSERT ON EVENT
FOR EACH ROW
L_query varchar2(2000); --size it appropriately
BEGIN
SELECT 'INSERT INTO tmp_event VALUES ('|| listagg (':new.'||column_name, ',')
WITHIN GROUP (ORDER BY column_name) ||')'
INTO l_query
FROM all_tab_columns
WHERE table_name='EVENT';
EXECUTE IMMEDIATE l_query;
EXCEPTION
WHEN OTHERS THEN
--Meaningful exception handling here
END;
There is a way to insert multiple rows into table with %Rowtype.
checkout below example.
DECLARE
TYPE v_test IS TABLE OF TEST_TAB%rowtype;
v_test_tab v_test ;
EXECUTE immediate ' SELECT * FROM TEST_TAB ' bulk collect INTO v_test_tab ;
dbms_output.put_line('v_test_tab.count -->'||v_test_tab.count);
FOR i IN 1..v_test_tab.count
LOOP
INSERT INTO TEST_TAB_1 VALUES v_test_tab
(i
) ;
END LOOP;
END;
sum up to full working excample ...
DECLARE
TYPE t_bulk_collect_test_tab IS TABLE OF event%ROWTYPE;
l_tab t_bulk_collect_test_tab;
CURSOR c_data IS SELECT * FROM event;
BEGIN
OPEN c_data;
LOOP
FETCH c_data
BULK COLLECT INTO l_tab LIMIT 10000;
EXIT WHEN l_tab.count = 0;
FORALL i IN 1..l_tab.count
Insert into tmp_event values l_tab(i);
commit;
END LOOP;
CLOSE c_data;
END;
/

Resources