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

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.

Related

How to access and query objects passed as parameter to a procedure while converting from Oracle to postgresql

I have a procedure in Oracle that I need to convert to Postgresql and need help on it. It paases a collection of objects in a procedure.The procedure then checks if each object is present in a database table or not and if present it gives a message that , that specific element is found/present. if some element that is paassed to the procedure is not present in the table, the procedure just doesnt do anything. I have to write equivalent of that in postgresql. I think the heart of the issue is this statement:
SELECT COUNT (*)
INTO v_cnt
FROM **TABLE (p_cust_tab_type_i)** pt
WHERE pt.ssn = cc.ssn;
In Oracle a collection can be treated as a table and one can query it but I dont know how to do that in postgresql. The code to create the table, add data, create the procedure, call the procedure by passing the collection (3 objects) and output of that is posted below. Can someone suggest how this can be done in postgresql?
Following the oracle related code and details:
--create table
create table temp_n_tab1
(ssn number,
fname varchar2(20),
lname varchar2(20),
items varchar2(100));
/
--add data
insert into temp_n_tab1 values (1,'f1','l1','i1');
--SKIP no. ssn no. 2 intentionally..
insert into temp_n_tab1 values (3,'f3','l3','i3');
insert into temp_n_tab1 values (4,'f4','l4','i4');
insert into temp_n_tab1 values (5,'f5','l5','i5');
insert into temp_n_tab1 values (6,'f6','l6','i6');
commit;
--create procedure
SET SERVEROUTPUT ON
CREATE OR REPLACE PROCEDURE temp_n_proc (
p_cust_tab_type_i IN temp_n_customer_tab_type)
IS
t_cust_tab_type_i temp_n_customer_tab_type;
v_cnt NUMBER;
v_ssn temp_n_tab1.ssn%TYPE;
CURSOR c
IS
SELECT ssn
FROM temp_n_tab1
ORDER BY 1;
BEGIN
--t_cust_tab_type_i := p_cust_tab_type_i();
FOR cc IN c
LOOP
SELECT COUNT (*)
INTO v_cnt
FROM TABLE (p_cust_tab_type_i) pt
WHERE pt.ssn = cc.ssn;
IF (v_cnt > 0)
THEN
DBMS_OUTPUT.put_line (
'The array element '
|| TO_CHAR (cc.ssn)
|| ' exists in the table.');
END IF;
END LOOP;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.PUT_LINE (SQLERRM);
END;
/
--caller proc
SET SERVEROUTPUT ON
declare
array temp_n_customer_tab_type := temp_n_customer_tab_type();
begin
for i in 1 .. 3
loop
array.extend;
array(i) := temp_n_cust_header_type( i, 'name ' || i, 'lname ' || i,i*i*i*i );
end loop;
temp_n_proc( array );
end;
/
caller proc output:
The array element 1 exists in the table.
The array element 3 exists in the table.
When you create a table in Postgres, a type with the same name is also created. So you can simply pass an array of the table's type as a parameter to the function.
Inside the function you can then use unnest() to treat the array like a table.
The following is the closest match to your original Oracle code:
create function temp_n_proc(p_cust_tab_type_i temp_n_tab1[])
returns void
as
$$
declare
l_rec record;
l_msg text;
l_count integer;
BEGIN
for l_rec in select t1.ssn
from temp_n_tab1 t1
loop
select count(*)
into l_count
from unnest(p_cust_tab_type_i) as t
where t.ssn = l_rec.ssn;
if l_count > 0 then
raise notice 'The array element % exist in the table', l_rec.ssn;
end if;
end loop;
END;
$$
language plpgsql;
The row-by-row processing is not a good idea to begin with (neither in Postgres, nor in Oracle). It would be a lot more efficient to get the existing elements in a single query:
create function temp_n_proc(p_cust_tab_type_i temp_n_tab1[])
returns void
as
$$
declare
l_rec record;
l_msg text;
BEGIN
for l_rec in select t1.ssn
from temp_n_tab1 t1
where t1.ssn in (select t.ssn
from unnest(p_cust_tab_type_i) as t)
loop
raise notice 'The array element % exist in the table', l_rec.ssn;
end loop;
return;
END;
$$
language plpgsql;
You can call the function like this:
select temp_n_proc(array[row(1,'f1','l1','i1'),
row(2,'f2','l2','i2'),
row(3,'f3','l3','i3')
]::temp_n_tab1[]);
However a more "Postgres" like and much more efficient way would be to not use PL/pgSQL for this, but create a simple SQL function that returns the messages as a result:
create or replace function temp_n_proc(p_cust_tab_type_i temp_n_tab1[])
returns table(message text)
as
$$
select format('The array element %s exist in the table', t1.ssn)
from temp_n_tab1 t1
where t1.ssn in (select t.ssn
from unnest(p_cust_tab_type_i) as t)
$$
language sql;
This returns the output of the function as a result rather than using the clumsy raise notice.
You can use it like this:
select *
from temp_n_proc(array[row(1,'f1','l1','i1'),
row(2,'f2','l2','i2'),
row(3,'f3','l3','i3')
]::temp_n_tab1[]);

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

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

