ORACLE sql dynamic variables in stored procedure - oracle

i want to add a variable to a stored procedure but i want to be able to run the stored procedure in one of 2 different ways:
- if it is null, then it runs everything otherwise just matching records
- if it is null, then make it default to a set value otherwise just matching records
var countyvar varchar2(50);
begin
if countyvar is null then 'yyyy';
end if;
end;
OR
var countyvar varchar2(50);
begin
if countyvar is null then run script for all locations;
if countyvar is 'yyyy' then run script for only that location;
end if;
end;
used here:
select locations, count(accountID) from dim_locations where location_name = :COUNTYVAR group by locations;

I don't know if this helps you, but you can use the "DEFAULT" keyword in the procedure parameter declaration. For example:
PROCEDURE Get_emp_names (Dept_num IN NUMBER DEFAULT 20)
IS ...
And then you can control the flow of your procedure from inside. Just check if "countyvar" is null or not inside the procedure.
Check this also: Default Values to Stored Procedure in Oracle

Just do this ... watch for index performance however, it may or may not perform a little slower than expected depending on your data size and such ... test it out and tune a little as needed.
select locations, count(accountID)
from dim_locations
where ( :COUNTYVAR IS NULL
OR location_name = :COUNTYVAR )
group by locations;
[[Explanation of DEFAULT option mentioned in other answer ]]
DEFAULT option only kicks in when the parameter is not provided, not when it's NULL ... here's a test sample showing the behaviour - try it out ;)
set serverout on
declare
lv_var varchar2(10);
procedure p_test ( in_parm IN varchar2 default 10 )
is
begin
dbms_output.put_line ( 'in_parm is: ' || nvl(in_parm,'<<NULL>>') );
end;
begin
p_test ( 123 );
p_test ();
p_test ( NULL );
lv_var := 234;
p_test ( lv_var );
lv_var := NULL;
p_test ( lv_var );
end;
/
in_parm is: 123
in_parm is: 10
in_parm is: <<NULL>>
in_parm is: 234
in_parm is: <<NULL>>

Related

update query adding old data too even after changing id

Create a procedure that accepts 2 parameters represented the inv_id, and the percentage increase in price. The pseudo function should first update the database with the new price then return the new price and the quantity on hand.
Create a second procedure called L4Q3 that accepts the inv_id and the percentage increase in price. The procedure will use the old procedure to display the new value of the inventory (hint: value = price X quantity on hand)
CREATE OR REPLACE PROCEDURE ex3 (p_inv_id IN NUMBER, p_change IN NUMBER,
p_new_price OUT NUMBER, p_qoh OUT NUMBER)
AS
v_new_price NUMBER(6,2);
v_qoh NUMBER(6,2);
BEGIN
UPDATE inventory
SET inv_price = (SELECT inv_price + (inv_price*(p_change/100))
FROM inventory
WHERE inv_id = p_inv_id);
COMMIT;
SELECT inv_price, inv_qoh
INTO p_new_price, p_qoh
FROM inventory
WHERE inv_id = p_inv_id;
COMMIT;
v_qoh := p_qoh;
v_new_price := p_new_price;
DBMS_OUTPUT.PUT_LINE('hello'||v_new_price);
END;
/
CREATE OR REPLACE PROCEDURE use_ex3 ( p_inv_id NUMBER, p_change NUMBER)
AS
v_new_price NUMBER(6,2);
v_qoh NUMBER(6,2);
v_value NUMBER(10,2);
BEGIN
ex3(p_inv_id, p_change, v_new_price, v_qoh);
v_value := v_new_price*v_qoh;
DBMS_OUTPUT.PUT_LINE('value is:'||v_value);
END;
/
Consider converting your procedures like those below :
SQL> set serveroutput on;
SQL> CREATE OR REPLACE PROCEDURE ex3(p_inv_id IN inventory.inv_id%type,
p_change IN NUMBER,
p_new_price OUT inventory.inv_price%type,
p_qoh OUT inventory.inv_qoh%type) AS
BEGIN
UPDATE inventory
SET inv_price = inv_price * ( 1 + (p_change / 100) )
WHERE inv_id = p_inv_id
RETURNING inv_price, inv_qoh
INTO p_new_price, p_qoh;
DBMS_OUTPUT.PUT_LINE('hello '|| p_new_price);
END;
/
SQL> CREATE OR REPLACE PROCEDURE use_ex3(p_inv_id inventory.inv_id%type, p_change NUMBER) AS
v_new_price inventory.inv_price%type;
v_qoh inventory.inv_qoh%type;
v_value NUMBER(10, 2);
BEGIN
ex3(p_inv_id, p_change, v_new_price, v_qoh);
v_value := v_new_price * v_qoh;
DBMS_OUTPUT.PUT_LINE('value is: '|| v_value);
END;
the core issue is
missing filter WHERE inv_id = p_inv_id in the UPDATE statement.
i.e. not restricted to only one inv_id
Moreover considering below matters will make your code better :
don't need a subquery for the SET clause, just an assignment needed as inv_price = inv_price * ( 1 + (p_change / 100) )
had better defining variable types for columns as
inventory.<column_name>%type
extra local variables are not needed such as v_new_price, out
parameters of the procedures such as p_new_price might be used as
the assignment targets
SELECT statement after UPDATE is not needed, using RETURNING
INTO is enough
don't forget to use set serveroutput on to print out the results
I think it's a good habit to exclude commit in individual program
units to provide transaction integrity for data consistency in the
tables which got DML. Prefer keep only one commit inside the caller
application at the end of all statements and program units.

