PLS-00231 for in procedure defined function - oracle

I looked up the problem but didn't find a solution. my problem is as follows
create or replace procedure GETINFO(
pIVR OUT NUMBER
) IS
FUNCTION IS_MASK_ALLOWED(
pBusSeg IN NUMBER,
pCHTPMask IN NUMBER
)
RETURN BOOLEAN
IS
BEGIN
IF pCHTPMask > 0 THEN
ELSE RETURN TRUE;
END IF;
END;
BEGIN
SELECT 1
INTO pIVR
FROM DUAL
WHERE IS_MASK_ALLOWED(1, 2)=TRUE;
END;
the compiler says that I can not use IS_MASK_ALLOWED in SQL statements
ORACLE 10g
PL/SQL developer 7.5

Oracle does not support boolean statements in SQL so using True will not work. Try select 1 from dual where True = True to confirm.
Secondly if you're not allowing anyone to access is_mask_allowed outside your procedure and that's all it does then you can do this pIVR := 1. which'll have the same result as your code ( procedure and function ). Personally if this is somewhere near all they do I'd have everything in the same procedure.
If you're missing some information in is_mask_missing, that then with nothing after it maybe then your package will error on no_data_found as there is the possibility of select 1 from dual where 1 = 0, which returns nothing.
If you do want is_mask_allowed to be accessed outside your procedure it would be easiest to put it in a package. As you can't use a boolean I've used a binary, where 0 represents false and 1 true
create or replace package my_package is
function is_mask_allowed ( pBusSeg IN NUMBER
, pCHTPMask IN NUMBER
) RETURN NUMBER;
procedure get_info ( pIVR out NUMBER );
end my_package;
/
show error
create or replace package body my_package is
function is_mask_allowed ( pBusSeg IN NUMBER
, pCHTPMask IN NUMBER
) RETURN NUMBER is
begin
if pCHTPMask > 0 THEN
-- do something
return 0
else return 1
end if;
end is_mask_allowed;
-----------------------------------
procedure getinfo( pIVR OUT NUMBER ) IS
begin
if is_mask_allowed(1,2) = 1 then
pIVR := 1;
else
pIVR := -- something else;
end if;
end getinfo;
end my_package;
/
show error

You need to put your is_mask_allowed function into a package to use it in SQL, and you should return something other than a boolean (perhaps a number) from the function and test for that, so your SQL is something more like:
select my_pkg.is_mask_allowed(1, 2) into pIVR from dual;
Here is a recent discussion in the Oracle forums on the same error:
https://forums.oracle.com/forums/thread.jspa?threadID=2319500

Related

Comparing number with varchar2