how to use date in where clause while inserting values into table using PLSQL store procedure

This is the code i used in stored procedure;
CREATE OR REPLACE PROCEDURE MY_STORE_PROCEDURE (new_date in date)
IS
BEGIN
execute immediate 'INSERT INTO TEMP_1 ( ID CHAR(10),
A_CNT NUMBER,
JOIN_DT DATE,
)
SELECT
L1.ID,
L1.A_CNT,
L1.JOIN_DT,
FROM ACTVY_1 L1
WHERE L1.JOIN_DT = new_date';
END;
===========================================================
Below is the code i used to call store procedure with passing value. value is date which store procedure reciece and used to pull date from a table. but it is giving me error.
DECLARE
a_date DATE;
BEGIN
a_date :=to_DATE ('01-NOV-2013', 'DD-MON-YYYY');
MY_STORE_PROCEDURE(a_date);
END;
Please suggest is there any syntax error or what is issue.
Based on your example, there is no reason to use dynamic SQL. You also have a bunch of errors. Try this:
CREATE OR REPLACE PROCEDURE MY_STORE_PROCEDURE (new_date IN DATE)
IS
BEGIN
INSERT INTO TEMP_1 (ID, A_CNT, JOIN_DT)
SELECT L1.ID, L1.A_CNT, L1.JOIN_DT
FROM ACTVY_1 L1
WHERE L1.JOIN_DT = new_date;
END;

Inserting a RefCursor into a table

I have a simple function where I run a stored procedure that returns a RefCursor and I try to use that RefCursor to insert data to a temporary table. I get the following error when trying to do so:
SQL Error: ORA-00947: not enough values
I know for a fact that the refcursor returns exactly the same number of values as the temporary table has, correct column names, their order and their type. I ran print RefCursor and I can see all of the data. Here's the code:
var r refcursor;
EXEC SCHEMA.PACKAGE.SPROC(:r);
insert into SCHEMA.TEMP_TABLE
values
(r);
I have to add that the stored procedure has a refcursor defined as a OUT parameter so it returns a correct type. Using print r; prints the correct data.
What am I doing wrong?
EDIT:
Based on a suggestion I tried to use a fetch to a rowtype variable, but getting Invalid Number exception whenever I attempt to fetch a row:
DECLARE
cur SYS_refcursor;
rec SCHEMA.TEMP_TABLE%rowtype;
begin
SCHEMA.PACKAGE.SPROC( cur );
LOOP
FETCH cur INTO rec;
EXIT WHEN cur%NOTFOUND;
INSERT INTO SCHEMA.TEMP_TABLE
VALUES rec;
END LOOP;
EXCEPTION
WHEN INVALID_NUMBER THEN
DBMS_output.put_line(rec.move_id);
end;
I added the exception block to see which row is failing and needless to say it is the first one. The stored procedure I run returns a refcursor of a select query from multiple tables. The temporary table defined as the exact copy of the refcursor columns and their types. Not sure what could be causing the exception.
You can't insert into a table from a refcursor. You could write a procedure that fetches from the cursor and inserts into the table. If schema.package.sproc returns a ref cursor of temp_table%rowtype, you could do something like
DECLARE
cur sys_refcursor;
rec schema.temp_table%rowtype;
BEGIN
schema.package.sproc( cur );
LOOP
FETCH cur INTO rec;
EXIT WHEN cur%NOTFOUND;
INSERT INTO schema.temp_table
VALUES rec;
END LOOP;
END;
You can use LOOP + FETCH to filter the row in SYS_REFCURSOR.
Exit the LOOP using "EXIT WHEN ref_%notfound;"
Example: Have 2 functions.
FUNCTION get_data
RETURN SYS_REFCURSOR
is
s varchar2(2000);
ref_ SYS_REFCURSOR;
begin
s := 'select username, password, email from user_info where id < 100';
OPEN ref_ FOR s;
return ref_;
end;
FUNCTION load_data_in_table
RETURN varchar2
is
s varchar2(2000);
puser_name varchar2(2000);
ppassword varchar2(2000);
pemail varchar2(2000);
ref_ SYS_REFCURSOR;
begin
ref_ := get_data();
LOOP
--process_record_statements;
FETCH ref_ into puser_name, ppassword, pemail;
s := 'INSERT INTO TEST_USER_EXP VALUES(:user_name, :password, :email)';
EXECUTE IMMEDIATE s USING puser_name, ppassword, pemail;
EXIT WHEN ref_%notfound;
END LOOP;
commit;
return 'OK';
end;

Using OLD and NEW object for dynamic operations inside trigger