Can we use a table type parameter as a default null parameter in PLSQL?

I have a record type as follows,
TYPE x_Rec IS RECORD(
master_company x_tab.master_company%TYPE,
report_trans_type x_tab.report_trans_type%TYPE,
balance_version_id x_tab.balance_version_id%TYPE,
reporting_entity x_tab.reporting_entity%TYPE,
year_period_from x_tab.year_period%TYPE,
year_period_to x_tab.year_period%TYPE,
journal_id x_tab.journal_id%TYPE,
row_id x_tab.row_id%TYPE);
and I have created a table type using this record:
TYPE x_rec_tab IS TABLE OF x_Rec INDEX BY PLS_INTEGER;
I want to use this table type in a procedure as a default null parameter.
PROCEDURE x_Balance___(x_param IN NUMBER,
x_rec_ IN x_rec_tab default null)
IS
BEGIN
...My code
END;
It gives the following error message
PLS-00382: expression is of the wrong type
I resolved this by using CAST(null as /*your_type*/) in the Procedure's signature.
For instance, in your case, it will be something like this:
PROCEDURE x_Balance (x_param IN NUMBER,
x_rec_ IN x_rec_tab default cast(null as x_rec_tab))
Then, within the procedure, you just need to check if x_rec_ has elements by using the count method.
This way works for me.
You can't do that with an associative array, as that can never be null. You would get the same error if you tried to assign null to a variable of type x_rec_tab. They also don't have constructors, so you can't use an empty collection instead.
You can do this will a varray or more usefully for your situation a nested table:
create or replace package p42 as
TYPE x_Rec IS RECORD(
master_company x_tab.master_company%TYPE,
report_trans_type x_tab.report_trans_type%TYPE,
balance_version_id x_tab.balance_version_id%TYPE,
reporting_entity x_tab.reporting_entity%TYPE,
year_period_from x_tab.year_period%TYPE,
year_period_to x_tab.year_period%TYPE,
journal_id x_tab.journal_id%TYPE,
row_id x_tab.row_id%TYPE);
-- no index-by clause, so nested table not associative array
TYPE x_rec_tab IS TABLE OF x_Rec;
end p42;
/
Package P42 compiled
show errors
No errors.
create or replace package body p42 as
PROCEDURE x_Balance___(x_param IN NUMBER,
x_rec_ IN x_rec_tab default null)
IS
BEGIN
--...My code
null;
END;
PROCEDURE dummy IS
l_rec_tab x_rec_tab;
BEGIN
l_rec_tab := null;
END;
end p42;
/
Package Body P42 compiled
show errors;
No errors.
You could also default to an empty collection instead:
PROCEDURE x_Balance___(x_param IN NUMBER,
x_rec_ IN x_rec_tab default x_rec_tab())
IS
...
That doesn't really help you much if you have other code that relies on the type being an associative array of course.
Old question but still might be helpful.
You can create a function:
function empty_tab
return x_rec_tab
as
l_tab x_rec_tab;
begin
return l_tab;
end empty_tab;
This way you can (notice that empty_tab is used as default parameter):
PROCEDURE x_Balance___(x_param IN NUMBER,
x_rec_ IN x_rec_tab default empty_tab)
IS
BEGIN
...My code
END;
This is a repeat of #ManuelPerez answer, but I just feel that it could have been explained better.
Create this procedure, casting your optional variable to your datatype like this:
CREATE OR REPLACE PROCEDURE Test_Procedure (
txt_ IN VARCHAR2,
col_formats_ IN dbms_sql.varchar2a DEFAULT cast(null as dbms_sql.varchar2a) )
IS BEGIN
Dbms_Output.Put_Line (txt_);
FOR i_ IN 1 .. 10 LOOP
IF col_formats_.EXISTS(i_) THEN
Dbms_Output.Put_Line (i_ || ' Exists');
ELSE
Dbms_Output.Put_Line (i_ || ' DOES NOT Exist');
END IF;
END LOOP;
END Test_Procedure;
The reason this beats the accepted answer is that it doesn't require you to change the datatype of the incoming variable. Depending on your circumstance, you may not have the flexibility to do that.
Now call your procedure like this if you have a variable to feed the procedure:
DECLARE
txt_ VARCHAR2(100) := 'dummy';
arr_ dbms_sql.varchar2a;
BEGIN
arr_(4) := 'another dummy';
Test_Procedure (txt_, arr_);
END;
Or like this if you don't:
DECLARE
txt_ VARCHAR2(100) := 'dummy';
BEGIN
Test_Procedure (txt_);
END;
Your output will look something like this:
dummy
1 DOES NOT Exist
2 DOES NOT Exist
3 DOES NOT Exist
4 Exists
5 DOES NOT Exist
6 DOES NOT Exist
7 DOES NOT Exist
8 DOES NOT Exist
9 DOES NOT Exist
10 DOES NOT Exist