I have this function and I need to compare number with varchar.
CREATE OR REPLACE FUNCTION getOdds(i_odd in varchar2, i_id in number) return number as
begin
declare odd integer;
declare i_perecentage=0;
begin
if i_odd ='SP'
then
return (0);
end if;
odd:=round(to_number((1-i_perecentage/100)*i_odd),2);
if odd<1
then
return(i_odd);
else
return(round(odd,2));
end if;
end;
end;
/
PS: I edited function and i resolve problem with comparing , now i have another situation that i dont like..
This function returns calculated percentage of i_odd. The problem is that if i pass 0 in i_percentage in results i get result with no decimal places(for example: i_odd = 3.10 and i_percentage = 0 i get odd = 3 but if I pass i_odd = 3.10 and i_percentage = 1 i get odd = 3.10 ).
Why is on i_percentage = 0 i dont get decimal places ??
If you want to validate a varchar2 field as a number in PL/SQL, typically you'd just try converting it to a number and catch the exception.
CREATE OR REPLACE FUNCTION getOdds(i_odd in varchar2, i_id in number) return number as
odd number;
BEGIN
-- if i_odd = 'SP' (or another non-number), this will throw an ORA-01722
-- exception which will be caught in the exception block, below
odd := to_number(i_odd); -- you might want a format mask here
--... now you can use "odd" as a number
EXCEPTION WHEN INVALID_NUMBER THEN
return 0;
END;
/
You can also nest a begin..end block in the middle of your code just to catch exceptions, if that works better for you:
CREATE OR REPLACE FUNCTION getOdds(i_odd in varchar2, i_id in number) return number as
odd number;
begin
begin
odd := to_number(i_odd); -- you might want a format mask here
exception when INVALID_NUMBER then
odd := 0;
end;
--... now you can use "odd" as a number
end;
/
The reason why you can't catch the invalid_number exception is because you are declaring the input parameter as a number. When you call your function, Oracle tries to convert the string to a number first (and it fails of course, before entering your code at all).
If you change the input parameter to varchar2, then the conversions to number (implicit in this case) is done inside the function, and invalid numbers can be caught and handled as you want (here I'm just returning a different string to denote the issue):
create or replace function is_odd_even(i_num in varchar2)
return varchar2
is
begin
-- conversion to number is done here
if (mod(i_num, 2) = 0) then
return 'EVEN';
else
return 'ODD';
end if;
exception
when INVALID_NUMBER or VALUE_ERROR then
-- do something meaningful
return 'INV';
end;
Usage example:
with x as (
select '1' as val from dual
union all
select 'SP' as val from dual
union all
select '2' as val from dual
)
select x.val, is_odd_even(x.val)
from x;
Output:
1 ODD
SP INV
2 EVEN
SOLUTION:
CREATE OR REPLACE FUNCTION getOdds(i_odd in varchar2, i_id in number) return varchar2 as
odd varchar2(10);
ret_value number(4);
begin
if (i_odd ='SP') or i_odd is null then
return 'SP';
else
odd :=ROUND( TO_NUMBER( ( 1 - (play_beting.get_odds_percentage(i_id) / 100 ) ) * TO_NUMBER(i_odd) ), 2);
IF(odd < 1) THEN
ret_value := TO_NUMBER(i_odd);
ELSE
ret_value := to_char(odd,'9999.00');
END IF;
END IF;
RETURN to_char(ret_value,'9999.00');
END getOdds;

What is wrong with my PLSQL Boolean Function? [duplicate]

I have a function in PL/SQL which checks if a particular emp_id exists or not which is:
CREATE OR REPLACE FUNCTION checkEmpNo(eno numeric)
RETURN boolean IS
emp_number number;
BEGIN
SELECT emp_id INTO emp_number
FROM emp;
IF eno=emp_number
THEN
return true;
ELSE
return false;
END IF;
END checkEmpNo;
The function compiles successfully, but when I try to run it as:
DECLARE
exist boolean;
BEGIN
exist=checkEmpNo(1);
dbms_output.put_line(exist);
END;
it returns the error:
ORA-06550: line 5, column 1:
PLS-00306: wrong number or types of arguments in call to 'PUT_LINE'
ORA-06550: line 5, column 1:
PL/SQL: Statement ignored
3. BEGIN
4. exist:=checkEmpNo(1);
5. dbms_output.put_line(exist);
6. END;
EDIT:
I also tried this:
DECLARE
exist boolean:=true;
BEGIN
if(exist=checkEmpNo(1))
then
dbms_output.put_line('true');
else
dbms_output.put_line('false');
end if;
END;
And it returns the error: ORA-01422: exact fetch returns more than requested number of rows
dbms_output.put_line is not overloaded to accept a boolean argument. You can do something like
dbms_output.put_line( case when exist = true
then 'true'
else 'false'
end );
to convert the boolean into a string that you can then pass to dbms_output.
The ORA-01422 error is a completely separate issue. The function checkEmpNo includes the SELECT INTO statement
SELECT emp_id
INTO emp_number
FROM emp;
A SELECT INTO will generate an error if the query returns anything other than 1 row. In this case, if there are multiple rows in the emp table, you'll get an error. My guess is that you would want your function to do something like
CREATE OR REPLACE FUNCTION checkEmpNo(p_eno number)
RETURN boolean
IS
l_count number;
BEGIN
SELECT count(*)
INTO l_count
FROM emp
WHERE emp_id = p_eno;
IF( l_count = 0 )
THEN
RETURN false;
ELSE
RETURN true;
END IF;
END checkEmpNo;
Alternatively you can use the Oracle function diutil.bool_to_int to convert a boolean value to an integer: True -> 1, False -> 0.
dbms_output.put_line(diutil.bool_to_int(p_your_boolean));

Calling a function within a procedure

I apologize ahead of time for the probably basic question. I am student and it is crunch time!
I am using Oracle 10g Express.
I created a function:
create or replace FUNCTION test_glaccounts_description
(
account_description_param VARCHAR2
)
RETURN NUMBER
AS
description_dup_var NUMBER;
BEGIN
SELECT 1
INTO description_dup_var
FROM general_ledger_accounts
WHERE account_description = account_description_param;
RETURN description_dup_var;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN 0;
END;
And would like to use that function in a procedure.
I've tried:
PROCEDURE insert_gla_with_test
(
account_number_param NUMBER,
account_description_param VARCHAR2
)
AS
BEGIN
IF test_glaccounts_description = 1 THEN
INSERT INTO general_ledger_accounts
VALUES (account_number_param, account_description_param);
ELSE raise_application_error (-20001, 'Duplicate account description');
END IF;
END;
But it doesn't like the "test_gla_accounts" line.....what am I doing wrong?
To my understanding, the function returns a value of 1 or 0, in the procedure, if the function returned a 1, I would like the param fields added to the table. If the function returned a 0, I would like the procedure to raise the error.
test_glaccounts_description takes a parameter (account_description_param) and returns a NUMBER. In order to call the function, therefore, you need to pass in a parameter. Assuming that you want to pass in the account_description_param that is passed in to the insert_gla_with_test procedure
CREATE OR REPLACE PROCEDURE insert_gla_with_test
(
account_number_param NUMBER,
account_description_param VARCHAR2
)
AS
BEGIN
IF test_glaccounts_description( account_description_param ) = 1 THEN
INSERT INTO general_ledger_accounts
VALUES (account_number_param, account_description_param);
ELSE
raise_application_error (-20001, 'Duplicate account description');
END IF;
END;

"Boolean" parameter for Oracle stored procedure

I'm aware that Oracle does not have a boolean type to use for parameters, and am currently taking in a NUMBER type which would have 1/0 for True/False (instead of the 'Y'/'N' CHAR(1) approach).
I'm not a very advanced Oracle programmer, but after doing some digging and reading some ASKTOM posts, it seems like you can restrict a field using a format for the column like:
MyBool NUMBER(1) CHECK (MyBool IN (0,1))
Is there a way to apply the same sort of a check constraint to an input parameter to a stored procedure? I'd like to restrict the possible inputs to 0 or 1, rather than checking for it explicitly after receiving the input.
You can use Booleans as parameters to stored procedures:
procedure p (p_bool in boolean) is...
However you cannot use Booleans in SQL, e.g. select statements:
select my_function(TRUE) from dual; -- NOT allowed
For a number parameter there is no way to declaratively add a "check constraint" to it, you would have to code some validation e.g.
procedure p (p_num in number) is
begin
if p_num not in (0,1) then
raise_application_error(-20001,'p_num out of range');
end if;
...
Yes and no.
You can do..
create or replace package t_bool is
subtype t_bool_num IS PLS_INTEGER RANGE 0..1;
function f_test (i_bool_num t_bool_num) return varchar2;
end t_bool;
/
create or replace package body t_bool is
function f_test (i_bool_num t_bool_num) return varchar2 is
begin
if i_bool_num = 0 then
return 'false';
elsif i_bool_num = 1 then
return 'true';
elsif i_bool_num is null then
return 'null';
else
return to_char(i_bool_num);
end if;
end;
end t_bool;
/
The good news is that, if you do
exec dbms_output.put_line(t_bool.f_test(5));
it reports an error.
The bad news is that if you do
select t_bool.f_test(5) from dual;
then you don't get an error

Selecting Values from Oracle Table Variable / Array?

Following on from my last question (Table Variables in Oracle PL/SQL?)...
Once you have values in an array/table, how do you get them back out again? Preferably using a select statement or something of the like?
Here's what I've got so far:
declare
type array is table of number index by binary_integer;
pidms array;
begin
for i in (
select distinct sgbstdn_pidm
from sgbstdn
where sgbstdn_majr_code_1 = 'HS04'
and sgbstdn_program_1 = 'HSCOMPH'
)
loop
pidms(pidms.count+1) := i.sgbstdn_pidm;
end loop;
select *
from pidms; --ORACLE DOESN'T LIKE THIS BIT!!!
end;
I know I can output them using dbms_output.putline(), but I'm hoping to get a result set like I would from selecting from any other table.
Thanks in advance,
Matt
You might need a GLOBAL TEMPORARY TABLE.
In Oracle these are created once and then when invoked the data is private to your session.
Oracle Documentation Link
Try something like this...
CREATE GLOBAL TEMPORARY TABLE temp_number
( number_column NUMBER( 10, 0 )
)
ON COMMIT DELETE ROWS;
BEGIN
INSERT INTO temp_number
( number_column )
( select distinct sgbstdn_pidm
from sgbstdn
where sgbstdn_majr_code_1 = 'HS04'
and sgbstdn_program_1 = 'HSCOMPH'
);
FOR pidms_rec IN ( SELECT number_column FROM temp_number )
LOOP
-- Do something here
NULL;
END LOOP;
END;
/
In Oracle, the PL/SQL and SQL engines maintain some separation. When you execute a SQL statement within PL/SQL, it is handed off to the SQL engine, which has no knowledge of PL/SQL-specific structures like INDEX BY tables.
So, instead of declaring the type in the PL/SQL block, you need to create an equivalent collection type within the database schema:
CREATE OR REPLACE TYPE array is table of number;
/
Then you can use it as in these two examples within PL/SQL:
SQL> l
1 declare
2 p array := array();
3 begin
4 for i in (select level from dual connect by level < 10) loop
5 p.extend;
6 p(p.count) := i.level;
7 end loop;
8 for x in (select column_value from table(cast(p as array))) loop
9 dbms_output.put_line(x.column_value);
10 end loop;
11* end;
SQL> /
1
2
3
4
5
6
7
8
9
PL/SQL procedure successfully completed.
SQL> l
1 declare
2 p array := array();
3 begin
4 select level bulk collect into p from dual connect by level < 10;
5 for x in (select column_value from table(cast(p as array))) loop
6 dbms_output.put_line(x.column_value);
7 end loop;
8* end;
SQL> /
1
2
3
4
5
6
7
8
9
PL/SQL procedure successfully completed.
Additional example based on comments
Based on your comment on my answer and on the question itself, I think this is how I would implement it. Use a package so the records can be fetched from the actual table once and stored in a private package global; and have a function that returns an open ref cursor.
CREATE OR REPLACE PACKAGE p_cache AS
FUNCTION get_p_cursor RETURN sys_refcursor;
END p_cache;
/
CREATE OR REPLACE PACKAGE BODY p_cache AS
cache_array array;
FUNCTION get_p_cursor RETURN sys_refcursor IS
pCursor sys_refcursor;
BEGIN
OPEN pCursor FOR SELECT * from TABLE(CAST(cache_array AS array));
RETURN pCursor;
END get_p_cursor;
-- Package initialization runs once in each session that references the package
BEGIN
SELECT level BULK COLLECT INTO cache_array FROM dual CONNECT BY LEVEL < 10;
END p_cache;
/
The sql array type is not neccessary. Not if the element type is a primitive one. (Varchar, number, date,...)
Very basic sample:
declare
type TPidmList is table of sgbstdn.sgbstdn_pidm%type;
pidms TPidmList;
begin
select distinct sgbstdn_pidm
bulk collect into pidms
from sgbstdn
where sgbstdn_majr_code_1 = 'HS04'
and sgbstdn_program_1 = 'HSCOMPH';
-- do something with pidms
open :someCursor for
select value(t) pidm
from table(pidms) t;
end;
When you want to reuse it, then it might be interesting to know how that would look like.
If you issue several commands than those could be grouped in a package.
The private package variable trick from above has its downsides.
When you add variables to a package, you give it state and now it doesn't act as a stateless bunch of functions but as some weird sort of singleton object instance instead.
e.g. When you recompile the body, it will raise exceptions in sessions that already used it before. (because the variable values got invalided)
However, you could declare the type in a package (or globally in sql), and use it as a paramter in methods that should use it.
create package Abc as
type TPidmList is table of sgbstdn.sgbstdn_pidm%type;
function CreateList(majorCode in Varchar,
program in Varchar) return TPidmList;
function Test1(list in TPidmList) return PLS_Integer;
-- "in" to make it immutable so that PL/SQL can pass a pointer instead of a copy
procedure Test2(list in TPidmList);
end;
create package body Abc as
function CreateList(majorCode in Varchar,
program in Varchar) return TPidmList is
result TPidmList;
begin
select distinct sgbstdn_pidm
bulk collect into result
from sgbstdn
where sgbstdn_majr_code_1 = majorCode
and sgbstdn_program_1 = program;
return result;
end;
function Test1(list in TPidmList) return PLS_Integer is
result PLS_Integer := 0;
begin
if list is null or list.Count = 0 then
return result;
end if;
for i in list.First .. list.Last loop
if ... then
result := result + list(i);
end if;
end loop;
end;
procedure Test2(list in TPidmList) as
begin
...
end;
return result;
end;
How to call it:
declare
pidms constant Abc.TPidmList := Abc.CreateList('HS04', 'HSCOMPH');
xyz PLS_Integer;
begin
Abc.Test2(pidms);
xyz := Abc.Test1(pidms);
...
open :someCursor for
select value(t) as Pidm,
xyz as SomeValue
from table(pidms) t;
end;

Resources