I want to know whether I can use the OLD and NEW objects for dynamic operations inside trigger.
What I am looking for is something like this :-
ABC is a table for which I need to write Trigger.
TracK_Table maintains list of columns for table which need to be tracked (logged).
f_log is a function that inserts changes in data into a tracking(log) table.
CREATE OR REPLACE TRIGGER trg_TRACK
AFTER INSERT OR UPDATE OR DELETE ON ABC
FOR EACH ROW
declare
v_old_val varchar2(1000);
v_new_val varchar2(1000);
n_ret int;
n_id varchar(50);
cursor cur_col is
SELECT COLUMN_NAME,
TABLE_name
FROM track_TABLE
WHERE upper(TABLE_NAME) = upper('ABC')
AND exists (select cname
from col
where UPPER(tname) =upper('ABC')
and upper(cname)=upper(COLUMN_NAME))
AND upper(allow) = 'Y';
begin
n_id:= :old.id;
for i_get_col in c_get_col
loop
execute immediate
'begin
:v_old_val:= select '||i_get_col.column_name ||'
from '||:old ||'
where id = '||n_id ||';
end;' using out v_old_val;
execute immediate
'begin
:v_new_val:= select '||i_get_col.column_name ||'
from '||:new ||'
where id = '||n_id ||';
end;' using out v_new_val;
n_ret := f_log(n_id,i_get_col.column_name,v_old_val,v_new_val);
end loop;
end;
/
One Option: Push the logic to check if a column is being tracke into the f_log procedure and then pass across all of the columns.
For example, if your track_Table holds (table_name, column_name, allow) values for each column that you want to trackm then something like this
CREATE OF REPLACE PROCEDURE f_log( p_id varchar2
,p_table_name varchar2
,p_column_name varchar2
,p_old_val varchar2
,p_new_val varchar2)
as
l_exists number;
cursor chk_column_track IS
SELECT 1
FROM track_TABLE
WHERE upper(TABLE_NAME) = upper(p_table_name)
AND UPPER(column_name) = upper(p_column_name)
AND upper(allow) = 'Y';
begin
open chk_column_track;
fetch chk_column_track into l_exists;
if chk_column_track%found then
--do the insert here
end if;
close chk_column_track;
end;
/
CREATE OR REPLACE TRIGGER trg_TRACK
AFTER INSERT OR UPDATE OR DELETE ON ABC
FOR EACH ROW
DECLARE
n_id varchar(50);
BEGIN
n_id := NVL(:old.id, :new.id);
-- send all of the values to f_log and have it decide whether to save them
f_log(:old.id,'COL1',:old.col1,:new.col1);
f_log(:old.id,'COL2',:old.col2,:new.col2);
f_log(:old.id,'COL3',:old.col3,:new.col3);
...
END;
And for goodness sake, upper-case the values in your track_table on insert so that you don't have to UPPER() the stored values thus making any index on those values useless!
Now, this will chew up some resources checking each column name on each operation, but if you are not running high-volumes then it might be manageable.
Otherwise you will need a more elegant solution. Like leveraging the power of collections and the TABLE() clause to do the track_table lookup in a bulk operation. Bear in mind that I am away from my database at the moment, so I have not test-compiled this code.
CREATE OR REPLACE TYPE t_audit_row AS OBJECT (
p_table_name varchar2(30)
,p_column_name varchar2(30)
,p_id varchar2(50)
,p_old_val varchar2(2000)
,p_new_val varchar2(2000)
);
CREATE OR REPLACE TYPE t_audit_row_table AS TABLE OF t_audit_row;
CREATE OR REPLACE PROCEDURE f_log (p_audit_row_table t_audit_Row_table)
AS
begin
-- see how we can match the contents of the collection to the values
-- in the table all in one query. the insert is just my way of showing
-- how this can be done in one bulk operation. Alternately you could make
-- the select a cursor and loop through the rows to process them individually.
insert into my_audit_log (table_name, column_name, id, old_val, new_val)
select p_table_name
,p_column_name
,p_id
,p_old_val
,p_new_val
FROM track_TABLE TT
,table(p_audit_row_table) art
WHERE tt.TABLE_NAME = art.p_table_name
AND tt.column_name = art.p_column_name
AND tt.allow = 'Y';
end;
/
CREATE OR REPLACE TRIGGER trg_TRACK
AFTER INSERT OR UPDATE OR DELETE ON ABC
FOR EACH ROW
DECLARE
l_id varchar(50);
l_audit_table t_audit_row_table;
BEGIN
l_id := NVL(:old.id, :new.id);
-- send all of the values to f_log and have it decide whether to save them
l_audit_table := t_audit_row_table (
t_audit_row ('ABC','COL1',l_id, :old.col1, :new.col1)
,t_audit_row ('ABC','COL2',l_id, :old.col2, :new.col2)
,t_audit_row ('ABC','COL3',l_id, :old.col3, :new.col3)
,...
,t_audit_row ('ABC','COLn',l_id, :old.coln, :new.coln)
);
f_log(l_audit_table);
end;
/
No, you cannot access the OLD and NEW pseudo-variables dynamically. What you can do is use your track_table data in a script or procedure to generate static triggers that look like:
CREATE OR REPLACE TRIGGER trg_TRACK
AFTER INSERT OR UPDATE OR DELETE ON ABC
FOR EACH ROW
DECLARE
n_id varchar(50);
BEGIN
n_id := NVL(:old.id, :new.id);
f_log(:old.id,'COL1',:old.col1,:new.col1);
f_log(:old.id,'COL3',:old.col3,:new.col3);
...
END;
So if the data in the TRACK_CHANGES table changes you just have to re-generate the triggers.

Resources