referencing a variable from another procedure in plsql

I have written a package :
CREATE OR REPLACE
PACKAGE BODY NEW_HIRE_PKG
AS
PROCEDURE load_emp(
errbuf OUT VARCHAR2,
retcode OUT VARCHAR2 )
AS
CURSOR cur_person_info
IS
SELECT * FROM table_abc;
CURSOR cur_person_adr
IS
SELECT * FROM table_adr;
l_person_id NUMBER;
l_emp_num NUMBER;
lv_add_type VARCHAR2(100);
lv_address_line1 VARCHAR2(100);
BEGIN
FOR person_info_rec IN cur_address_info
LOOP
hr_employee_api.create_employee ( p_validate => FALSE,
--INPUT Parameter
P_HIRE_DATE =>person_info_rec.DATE_START,
-- output
p_employee_number => lc_employee_number, p_person_id => ln_person_id );
END LOOP ;
END;
PROCEDURE load_add(
errbuf OUT VARCHAR2,
retcode OUT VARCHAR2 )
as
ln_person_id number;
BEGIN
FOR address_info_rec IN cur_address_info
LOOP
BEGIN
hr_person_address_api.create_person_address
(p_validate => FALSE,
p_effective_date => TRUNC(SYSDATE),
p_person_id=> ln_person_id,
--output
p_address_type => lv_add_type,
p_address_line1 => lv_address_line1);
end;
end loop;
end;
end;
Now in the procedure the load_add there is a variable ln_person_id which should be the person ids generated in the procedure load_emp. I want to pass it in this procedure one by one. Can i do it by making ln_person_id an object ?
Judging by your code you have two concurrent programs - one that calls load_emp() and one that calls load_add(). If that's really the structure you want then the two calls will run in separate sessions and there's nothing you can do to pass a variable from one to the other. The best you could do would be to hold all the person_id values from load_emp in a custom table. The data could then later be consumed by load_add().
However I would restructure your package. Why not call load_add() from within the LOOP in load_emp() ?

