the below procedure complies fine,
CREATE PROCEDURE PROCEDURE1
(v_MGR int,
v_empid IN OUT int)
AS
BEGIN
v_empid :=0;
IF (v_mgr IS NOT NULL AND v_mgr <> '') then
EXECUTE IMMEDIATE 'SELECT EMPNO
FROM EMP
WHERE MGR = Rtrim(v_MGR)' into v_empid;
END IF;
END PROCEDURE1;
but when i run
DECLARE
V_MGR NUMBER;
V_EMPID NUMBER;
BEGIN
V_MGR := 7902;
V_EMPID := NULL;
PROCEDURE1(
V_MGR => V_MGR,
V_EMPID => V_EMPID
);
DBMS_OUTPUT.PUT_LINE('V_EMPID = ' || V_EMPID);
END;
the output should be v_empid =2356
but its always showing v_empid = 0 please help to get proper answer
Why is the out parameter is 0? Take a look at the if condition in the procedure
IF (v_mgr IS NOT NULL AND v_mgr <> '')
especially at its second part AND v_mgr <> ''. Oracle treats empty string '' as null and any comparison to null leads to unknown result, thus the above IF condition always evaluates to false so execute immediate statement never executes and as a result the value of v_empid
will never be overwritten.
In this particular situation there is absolutely no need of using dynamic SQL(native dynamic sql execute immediate), because there is no dynamic construction of the query - table and columns are known at compile-time. You simply could use static sql instead:
If your query returns more than one row, you will hit too_many_rows exception. You either should guarantee that your query returns exactly one row, by including rownum=1 in the where clause of the query(if there is a change of returning multiple rows) or you use a collection as the out parameter, to return the result set:
create or replace type T_EmpNums is table of number;
/
create or replace procedure procedure1(
v_mgr int,
v_emps out T_empnums
)
as
begin
if v_mgr is not null
then
select empno
bulk collect into v_emps
from emp
where mgr = v_mgr;
end if;
end;
/
declare
v_mgr number;
v_empids T_EmpNums;
begin
v_mgr := 7902;
procedure1(v_mgr, v_empids);
if v_empids is not empty
then
for empno in v_empids.first .. v_empids.last
loop
dbms_output.put_line('v_empid = ' || to_char(v_empids(empno)));
end loop;
end if;
end;
Well, you declare v_MGR int and then you go and test this v_mgr <> '' and use this Rtrim(v_MGR) in your condition.
Which is it, (var)char or number for mgr and v_mgr?
try this
CREATE PROCEDURE PROCEDURE1
(v_MGR int,
v_empid IN OUT int)
AS
BEGIN
v_empid :=0;
IF (v_mgr IS NOT NULL AND v_mgr <> '') then
SELECT EMPNO into v_empid
FROM EMP
WHERE MGR = Rtrim(v_MGR) ;
END IF;
END PROCEDURE1;
Related
I have a procedure which returns result set from query which is dynamically generated in oracle. It do returns the result set but what I want is to process from information from the generated result set and save it in a table.
This is my query..
PROCEDURE GetItem(pitem_list in varchar2,
PGetData OUT SYS_REFCURSOR)
is
strsql2 long;
BEGIN
strsql2 :='SELECT val, val1, val2 from table1'; ----- This is a sample query as the main query is complex so just for simplicity I wrote this here.
open PGetData for strsql2; ----- This do returns me the result set generated from the query;
Now what I want is to execute the query stored in "strsql2" variable and read the result and process some information..
I thought of executing it from FOR LOOP.
strsql2 :='SELECT val, val1, val2 from table1';
for log in (select strsql2 from dual) ---- I even tried execute immediate
loop
if(log.val = 'TEST')
then
insert into table ----
else
update table --
end if;
end loop;
open PGetData for strsql2; --- After saving the result in table then return the result set..
Where I m going wrong here, or is there any other way to do it?
I m stuck here.
In that case, you can simply fetch from the cursor into local variables. In this case, I know that my query returns three columns, one an integer, one a string, and one a date so I'm declaring three local variables to hold the results.
declare
l_sql varchar2(1000);
l_rc sys_refcursor;
l_integer pls_integer;
l_string varchar2(100);
l_date date;
begin
l_sql := q'{select 1 int, 'foo' str, date '2020-12-21' dt from dual}';
open l_rc for l_sql;
loop
fetch l_rc
into l_integer, l_string, l_date;
exit when l_rc%notfound;
dbms_output.put_line( 'l_string = ' || l_string ||
' l_integer = ' || l_integer ||
' l_date = ' || to_char( l_date, 'dd-mon-yyyy' ) );
end loop;
end;
/
A liveSQL link
Im trying to create a procedure which takes sys refcursor as in out parameter and modifies it based on the logic explained in comments in the below code
TYPE t_params IS
TABLE OF VARCHAR2(32767 CHAR);
/
CREATE OR REPLACE PROCEDURE modify_cursor (
p_cursor IN OUT SYS_REFCURSOR,
p_array_binary IN t_params,
p_values IN t_params
)
/*
p_cursor IN OUT SYS_REFCURSOR
-- contains a single row {empId:123, ename:"king", mgr:"Porter",deptNo:200}
p_array_binary IN t_params
-- contains one binary value corresponding to each column in above cursor ["1","0","1","1"]
p_values IN t_params
-- contains one binary value corresponding to each column in above cursor ["123","king2","new manager","200"]
*/
IS
BEGIN
/*
Based on p_array_binary
if binary value 0 then take cursor should retain value as it is fro corresponding column
if binary value 1 then cusrsor should have the correspondoing column value from p_values
In short, the out cursor should be {empId:123, ename:"king", mgr:"new manager", deptNo:200}
*/
END;
/
Any help in this regard will be highly appreciated.
If you knew the ref cursor structure - it was always four columns of the data types shown - then this would be relatively simple:
CREATE OR REPLACE PROCEDURE modify_cursor (
p_cursor IN OUT SYS_REFCURSOR,
p_array_binary IN t_params,
p_values IN t_params
)
IS
l_empid number;
l_ename varchar2(30);
l_mgr varchar2(30);
l_deptNo number;
BEGIN
-- get original values into local variables
fetch p_cursor into l_empId, l_ename, l_mgr, l_deptNo;
-- re-open cursor using either local variables of p_values depending on p_binary flag
open p_cursor for
select
case when p_array_binary(1) = '1' then to_number(p_values(1)) else l_empId end as empId,
case when p_array_binary(2) = '1' then p_values(2) else l_ename end as ename,
case when p_array_binary(3) = '1' then p_values(3) else l_mgr end as mgr,
case when p_array_binary(4) = '1' then to_number(p_values(4)) else l_deptNo end as deptNo
from dual;
END;
/
Demo using your sample data, via SQL*Plus/SQL Developer/SQLcl bind variables:
var rc refcursor;
begin
open :rc for
select 123 as empId, 'king' as ename, 'Porter' as mgr, 200 as deptNo
from dual;
modify_cursor(:rc, t_params('1', '0', '1', '1'), t_params('123', 'king2', 'new manager', '200'));
end;
/
print rc
EMPID ENAME MGR DEPTNO
---------- -------------------------------- -------------------------------- ----------
123 king new manager 200
db<>fiddle
Since you don't know the structure in advance, you will have to use dynamic SQL, which is bit more complicated. Here's an outline:
CREATE OR REPLACE PROCEDURE modify_cursor (
p_cursor IN OUT SYS_REFCURSOR,
p_array_binary IN t_params,
p_values IN t_params
)
IS
l_c integer;
l_col_cnt integer;
l_desc_t dbms_sql.desc_tab3;
l_varchar2 varchar2(32767 char);
l_values t_params := new t_params();
l_result integer;
BEGIN
-- convert ref cursor to dbms_sql cursor
l_c := dbms_sql.to_cursor_number(rc => p_cursor);
-- analyse the cursor (columns, data types)
dbms_sql.describe_columns3(c => l_c, col_cnt => l_col_cnt, desc_t => l_desc_t);
-- optionally check l_col_cnt matches sise of t_params arguments?
l_values.extend(l_col_cnt);
-- define each column for fetch; here you're treating everything as strings,
-- which will cause issues with some other data types
for i in 1..l_col_cnt loop
dbms_sql.define_column(c => l_c, position => i, column => l_varchar2, column_size => 32767);
end loop;
-- fetch original values - only one row to worry about so no loop
l_result := dbms_sql.fetch_rows(c => l_c);
for i in 1..l_col_cnt loop
-- depending on p_array_binary, set l_values from either fetched data or p_values
if p_array_binary(i) = '1' then
l_values(i) := p_values(i);
else
-- this forces everything to varchar2, which is OK (ish) for your sample data;
-- if you have other data types e.g. dates then you will probably want type-specific
-- handling so you can control the conversions - which affects this, define_column
-- and the final cursor to retrieve the values. But you have the same issue with p_values.
dbms_sql.column_value(c => l_c, position => i, value => l_values(i));
end if;
end loop;
-- finished with original cursor, so close it
dbms_sql.close_cursor(c => l_c);
-- re-open ref cursor using l_values data, with another dynamic SQL statement
l_varchar2 := 'select ';
for i in 1..l_col_cnt loop
if i > 1 then
l_varchar2 := l_varchar2 || ', ';
end if;
if l_desc_t(i).col_type = 2 then
l_varchar2 := l_varchar2 || l_values(i);
else
l_varchar2 := l_varchar2 || '''' || l_values(i) || '''';
end if;
l_varchar2 := l_varchar2 || ' as "' || l_desc_t(i).col_name || '"';
end loop;
l_varchar2 := l_varchar2 || ' from dual';
open p_cursor for l_varchar2;
END;
/
Running exactly the same demo block gives:
EMPID ENAM MGR DEPTNO
---------- ---- ----------- ----------
123 king new manager 200
db<>fiddle
You can add handling for other data types if needed, error handling etc.
Read more about dbms_sql.
I am trying to check for two kinds of authorization level for entries in the menu to display according to different levels given to each user.
But the no data exception in oracle sql is preventing me from checking the second table for authorization.
Here is my code:
declare
v_count number;
v_name varchar2(255);
begin
select DASHBOARD into v_count from RCM_ADMINISTRATOR where USER_NAME = :APP_USER;
select MANAGER into v_name from RCM_ADMINISTRATION_TEAMS where MANAGER = :APP_USER;
if v_count >= 1 then
return true;
end if;
if v_name is not null then
return true;
end if;
if v_count = 0 then
return false;
end if;
EXCEPTION WHEN NO_DATA_FOUND THEN
return false;
end;
Is there a way to make it so that if I get no data from the first query, I can still run the second query?
This is a very common problem, with many solutions.
One option is to put a BEGIN / EXCEPTION / END block around each SELECT INTO that you're trying to do.
declare
v_count number;
v_name varchar2(255);
begin
BEGIN
select DASHBOARD into v_count from RCM_ADMINISTRATOR where USER_NAME = :APP_USER;
EXCEPTION when no_data_found THEN v_count := null;
END;
BEGIN
select MANAGER into v_name from RCM_ADMINISTRATION_TEAMS where MANAGER = :APP_USER;
EXCEPTION when no_data_found THEN v_name := null;
END;
if v_count >= 1 then
...etc...
I often find it easier just to use MAX:
declare
v_count number;
v_name varchar2(255);
begin
select max(DASHBOARD) into v_count from RCM_ADMINISTRATOR where USER_NAME = :APP_USER;
select max(MANAGER) into v_name from RCM_ADMINISTRATION_TEAMS where MANAGER = :APP_USER;
if v_count >= 1 then
...etc...
It's a lazy technique, and if you aren't careful, it can disguise ORA-01422 errors (exact fetch returns more than requested number of rows). But if you know your query will only return 0 or 1 row, then it will work fine.
There are several issues with your code. You are using an anonymous block and returning a value, which is not possible. You can write a function which returns a BOOLEAN value(True or False) However, it is of no use because as per Oracle documentation
http://docs.oracle.com/cd/B19306_01/appdev.102/b14261/datatypes.htm#CJACJGBG.
You cannot insert the values TRUE and FALSE into a database column.
You cannot select or fetch column values into a BOOLEAN variable.
Functions called from a SQL query cannot take any BOOLEAN parameters.
Neither can built-in SQL functions such as TO_CHAR; to represent
BOOLEAN values in output, you must use IF-THEN or CASE constructs to
translate BOOLEAN values into some other type, such as 0 or 1, 'Y' or
'N', 'true' or 'false', and so on.
So In the code, VARCHAR2 is used as RETURN argument.
Pass APP_USER as function argument instead of using a bind variable.
For your condition that each query must execute irrespective of exceptions, you can place it in separate BEGIN..END blocks.
Once you have used RETURN from a stored procedure, you cannot jump back to the rest of the code after exception as you are trying to achieve. Instead, store the value in a variable exit_code and RETURN using the code before the last END to return eventually with an exit code if other conditions are not satisfied.
CREATE OR REPLACE FUNCTION f_test_ret(p_app_user IN VARCHAR2)
RETURN VARCHAR2
AS
v_count NUMBER;
v_name VARCHAR2(255);
exit_code VARCHAR2(6);
BEGIN
BEGIN
SELECT DASHBOARD
INTO v_count
FROM RCM_ADMINISTRATOR
WHERE USER_NAME = p_app_user;
EXCEPTION
WHEN NO_DATA_FOUND THEN
exit_code := 'FALSE';
END;
BEGIN
SELECT MANAGER
INTO v_name
FROM RCM_ADMINISTRATION_TEAMS
WHERE MANAGER = p_app_user;
EXCEPTION
WHEN NO_DATA_FOUND THEN
exit_code := 'FALSE';
END;
IF v_count >= 1 THEN
RETURN 'TRUE';
END IF;
IF v_name IS NOT NULL THEN
RETURN 'TRUE';
END IF;
IF v_count = 0 THEN
RETURN 'TRUE';
END IF;
RETURN exit_code;
END;
/
I am using Oracle 12, and I want to make a dynamic procedure which selects rows from specific table but according to an unknown conditio. That condition will be specified as input parameter.
Suppose I have a column called employee id and I want to call the procedure
with the following condition
execute s('employeeid = 2')
My code is
create or replace procedure s (condition varchar)
as
TYPE EmpCurTyp IS REF CURSOR; -- define weak REF CURSOR type
emp_cv EmpCurTyp; -- declare cursor variable
my_ename VARCHAR2(15);
my_sal NUMBER := 2;
mycondition varchar2(100):=condition;
BEGIN
OPEN emp_cv FOR -- open cursor variable
'SELECT employeeid, employeename FROM employees WHERE = :s' USING mycondition;
END;
but I am getting an error
missing expression
What am I doing wrong, and will the result of this procedure be selected rows from employees table that satisfy applied condition ?
The USING is meant to handle values, not pieces of code; if you need to edit your query depending on an input parameter ( and I believe this is a very dangerous way of coding), you should treat the condition as a string to concatenate to the query.
For example, say you have this table:
create table someTable(column1 number)
This procedure does somthing similar to what you need:
create or replace procedure testDyn( condition IN varchar2) is
cur sys_refcursor;
begin
open cur for 'select column1 from sometable where ' || condition;
/* your code */
end;
Hot it works:
SQL> exec testDyn('column1 is null');
PL/SQL procedure successfully completed.
SQL> exec testDyn('column99 is null');
BEGIN testDyn('column99 is null'); END;
*
ERROR at line 1:
ORA-00904: "COLUMN99": invalid identifier
ORA-06512: at "ALEK.TESTDYN", line 4
ORA-06512: at line 1
This is not embedded in a procedure yet but I tested this and works:
DECLARE
TYPE OUT_TYPE IS TABLE OF VARCHAR2 (20)
INDEX BY BINARY_INTEGER;
l_cursor INTEGER;
l_fetched_rows INTEGER;
l_sql_string VARCHAR2 (250);
l_where_clause VARCHAR2 (100);
l_employeeid VARCHAR2 (20);
l_employeename VARCHAR2 (20);
l_result INTEGER;
o_employeeid OUT_TYPE;
o_employeename OUT_TYPE;
BEGIN
l_cursor := DBMS_SQL.OPEN_CURSOR;
l_sql_string := 'SELECT employeeid, employeename FROM employees WHERE ';
l_where_clause := 'employeeid = 2';
l_sql_string := l_sql_string || l_where_clause;
DBMS_SQL.PARSE (l_cursor, l_sql_string, DBMS_SQL.V7);
DBMS_SQL.DEFINE_COLUMN (l_cursor,
1,
l_employeeid,
20);
DBMS_SQL.DEFINE_COLUMN (l_cursor,
2,
l_employeename,
20);
l_fetched_rows := 0;
l_result := DBMS_SQL.EXECUTE_AND_FETCH (l_cursor);
LOOP
EXIT WHEN l_result = 0;
DBMS_SQL.COLUMN_VALUE (l_cursor, 1, l_employeeid);
DBMS_SQL.COLUMN_VALUE (l_cursor, 2, l_employeename);
l_fetched_rows := l_fetched_rows + 1;
o_employeeid (l_fetched_rows) := l_employeeid;
o_employeename (l_fetched_rows) := l_employeename;
l_result := DBMS_SQL.FETCH_ROWS (l_cursor);
END LOOP;
DBMS_SQL.CLOSE_CURSOR (l_cursor);
DBMS_OUTPUT.PUT_LINE (o_employeeid (1));
DBMS_OUTPUT.PUT_LINE (o_employeename (1));
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.PUT_LINE ('GENERAL FAILURE: ' || SQLERRM);
END;
I have a 'person' table with two fields 'person_name' and 'person_age'.
i want write a procedure that return sys_refcursor but calculating a extra field 'is_old'. por example:
PROCEDURE people_load(p_name IN VARCHAR2, P_RESULT OUT SYS_REFCURSOR) IS
BEGIN
DECLARE
isOld BOOLEAN := false;
CURSOR cursorTemp IS SELECT person_name, person_age, is_old
WHERE person_name = p_name;
BEGIN
FOR _p IN cursorTemp
LOOP
IF _p.person_age > 75 THEN
_p.is_old:=TRUE;
END IF;
END LOOP;
¿¿P_RESULT:=cursorTemp; //open P_RESULT for (open cursorTemp);??
END;
END people_load;
i dont know how to assign temporal cursor 'cursorTemp' to OUT param 'P_RESULT' to returning the result.
You can not use a BOOLEAN in SQL, only in PL/SQL.
You can not loop through a cursor and recalculate a column.
You declare a variable isOld and never use it.
I suggest you calculate the is_old within the cursor. I changed it to contain 1 (true) or 0 (false).
PROCEDURE people_load(p_name IN VARCHAR2, P_RESULT OUT SYS_REFCURSOR) IS
BEGIN
DECLARE
CURSOR cursorTemp IS SELECT person_name, person_age, case when person_age > 75 then 1 else 0 end is_old
WHERE person_name = p_name;
BEGIN
P_RESULT := cursorTemp; //open P_RESULT for (open cursorTemp);
END;
END people_load;
You should do the calculation in the select.
CURSOR cursorTemp IS
SELECT
person_name,
person_age,
CASE WHEN person_age>75 THEN 1 ELSE 0 END AS is_old
WHERE person_name = p_name;