Can I access :NEW and :OLD dynamically in Oracle PL/SQL? - oracle

I am trying to somehow dynamically access :NEW and :OLD in Oracle SQL. I know that this isn't possible, but I am searching for an easier (and less messier) way than just creating the script for creating the trigger and executing it. My code so far:
--Creating a new type called COLUMN_ARRAY to save multiple column names
--(up to 50) dynamically in an array.
CREATE OR REPLACE TYPE COLUMN_ARRAY AS VARRAY(50) OF VARCHAR2(30);
/
--Creating a procedure which is called in the trigger.
--It inserts the given values into a changelog table.
CREATE OR REPLACE PROCEDURE PRO_CL(
var_changelogTable VARCHAR2,
var_table VARCHAR2,
var_column VARCHAR2,
var_oldValue VARCHAR2,
var_newValue VARCHAR2)
IS
BEGIN
EXECUTE IMMEDIATE '
INSERT
INTO '||var_changelogTable||' VALUES
(
'''',
SYSTIMESTAMP,
'''||var_table||''',
'''||var_column||''',
'''||var_oldValue||''',
'''||var_newValue||'''
)';
END;
/
This creates the trigger. Right now, I iterate over my array and use the tablenames to dynamically initialize the variables that are passed to the procedure, this doesn't work.
CREATE OR REPLACE TRIGGER TR_CL BEFORE
INSERT OR
UPDATE OR
DELETE ON TESTTABLE FOR EACH ROW DECLARE
------------------------------------------------------------------------------
-- These values have to be put in by a user
var_changelogTable VARCHAR2(30) := 'CHANGELOGTABLE';
var_table VARCHAR2(30) := 'TESTTABLE';
var_columns COLUMN_ARRAY := COLUMN_ARRAY('TEST1', 'TEST2');
------------------------------------------------------------------------------
-- These values are dynamic and are produced by the trigger
var_column VARCHAR2(30);
var_oldValue VARCHAR2(4000);
var_newValue VARCHAR2(4000);
------------------------------------------------------------------------------
BEGIN
FOR i IN 1..var_columns.count
LOOP
-- Dynamic variables are initialized
var_column := var_columns(i);
EXECUTE IMMEDIATE 'var_oldValue := :OLD.'||var_column;
EXECUTE IMMEDIATE 'var_newValue := :NEW.'||var_column;
-- The Procedure only is called when the old and new values are different
IF var_oldValue != var_newValue THEN
PRO_CL(var_changelogTable, var_table, var_column, var_oldValue, var_newValue);
END IF;
END LOOP;
END;
/
So my question is: Is there any way to create this trigger dynamically, or do I have to use the messy way and dynamically create a script which then creates the trigger?

you can create the trigger itself dynamically looping over user_tab_columns (for each column there will be 1 line with call to PRO_CL_BESTNR)
oldValue != newValue will not cover NULL value without change, you should pass both values to the log procedure and this shall compare them

Related

Is there an easy to to iterate over all :NEW values from an Oracle database trigger execution?

I am attempting to write a generic trigger that will provide all of the :NEW values for the row inserted. Ultimately I want to turn them into XML and insert the XML string into a binary field on another table.
There are a variable number of columns in each table - many times over 100 fields and over 100 tables in all, so individual mapping to XML per table is extremely time consuming.
Is there a way to reference the :NEW pseudorecord as a collection of column values - or perhaps a way to pass the whole :NEW record to a Stored Procedure that could pass it to a Java function (hosted on the database) that might make the individual values iterable?
I've found an example here:
https://docs.oracle.com/database/121/LNPLS/triggers.htm
Create history table and trigger:
CREATE TABLE tbl_history ( d DATE, old_obj t, new_obj t)
/
CREATE OR REPLACE TRIGGER Tbl_Trg
AFTER UPDATE ON tbl
FOR EACH ROW
BEGIN
INSERT INTO tbl_history (d, old_obj, new_obj)
VALUES (SYSDATE, :OLD.OBJECT_VALUE, :NEW.OBJECT_VALUE);
END Tbl_Trg;
/
This seems to imply there is some sort of way it is storing all of the values as a variable, but this appears to put them directly back into a database table. I want to get the 'text' values of the column values listed.
You can create a stored procedure to create your trigger
for table tbl like
create table tbl (id number, value varchar2(10));
and an history table like
create table tbl_history (d date,id number, value varchar2(10));
you can create your trigger like this
create or replace procedure CREATE_TRIGGER IS
trig_str VARCHAR2(32767);
col_str VARCHAR2(32767) := '(d';
values_str VARCHAR2(32767) := '(sysdate';
begin
trig_str := 'CREATE OR REPLACE TRIGGER Tbl_Trg AFTER UPDATE ON tbl FOR EACH ROW'||chr(10)||
'BEGIN'||chr(10)||chr(9)||'INSERT INTO tbl_history ';
for col in (
SELECT column_name FROM all_tab_columns where table_name = 'TBL'
) loop
col_str := col_str||','||col.column_name;
values_str := values_str||','||':OLD.'||col.column_name;
end loop;
col_str := substr(col_str,1,length(col_str)-1)||')';
values_str := substr(values_str,1,length(values_str)-1)||')';
trig_str := trig_str||col_str||' VALUES '||values_str||';'||chr(10)||'END;';
execute immediate trig_str;
END;
/
With an history table with old and new values it's a bit more complicated but same idea

ORACLE stored procedure - Store query result

I have the following stored procedure:
CREATE OR REPLACE PROCEDURE SP
(
query IN VARCHAR2(200),
CURSOR_ OUT SYS_REFCURSOR
)
AS
row_ PROCESSED_DATA_OBJECT;
processed PROCESSED_DATA_TABLE;
BEGIN
.....
END;
with
CREATE TYPE processed_data_obj AS OBJECT(
id INTEGER,
value FLOAT
);
/
CREATE OR REPLACE TYPE processed_data_table AS TABLE OF processed_data_obj;
/
I call the stored procedure passing the query to be executed as input parameter.
The query is something like that:
SELECT A,B FROM TABLE WHERE
where A,B and TABLE are not fixed (defined at runtime during java program execution), so I don't know their values in advance.
How could I fetch/store each returned row in my structure?
processed PROCESSED_DATA_TABLE;
Thanks
This is one way you can process a dynamically generated query into a user defined type. Note that, in order for this to work, the structure of your query (columns) must match the data type structure of your type (attributes) otherwise you're in for trouble.
CREATE TYPE processed_data_obj AS OBJECT(
ID INTEGER,
VALUE FLOAT,
constructor FUNCTION processed_data_obj RETURN self AS result
);
/
CREATE OR REPLACE TYPE BODY processed_data_obj IS
constructor FUNCTION processed_data_obj RETURN self AS result IS
BEGIN
RETURN;
END;
END;
/
CREATE OR REPLACE TYPE processed_data_table AS TABLE OF processed_data_obj;
/
CREATE OR REPLACE PROCEDURE sp (
p_query IN VARCHAR2
) AS
cursor_ sys_refcursor;
processed processed_data_table := processed_data_table();
BEGIN
OPEN cursor_ FOR p_query;
loop
processed.EXTEND;
processed(processed.count) := processed_data_obj();
fetch cursor_ INTO processed(processed.count).ID, processed(processed.count).VALUE;
exit WHEN cursor_%notfound;
dbms_output.put_line(processed(processed.count).ID||' '||processed(processed.count).VALUE);-- at this point do as you please with your data.
END loop;
CLOSE cursor_; -- always close cursor ;)
processed.TRIM; -- or processed.DELETE(processed.count);
END sp;
I noticed that, originally, you did put CURSOR_ as an output parameter in your stored procedure, if that is still your goal, you can create your procedure as:
CREATE OR REPLACE PROCEDURE sp (
p_query IN VARCHAR2,
cursor_ out sys_refcursor
) AS
processed processed_data_table := processed_data_table();
BEGIN
OPEN cursor_ FOR p_query;
loop
processed.EXTEND;
processed(processed.count) := processed_data_obj();
fetch cursor_ INTO processed(processed.count).ID, processed(processed.count).VALUE;
exit WHEN cursor_%notfound;
dbms_output.put_line(processed(processed.count).ID||' '||processed(processed.count).VALUE);-- at this point do as you please with your data.
END loop;
-- cursor remains open
processed.TRIM; -- or processed.DELETE(processed.count);
END sp;
In this case just be conscious about handling your cursor properly and always close it when you're done with it.

Taking multiple value from Oracle Apex page and store it in an table

I have developed 1 apex page where I have one text field. I will insert multiple values by separating it through a comma. I want that when I hit the submit button then it should separate the values on the basis of comma and should insert individually in different rows. For example if I pass "abc,cde,efgh,ijhk,gygg" in my textfield then it should insert "abc" in one row,"cde" in another row and so on. and also it should not through any error when I insert only one value.
I am able to store the value for only one data as I have created a procedure and that procedure takes only one data but I am not getting an idea of if I pass multiple values by separating it via comma then it should insert. I am posting my procedure here.
create or replace procedure Exclude_items(
p_analyze_name in varchar2,
p_material_number in varchar2
)
as
p_analyze_id number;
begin
select analyze_id
into p_analyze_id
from analyzes
where table_name = p_analyze_name;
insert into p20da.test_vishal(ANALYZE_ID,MATNR,) values(p_analyze_id,p_material_number)
end;
Here matnr will be having multiple values separated by comma as it is a text field in apex page but analyze_id will be constant.
I want to write the procedure so that it could separate whenever a comma comes and should not through any error if I only insert one value.
You should take advantage of STRING_TO_TABLE function of APEX_UTIL package. Add below code to Submit process, your exclude_items procedure stays the same.
DECLARE
L_MATERIAL_NUMBER APEX_APPLICATION_GLOBAL.VC_ARR2;
BEGIN
L_MATERIAL_NUMBER := APEX_UTIL.STRING_TO_TABLE(:P2_MATERIAL_NUMBER,',');
FOR I IN 1..L_MATERIAL_NUMBER.COUNT
LOOP
EXCLUDE_ITEMS('tablea', L_MATERIAL_NUMBER(I));
END LOOP;
END;
More Information on API Package Function --> http://docs.oracle.com/cd/E11882_01/appdev.112/e12510/apex_util.htm#CHDFEEJD
How about something like:
create or replace procedure exclude_items( p_analyze_name in varchar2, p_material_number in varchar2 )
as
comma_pos number;
sub_name varchar2(4000);
temp_name varchar2(4000);
p_analyze_id number;
begin
select analyze_id
into p_analyze_id
from analyzes
where table_name = p_analyze_name;
temp_name := p_material_number;
LOOP
comma_pos := instr( temp_name, ',' );
exit when comma_pos = 0;
sub_name := substr( temp_name,1,comma_pos-1 );
insert into p20da.test_vishal(ANALYZE_ID,MATNR,) values(p_analyze_id,sub_name)
temp_name := substr( temp_name, comma_pos + 1 );
END LOOP;
insert into p20da.test_vishal(ANALYZE_ID,MATNR,) values(p_analyze_id,temp_name)
end;
/

How do I insert variable values in to a table record in an oracle procedure

How do I insert variable values in to a table record in an oracle procedure?
if pCount1=0 then
insert into opions(qid,otext,oflag)
(rec.pQid, rec.pOptions, rec.pCorrect);
end if;
where rec.* are the variables of the procedure
Are you looking for one of these statements (which are functionally equivalent)?
insert into opions(qid,otext,oflag)
values (rec.pQid, rec.pOptions, rec.pCorrect);
insert into opions(qid,otext,oflag)
select rec.pQid, rec.pOptions, rec.pCorrect from dual;
This assumes that rec is defined somewhere else in the stored procedure. Otherwise, you need to use the second form with rec defined in the from clause.
Try:
CREATE OR REPLACE PROCEDURE SOME_PROC AS
nQid NUMBER;
strOptions VARCHAR2(100);
strCorrect VARCHAR2(1);
BEGIN
nQid := 1;
strOptions := 1234;
strCorrect := 'Y';
INSERT INTO OPIONS(qid, otext, oflag)
VALUES (nQid, strOptions, strCorrect);
END SOME_PROC;
Best of luck.

pass pl/sql record as arguement to procedure

How to pass pl/sql record type to a procedure :
CREATE OR REPLACE PACKAGE BODY PKGDeleteNumber
AS
PROCEDURE deleteNumber (
list_of_numbers IN List_Numbers
)
IS
i_write VARCHAR2(5);
BEGIN
--do something
END deleteNumber;
END PKGDeleteNumber;
/
In this procedure deleteNumber I have used List_Numbers, which is a record type. The package declaration for the same is :
CREATE OR REPLACE PACKAGE PKGDeleteNumber
AS
TYPE List_Numbers IS RECORD (
IID NUMBER
);
TYPE list_of_numbers IS TABLE OF List_Numbers;
PROCEDURE deleteNumber (
list_of_numbers IN List_Numbers
);
END PKGDeleteNumber;
I have to execute the procedure deleteNumber passing a list of values. I inserted numbers in temp_test table, then using a cursor U fetched the data from it :
SELECT *
BULK COLLECT INTO test1
FROM temp_test;
Now, to call the procedure I am using
execute immediate 'begin PKGDELETENUMBER.DELETENUMBER(:1); end;'
using test1;
I have tried many other things as well(for loop, dbms_binding, etc). How do I pass a pl/sql record type as argument to the procedure?
EDIT:
Basically, I want to pass a list of numbers, using native dynamic sql only...
adding the table temp_test defn (no index or constraint):
create table test_temp (
IID number
);
and then inserted 1,2,3,4,5 using normal insert statements.
For this solution,
In a package testproc
CREATE TYPE num_tab_t IS TABLE OF NUMBER;
CREATE OR REPLACE PROCEDURE my_dyn_proc_test (p_num_array IN num_tab_t) AS
BEGIN
dbms_output.put_line(p_num_array.COUNT);
END;
/
this is called from sql prompt/toad
DECLARE
v_tab testproc.num_tab_t := testproc.num_tab_t(1, 10);
BEGIN
EXECUTE IMMEDIATE 'BEGIN testproc.my_dyn_proc_test(:1); END;' USING v_tab;
END;
this will not work.This shows error.I am not at my workstation so am not able to reproduce the issue now.
You can't use RECORD types in USING clause of EXECUTE IMMEDIATE statement. If you just want to pass a list of numbers, why don't you just use a variable of TABLE OF NUMBER type? Check below example:
CREATE TYPE num_tab_t IS TABLE OF NUMBER;
CREATE OR REPLACE PROCEDURE my_dyn_proc_test (p_num_array IN num_tab_t) AS
BEGIN
dbms_output.put_line(p_num_array.COUNT);
END;
/
DECLARE
v_tab num_tab_t := num_tab_t(1, 10);
BEGIN
EXECUTE IMMEDIATE 'BEGIN my_dyn_proc_test(:1); END;' USING v_tab;
END;
Output:
2
Edit
Try this:
CREATE TYPE num_tab_t IS TABLE OF NUMBER;
CREATE OR REPLACE PACKAGE testproc AS
PROCEDURE my_dyn_proc_test (p_num_array IN num_tab_t);
END;
/
CREATE OR REPLACE PACKAGE BODY testproc AS
PROCEDURE my_dyn_proc_test (p_num_array IN num_tab_t) AS
BEGIN
dbms_output.put_line(p_num_array.COUNT);
END;
END;
/
DECLARE
v_tab num_tab_t := num_tab_t(1, 10);
BEGIN
EXECUTE IMMEDIATE 'BEGIN testproc.my_dyn_proc_test(:1); END;' USING v_tab;
END;
Use an object type. object types are visible to all packages

Resources