Table variable as in parameter to populate a table in oracle Stored procedure

Mostly I avoid table variables as input parameters for a stored procedure. Because I do not know how to handle them, but in this case I have no other option. I have a requirement where hundreds of records will be passed on to database from Oracle Agile PLM. What I have to do is to populate a table from the input records/list. For accomplishing this I have developed an object type and then a table type out of that object type.
CREATE OR REPLACE TYPE TEST_USER.MD_TYPE AS OBJECT
(QUERY_REF VARCHAR2 (1000 BYTE),
COL_NAME VARCHAR2 (100 BYTE),
COL_LENGTH VARCHAR2 (50 BYTE),
COL_SEQ NUMBER)
/
CREATE OR REPLACE TYPE TEST_USER.MD_TYPE_TABLE AS TABLE OF MD_TYPE
/
Stored Procedure:
CREATE OR REPLACE PROCEDURE SP_TEST2
(
P_MD_TABLE IN MD_TYPE_TABLE,
p_success OUT number
)
IS
BEGIN
INSERT INTO MDATA_TABLE
(
QUERY_REF ,
COL_NAME ,
COL_LENGTH ,
COL_SEQ
)
SELECT ea.*
FROM TABLE(P_MD_TABLE) ea;
p_success :=1;
EXCEPTION
WHEN OTHERS THEN
p_success := -1;
END SP_TEST2;
The problem is I do not know how to populate, first parameter P_MD_TABLE and then MDATA_TABLE. And the procedure compiles without any errors. I have not tested this procedure.
Any help please.
Procedure for loading MD_TYPE_TABLE by passing parameters to MD_TYPE
CREATE OR REPLACE PROCEDURE SP_UPLOAD_MD_TYPE
(
P_QUERY_REF VARCHAR2,
P_COL_NAME VARCHAR2,
P_COL_LENGTH VARCHAR2,
p_col_seq NUMBER,
p_no_of_rows_to_insert NUMBER,
p_num OUT NUMBER
)
IS
p_type_tbl MD_TYPE_TABLE := MD_TYPE_TABLE(); --initialize
BEGIN
<<vartype>>
FOR i IN 1..p_no_of_rows_to_insert
LOOP
p_type_tbl.extend();
p_type_tbl(p_type_tbl.last) := MD_TYPE(P_QUERY_REF, P_COL_NAME, P_COL_LENGTH, p_col_seq);
END LOOP vartype;
SP_TEST2(p_type_tbl, p_num);
END;
You can populate a table type by using extend/ bulk collect
using extend
p_type_tbl.extend();
p_type_tbl(p_type_tbl.last) := MD_TYPE('QUERY_REF1', 'COL_NAME1', 'COL_LENGTH1', 1);
or using bulk collect
SELECT MD_TYPE(c1, c2... cn)
BULK COLLECT INTO p_type_tbl
FROM some_table;
Demo
DECLARE
p_type_tbl MD_TYPE_TABLE := MD_TYPE_TABLE(); --initialize
p_num NUMBER;
BEGIN
p_type_tbl.extend();
p_type_tbl(p_type_tbl.last) := MD_TYPE('QUERY_REF1', 'COL_NAME1', 'COL_LENGTH1', 1);
p_type_tbl.extend();
p_type_tbl(p_type_tbl.last) := MD_TYPE('QUERY_REF2', 'COL_NAME2', 'COL_LENGTH2', 2);
SP_TEST2(p_type_tbl, p_num);
DBMS_OUTPUT.PUT_LINE(p_num);
END;
/
OutPut
1
SELECT * FROM MDATA_TABLE;
OutPut
QUERY_REF COL_NAME COL_LENGTH COL_SEQ
QUERY_REF1 COL_NAME1 COL_LENGTH1 1
QUERY_REF2 COL_NAME2 COL_LENGTH2 2

