I read about IS OF TYPE and I expected that it should return TRUE, FALSE or NULL.
I have two object types:
CREATE TYPE o1 AS OBJECT ( id NUMBER );
/
CREATE TYPE o2 AS OBJECT ( id NUMBER );
/
When I run the code below, everything is OK.
DECLARE
type1 o1;
BEGIN
type1 := o1(id=>1);
if (type1 IS OF (o1)) then
DBMS_OUTPUT.PUT_LINE('type1 is o1');
END if;
END;
/
But when I try to run:
DECLARE
type1 o1;
BEGIN
type1 := o1(id=>1);
if (type1 IS OF (o2)) then
DBMS_OUTPUT.PUT_LINE('type1 is o1');
END if;
END;
/
I received the following exceptions
Error report:
ORA-06550: line 6, column 21:
PLS-00382: expression is of wrong type
ORA-06550: line 6, column 4:
PL/SQL: Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
In the documentation there isn't clear explanation, should I catch exception if something is of the wrong type? Or, should I expect false in the IF condition?
If you have declared your variable as O1 type then you can use is of [type] condition to test only whether your variable is of o1 type or is of o1's subtype. Here is an example(variables must be instantiated):
-- base type
SQL> create or replace type o1 as object(
2 prop number
3 )not final;
4 /
Type created
-- O1's subtype
SQL> create or replace type o2 under o1(
2 prop1 number
3 );
4 /
-- test if the l_o1 is of O1 type
SQL> declare
2 l_o1 o1;
3 begin
4 l_o1 := o1(prop=>1);
5 if l_o1 is of (o1)
6 then
7 dbms_output.put_line('Yes');
8 else
9 dbms_output.put_line('No');
10 end if;
11 end;
12 /
Yes
PL/SQL procedure successfully completed
-- test if the l_o1 is of O2 type
SQL> declare
2 l_o1 o1;
3 begin
4 l_o1 := o1(prop=>1);
5 if l_o1 is of (o2)
6 then
7 dbms_output.put_line('Yes');
8 else
9 dbms_output.put_line('No');
10 end if;
11 end;
12 /
No
PL/SQL procedure successfully completed
-- test if the l_o2 is of O2 type
SQL> declare
2 l_o2 o2;
3 begin
4 l_o2 := o2(prop=>1, prop1 => 1);
5 if l_o2 is of (o2)
6 then
7 dbms_output.put_line('Yes');
8 else
9 dbms_output.put_line('No');
10 end if;
11 end;
12 /
Yes
PL/SQL procedure successfully completed
Update:
Take a look at this to get more information about is of[type]. Usually data type of a variable is known at compile time, but if you have to deal with dynamic typing you may look at anydata(object data type). Here is a simple example:
SQL> declare
2 l_o1 o1;
3
4 -- Here is a procedure(for the sake of simplicity has not
5 -- been written as a schema object)
6 -- that doesn't not know
7 -- variable of what dada type will be passed in
8 -- at compile time;
9 procedure DoSomething(p_var anydata)
10 is
11 begin
12 case p_var.gettypename
13 when 'HR.O1'
14 then dbms_output.put_line('O1 data type. Do something');
15 when 'HR.O2'
16 then dbms_output.put_line('O2 data type. Do something');
17 else
18 dbms_output.put_line('Unknown data type');
19 end case;
20 end;
21
22 begin
23 l_o1 := o1(prop => 1);
24 DoSomething(anydata.ConvertObject(l_o1));
25 end;
26 /
O1 data type. Do something
PL/SQL procedure successfully completed
Related
I was wondering if theres a way in which I can decide that from "this day on" a user-defined error can act as a resource I can use over and over.
For example->
Raise_Application_Error (-20343, 'The balance is too low.');
So basically if I can use -20343 as key word(error code) and use again in a different procedure instead of raising it again and again..,
Is that possible?
Well, you'll have to raise it, somehow, Oracle can't know what you want to do if balance is too low.
Maybe you could create your own table of exceptions, e.g.
SQL> select * from my_exception;
ERR_CODE ERR_NAM ERR_MESSAGE
---------- ------- ------------------------------
-20343 bal_low Balance is too low
-20344 name_s Name can not begin with an "S"
Function accepts error code and returns message:
SQL> create or replace function f_myerr (par_err_code in my_exception.err_code%type)
2 return my_exception.err_message%type
3 is
4 retval my_exception.err_message%type;
5 begin
6 select err_message
7 into retval
8 from my_exception
9 where err_code = par_err_code;
10 return retval;
11 exception
12 when no_data_found then
13 return 'Exception does not exist';
14 end;
15 /
Function created.
This piece of code simulates "balance too low" error:
SQL> declare
2 l_balance number;
3 bal_low exception;
4 begin
5 select sal into l_balance
6 from emp
7 where ename = 'JONES';
8
9 if l_balance < 5000 then
10 raise bal_low;
11 end if;
12
13 exception
14 when bal_low then
15 raise_application_error(-20343, f_myerr(-20343));
16 end;
17 /
declare
*
ERROR at line 1:
ORA-20343: Balance is too low
ORA-06512: at line 15
SQL>
Another PL/SQL procedure might also find out that balance is too low, but you'd have to repeat such a code again, I'm afraid.
You can modify that code (i.e. table, function) to better suit your needs, but - that's what I understood for what you said so far.
I wanted to have a function in PLSQL that would return an set of numbers, and then have a for loop that would iterate over that dataset and do something with it.
Any suggestions ? Does the function need to be a pipeline function for it to be used in the for loop? Do I need to create a new type even though Im just returning numbers?
Thanks!
CREATE OR REPLACE PACKAGE BODY someBody AS
FUNCTION getListOfNumbers RETURN someList IS -- what type do I return ??
BEGIN
RETURN SELECT SID FROM V$SESSION; -- Not sure what do here ??
END;
PROCEDURE soSomeStuff IS
BEGIN
FOR rec IN(getListOfNumbers) -- how do I select from the function?
LOOP
dbms_output.put_line(rec);
END LOOP;
END;
END;
You would need to declare a collection type:
SQL> CREATE OR REPLACE PACKAGE my_package AS
2
3 TYPE someList IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
4
5 FUNCTION getListOfNumbers RETURN someList;
6 PROCEDURE soSomeStuff;
7
8 END my_package;
9 /
Package created
You would then use the defined type like this:
SQL> CREATE OR REPLACE PACKAGE BODY my_package AS
2
3 FUNCTION getListOfNumbers RETURN someList IS
4 l_list someList;
5 BEGIN
6 SELECT SID BULK COLLECT INTO l_list FROM V$SESSION;
7 RETURN l_list;
8 END;
9
10 PROCEDURE soSomeStuff IS
11 l_list someList;
12 BEGIN
13 l_list := getListOfNumbers;
14 FOR i IN 1..l_list.count LOOP
15 dbms_output.put_line(l_list(i));
16 END LOOP;
17 END;
18
19 END my_package;
20 /
Package body created
SQL> exec my_package.soSomeStuff;
284
285
287
288
[...]
PL/SQL procedure successfully completed
How can we define output parameter size in stored procedure?
You can't. Of course, you are in control of how much data you put into the OUT parameter in the stored procedure. If you want you can create a sized local variable to hold the data and then assign the value of that variable to the OUT parameter.
The calling program determines the size of the variable that receives the OUT parameter.
Here is a simple package which declares and uses a subtype:
SQL> create or replace package my_pkg as
2 subtype limited_string is varchar2(10);
3 procedure pad_string (p_in_str varchar
4 , p_length number
5 , p_out_str out limited_string);
6 end my_pkg;
7 /
Package created.
SQL> create or replace package body my_pkg as
2 procedure pad_string
3 (p_in_str varchar
4 , p_length number
5 , p_out_str out limited_string)
6 as
7 begin
8 p_out_str := rpad(p_in_str, p_length, 'A');
9 end pad_string;
10 end my_pkg;
11 /
Package body created.
SQL>
However, if we call PAD_STRING() in such a way that the output string exceeds the subtype's precision it still completes successfully. Bother!
SQL> var out_str varchar2(128)
SQL>
SQL> exec my_pkg.pad_string('PAD THIS!', 12, :out_str)
PL/SQL procedure successfully completed.
SQL>
SQL> select length(:out_str) from dual
2 /
LENGTH(:OUT_STR)
----------------
12
SQL>
This is annoying but it's the way PL/SQL works so we have to live with it.
The way to resolve the situaton is basically to apply DBC principles and validate our parameters. So, we can assert business rules against the inputs like this:
SQL> create or replace package body my_pkg as
2 procedure pad_string
3 (p_in_str varchar
4 , p_length number
5 , p_out_str out limited_string)
6 as
7 begin
8 if length(p_in_str) + p_length > 10 then
9 raise_application_error(
10 -20000
11 , 'Returned string cannot be longer than 10 characters!');
12 end if;
13 p_out_str := rpad(p_in_str, p_length, 'A');
14 end pad_string;
15 end my_pkg;
16 /
Package body created.
SQL>
SQL> exec my_pkg.pad_string('PAD THIS!', 12, :out_str)
BEGIN my_pkg.pad_string('PAD THIS!', 12, :out_str); END;
*
ERROR at line 1:
ORA-20000: Returned string cannot be longer than 10 characters!
ORA-06512: at "APC.MY_PKG", line 9
ORA-06512: at line 1
SQL>
Or we can assert business rules against the output like this:
SQL> create or replace package body my_pkg as
2 procedure pad_string
3 (p_in_str varchar
4 , p_length number
5 , p_out_str out limited_string)
6 as
7 l_str limited_string;
8 begin
9 l_str := rpad(p_in_str, p_length, 'A');
10 p_out_str := l_str;
11 end pad_string;
12 end my_pkg;
13 /
Package body created.
SQL>
SQL> exec my_pkg.pad_string('PAD THIS!', 12, :out_str)
BEGIN my_pkg.pad_string('PAD THIS!', 12, :out_str); END;
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at "APC.MY_PKG", line 9
ORA-06512: at line 1
SQL>
In most scenarios we should do both. This is the polite way to build interfaces, because it means other routines can call our procedures with the confidence that they will return the values they say they will.
You could use a subtype in a package header and type check that in the body...
CREATE OR REPLACE PACKAGE my_test
AS
SUBTYPE my_out IS VARCHAR2( 10 );
PROCEDURE do_something( pv_variable IN OUT my_out );
END;
/
CREATE OR REPLACE PACKAGE BODY my_test
AS
PROCEDURE do_something( pv_variable IN OUT my_out )
IS
lv_variable my_out;
BEGIN
-- Work on a local copy of the variable in question
lv_variable := 'abcdefghijklmnopqrstuvwxyz';
pv_variable := lv_variable;
END do_something;
END;
/
Then when you run this
DECLARE
lv_variable VARCHAR2(30);
BEGIN
my_test.do_something( lv_variable );
DBMS_OUTPUT.PUT_LINE( '['||lv_variable||']');
END;
/
You would get the error
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
Seems to go against the spirit of using an out parameter, but after Tony's comment this was the only thing I could think of to control data within the called code.
I've been playing around with the pluto-test-framework today, and I'd like to get some existing functions into a test harness with it.
I have lots of functions with this type of specification.
FUNCTION DO_SOME_STUFF (pOldSchedule IN SCHEDULE_OBJ,
pNewSchedule OUT SCHEDULE_OBJ,
pLoggerContext IN OUT LOGGER_CONTEXT_OBJ)
RETURN NUMBER;
It takes pOldSchedule, does some stuff to it, and then returns pNewSchedule. The logger_context just does logging.
As part of a test, I'd like to be able to compare the values in each of the columns of the type, without having to write individual IF statements.
It'll need to return boolean to signify whether or not pOldSchedule and pNewSchedule match.
Any ideas?
Straightforward equality tests work with nested tables:
SQL> declare
2 type nt is table of number;
3 nt1 nt;
4 nt2 nt;
5 nt3 nt;
6 begin
7 nt1 := nt(1,2,3);
8 nt2 := nt(1,2,3);
9 if nt1 = nt2 then
10 dbms_output.put_line('NT2 is the same nested table as NT1');
11 else
12 dbms_output.put_line('NT2 is a different nested table from NT1');
13 end if;
14 nt2 := nt(1,2,3,4);
15 if nt1 = nt3 then
16 dbms_output.put_line('NT3 is the same nested table as NT1');
17 else
18 dbms_output.put_line('E3 is a different nested table from NT1');
19 end if;
20 end;
21 /
NT2 is the same nested table as NT1
E3 is a different nested table from NT1
PL/SQL procedure successfully completed.
SQL>
However the same is not true of full-on objects:
SQL> create or replace type new_emp as object (
2 ename varchar2(10)
3 , sal number
4 , deptno number
5 , job varchar2(10))
6 /
Type created.
SQL> declare
2 e1 new_emp;
3 e2 new_emp;
4 begin
5 e1 := new_emp('KESTELYN', 3700, 30, 'MARKETING');
6 e2 := new_emp('KESTELYN', 3700, 30, 'MARKETING');
7 if e1 = e2 then
8 dbms_output.put_line('E2 is the same as E1');
9 else
10 dbms_output.put_line('E2 is different from E1');
11 end if;
12 end;
13 /
if e1 = e2 then
*
ERROR at line 7:
ORA-06550: line 7, column 11:
PLS-00526: A MAP or ORDER function is required for comparing objects in PL/SQL.
SQL>
We need to explicitly define a member function for executing comparisons. So here is the same object with a MAP function. The example implementation generates a hashed string, which is useful if we want to store the value for later comparison, but it could just return the concatenated string (especially as EXECUTE on DBMS_CRYPTO is not granted by default). The NVL() functions are necessary to avoid (null, value) and (value, null) being evaluated as equal. There is the always a risk when using magic values, so we need to choose them carefully.
SQL> create or replace type new_emp as object (
2 ename varchar2(10)
3 , sal number
4 , deptno number
5 , job varchar2(10)
6 , map member function equals return raw)
7 /
Type created.
SQL> create or replace type body new_emp as
2 map member function equals return raw
3 is
4 begin
5 return dbms_crypto.hash(
6 utl_raw.cast_to_raw(nvl(self.ename,'***')||
7 nvl(self.sal,-99)||
8 nvl(self.deptno,-99)||
9 nvl(self.job,'***')
10 )
11 , 1);
12 end equals;
13 end;
14 /
Type body created.
SQL>
Now we have a basis for comparing instances of our objects:
SQL> declare
2 e1 new_emp;
3 e2 new_emp;
4 begin
5 e1 := new_emp('KESTELYN', 3700, 30, 'MARKETING');
6 e2 := new_emp('KESTELYN', 3700, 30, 'MARKETING');
7 if e1 = e2 then
8 dbms_output.put_line('E2 is the same as E1');
9 else
10 dbms_output.put_line('E2 is different from E1');
11 end if;
12 end;
13 /
E2 is the same as E1
PL/SQL procedure successfully completed.
SQL>
You might be wondering why Oracle doesn't do this by default. Well, the TYPE implemntation only allows one comparison method (if we have a MAP function we cannot have an ORDER function) so we need to have the capability to choose our own definition of equality. For instance, a type called rectangle might have a MAP function called area() which returns self.width * self.length.
I have a stored procedure with an IN OUT parameter declared like follows:
create or replace PROCEDURE RIFATT_SEGN0_INS(pIdRifattSegn0 in OUT NUMBER,
pNumDossier IN VARCHAR2 ,
pNumConsegna IN NUMBER,
pDtConsegna IN DATE,
[..]
) AS
[..]
Whenever i call it from another procedure, how do i get the pIdRifattSegn0 parameter that is also out?
Your question isn't entirely clear. An IN OUT parameter is passed both ways, as its name implies. This means it has to be passed a variable, not a literal and you need a declare block to do that. For example:
declare
l_segn number;
begin
l_segn := 1;
-- procedure will have received value = 1
rifatt_segn0_ins(l_segn, 'x', 2, sysdate);
-- procedure may have changed value of l_segn from 1 to something else
dbms_output.put_line(l_segn);
end;
Here is an example:
SQL> create or replace PROCEDURE RIFATT_SEGN0_INS
2 ( pIdRifattSegn0 IN OUT NUMBER
3 , pNumDossier IN VARCHAR2
4 , pNumConsegna IN NUMBER
5 , pDtConsegna IN DATE
6 )
7 as
8 begin
9 dbms_output.put_line(pNumDossier);
10 dbms_output.put_line(to_char(pNumConsegna));
11 dbms_output.put_line(to_char(pDtConsegna,'yyyy-mm-dd'));
12 pIdRifattSegn0 := sqrt(pIdRifattSegn0);
13 end;
14 /
Procedure is aangemaakt.
SQL> create or replace procedure another_procedure
2 as
3 l_IdRifattSegn0 number := 4;
4 begin
5 rifatt_segn0_ins
6 ( pIdRifattSegn0 => l_IdRifattSegn0
7 , pNumDossier => '1A'
8 , pNumConsegna => 42
9 , pDtConsegna => sysdate
10 );
11 dbms_output.put_line('from another_procedure: l_IdRifattSegn0 = ' || to_char(l_IdRifattSegn0));
12 end;
13 /
Procedure is aangemaakt.
SQL> exec another_procedure
1A
42
2009-05-21
from another_procedure: l_IdRifattSegn0 = 2
PL/SQL-procedure is geslaagd.
Regards,
Rob.