Share transaction among sessions in Oracle [duplicate]

Is there a possibility to connect to Oracle (via OCI) from one process, then connect on the same database session from another process?
In my current app, there are two ways to access the database: a synchronous one and an asynchronous one (by using a separate process, communicating via sockets).
The problem is the two methods implement distinct sessions.
If I attempt e.g. an update on one session, then try to update the same table from the other session without committing, I get a hang on the OCI call.
Worse, if a session variable is set from one session - the other session does not see it (which is exactly what the name says...).
If you are using an 11g database, you could use the DBMS_XA package to allow one session to to join a transaction started by the first session. As Tim Hall deomonstrates, you can start a transaction in one session, join that transaction from another session, and read the uncommitted changes made in the transaction. Unfortunately, however, that is not going to help with session variables (assuming that "session variable" means package variable that have session scope).
Create the package and the table:
CREATE TABLE foo( col1 NUMBER );
create or replace package pkg_foo
as
g_var number;
procedure set_var( p_in number );
end;
create or replace package body pkg_foo
as
procedure set_var( p_in number )
as
begin
g_var := p_in;
end;
end;
In Session 1, we start a global transaction, set the package variable, and insert a row into the table before suspending the global transaction (which allows another session to resume it)
SQL> ed
Wrote file afiedt.buf
1 declare
2 l_xid dbms_xa_xid := dbms_xa_xid( 1 );
3 l_ret integer;
4 begin
5 l_ret := dbms_xa.xa_start( l_xid, dbms_xa.tmnoflags );
6 pkg_foo.set_var(42);
7 dbms_output.put_line( 'Set pkg_foo.g_var to ' || pkg_foo.g_var );
8 insert into foo values( 42 );
9 l_ret := dbms_xa.xa_end( l_xid, dbms_xa.tmsuspend );
10* end;
SQL> /
Set pkg_foo.g_var to 42
PL/SQL procedure successfully completed.
In session 2, we resume the global transaction, read from the table, read the session variable, and end the global transaction. Note that the query against the table sees the row we inserted but the package variable change is not visible.
SQL> ed
Wrote file afiedt.buf
1 declare
2 l_xid dbms_xa_xid := dbms_xa_xid( 1 );
3 l_ret integer;
4 l_col1 integer;
5 begin
6 l_ret := dbms_xa.xa_start( l_xid, dbms_xa.tmresume );
7 dbms_output.put_line( 'Read pkg_foo.g_var as ' || pkg_foo.g_var );
8 select col1 into l_col1 from foo;
9 dbms_output.put_line( 'Read COL1 from FOO as ' || l_col1 );
10 l_ret := dbms_xa.xa_end( l_xid, dbms_xa.tmsuccess );
11* end;
SQL> /
Read pkg_foo.g_var as
Read COL1 from FOO as 42
PL/SQL procedure successfully completed.
To share session state between the sessions, would it be possible to use a global application context rather than using package variables? You could combine that with the DBMS_XA packages if you want to read both database tables and session state.
Create the context and the package with the getter and setter
CREATE CONTEXT my_context
USING pkg_foo
ACCESSED GLOBALLY;
create or replace package pkg_foo
as
procedure set_var( p_session_id in number,
p_in in number );
function get_var( p_session_id in number )
return number;
end;
create or replace package body pkg_foo
as
procedure set_var( p_session_id in number,
p_in in number )
as
begin
dbms_session.set_identifier( p_session_id );
dbms_session.set_context( 'MY_CONTEXT', 'G_VAR', p_in, null, p_session_id );
end;
function get_var( p_session_id in number )
return number
is
begin
dbms_session.set_identifier( p_session_id );
return sys_context('MY_CONTEXT', 'G_VAR');
end;
end;
In session 1, set the value of the context variable G_VAR to 47 for session 12345
begin
pkg_foo.set_var( 12345, 47 );
end;
Now, session 2 can read the value from the context
1* select pkg_foo.get_var( 12345 ) from dual
SQL> /
PKG_FOO.GET_VAR(12345)
----------------------
47

Resources