Oracle PL/SQL How to create list of lists - oracle

in python we create complex arrays like :
[['element1',['element2',['element3'],
['element1',['element2',['element3'],
['element1',['element2',['element3']]
is there anyway to do that in PL/SQL?

You can use inheritance with Oracle types to have multiple nested collections:
Oracle Setup:
CREATE TYPE collect_abstract_type IS OBJECT(
isCollection NUMBER(1,0),
MEMBER FUNCTION toString RETURN CLOB
) NOT FINAL NOT INSTANTIABLE
/
CREATE TYPE collect_element_type UNDER collect_abstract_type (
value VARCHAR2(20),
OVERRIDING MEMBER FUNCTION toString
RETURN CLOB,
CONSTRUCTOR FUNCTION collect_element_type(value VARCHAR2)
RETURN SELF AS RESULT
)
/
CREATE TYPE BODY collect_element_type AS
OVERRIDING MEMBER FUNCTION toString
RETURN CLOB
IS
BEGIN
RETURN '"' || SELF.value || '"';
END;
CONSTRUCTOR FUNCTION collect_element_type(value VARCHAR2)
RETURN SELF AS RESULT
IS
BEGIN
SELF.isCollection := 0;
SELF.value := value;
RETURN;
END;
END;
/
CREATE OR REPLACE TYPE collect_abstract_type_table IS TABLE OF collect_abstract_type
/
CREATE TYPE collect_list_type UNDER collect_abstract_type (
items collect_abstract_type_table,
OVERRIDING MEMBER FUNCTION toString
RETURN CLOB,
CONSTRUCTOR FUNCTION collect_list_type
RETURN SELF AS RESULT,
CONSTRUCTOR FUNCTION collect_list_type(items collect_abstract_type_table)
RETURN SELF AS RESULT
)
/
CREATE TYPE BODY collect_list_type AS
OVERRIDING MEMBER FUNCTION toString
RETURN CLOB
IS
p_string CLOB;
p_first BOOLEAN := TRUE;
BEGIN
p_string := '[';
FOR i IN 1 .. SELF.items.count LOOP
IF p_first THEN
p_first := FALSE;
ELSE
p_string := p_string || ',';
END IF;
IF SELF.items(i) IS NULL THEN
p_string := p_string || 'NULL';
ELSE
p_string := p_string || SELF.items(i).toString;
END IF;
END LOOP;
p_string := p_string || ']';
RETURN p_string;
END;
CONSTRUCTOR FUNCTION collect_list_type
RETURN SELF AS RESULT
IS
BEGIN
SELF.isCollection := 1;
SELF.items := collect_abstract_type_table();
RETURN;
END;
CONSTRUCTOR FUNCTION collect_list_type(items collect_abstract_type_table)
RETURN SELF AS RESULT
IS
BEGIN
SELF.isCollection := 1;
IF items IS NULL THEN
SELF.items := collect_abstract_type_table();
ELSE
SELF.items := items;
END IF;
RETURN;
END;
END;
/
Then you could do something like:
SELECT collect_list_type(
collect_abstract_type_table(
collect_element_type( 'Element1' ),
collect_list_type(
collect_abstract_type_table(
collect_element_type( 'Element2' ),
collect_list_type()
)
),
NULL,
collect_element_type( 'Element4' )
)
).toString() AS list
FROM DUAL;
Which outputs:
LIST
--------------------------------------------
["Element1",["Element2",[]],NULL,"Element4"]

Related

Oracle dumping collection (table of objects) into clob or xml

I have MY_OBJ_TABLE type and would like to dump variable content of such either into text or xml format.
The thing is, function processing such request should be able to receive any type of table of objects, not just the MY_OBJ_TABLE.
I have looked into passAnyObject.sql which looks like a step in the right direction. Advices and solutions are greatly appreciated.
CREATE OR REPLACE TYPE "MY_OBJ" FORCE AS OBJECT (
key VARCHAR2(20),
value VARCHAR2(1000),
CONSTRUCTOR FUNCTION MY_OBJ RETURN SELF AS RESULT,
MEMBER PROCEDURE init_my_obj
);
CREATE OR REPLACE TYPE BODY "MY_OBJ" AS
CONSTRUCTOR FUNCTION MY_OBJ RETURN SELF AS RESULT
AS
BEGIN
init_my_obj ();
return;
END MY_OBJ;
MEMBER PROCEDURE init_my_obj
AS
BEGIN
key := NULL;
value := NULL;
END init_my_obj;
END;
CREATE OR REPLACE TYPE MY_OBJ_TABLE IS
TABLE OF MY_OBJ;
If you want an XML solution then:
CREATE TYPE MY_OBJ AS OBJECT (
key VARCHAR2(20),
value VARCHAR2(1000)
);
CREATE TYPE MY_OBJ_TABLE IS TABLE OF MY_OBJ;
Then you can use:
SELECT XMLELEMENT(
"OBJECTS",
XMLAGG(
XMLTYPE(VALUE(t))
)
).getClobVal() AS output
FROM TABLE(
MY_OBJ_TABLE(
MY_OBJ('a', 'one'),
MY_OBJ('b', 'two')
)
) t;
Which outputs:
OUTPUT
<OBJECTS><MY_OBJ>  <KEY>a</KEY>  <VALUE>one</VALUE></MY_OBJ><MY_OBJ>  <KEY>b</KEY>  <VALUE>two</VALUE></MY_OBJ></OBJECTS>
fiddle
You cannot achieve your goal using ANYDATA.
You can parse an individual object to a string using ANYDATA and reflection as described in my previous answer.
However, if you want to parse a collection then:
you can convert it to ANYDATA using ANYDATA.ConvertCollection( your_collection );
then when processing it you can set the evaluation mode to PIECEWISE (documentation) which will let you iterate through individual elements...
but there are no Get* methods (documentation) that allow you to extract an element as an abstract ANYDATA type; you MUST extract it as the concrete underlying type so any procedure you create must know the data type of the collection that you are passing in at compile time.
I did try to work through the problem but the functionality is not there to support an abstract solution. This is as far as I got:
CREATE PACKAGE reflection IS
TYPE type_info IS RECORD(
prec PLS_INTEGER,
scale PLS_INTEGER,
len PLS_INTEGER,
csid PLS_INTEGER,
csfrm PLS_INTEGER,
schema_name VARCHAR2(30),
type_name VARCHAR2(30),
version VARCHAR2(100),
count PLS_INTEGER
);
TYPE attr_info IS RECORD(
prec PLS_INTEGER,
scale PLS_INTEGER,
len PLS_INTEGER,
csid PLS_INTEGER,
csfrm PLS_INTEGER,
attr_elt_type ANYTYPE,
aname VARCHAR2(30)
);
FUNCTION get_size(
p_anydata IN ANYDATA
) RETURN PLS_INTEGER;
FUNCTION get_Object_At(
p_anydata IN ANYDATA,
p_index IN PLS_INTEGER DEFAULT 1
) RETURN ANYDATA;
FUNCTION get_attr_name_at(
p_anydata IN ANYDATA,
p_index IN PLS_INTEGER DEFAULT 1
) RETURN VARCHAR2;
FUNCTION get_attr_value_at(
p_anydata IN ANYDATA,
p_index IN PLS_INTEGER DEFAULT 1
) RETURN VARCHAR2;
END;
/
Then:
CREATE PACKAGE BODY reflection IS
DEBUG BOOLEAN := FALSE;
PROCEDURE get_type(
i_anydata IN ANYDATA,
o_typeid OUT PLS_INTEGER,
o_anytype OUT ANYTYPE
)
IS
BEGIN
o_typeid := i_anydata.GetType( typ => o_anytype );
END;
FUNCTION is_Object(
p_typeid PLS_INTEGER
) RETURN BOOLEAN
IS
BEGIN
RETURN p_typeid = DBMS_TYPES.TYPECODE_OBJECT;
END;
FUNCTION is_Collection(
p_typeid PLS_INTEGER
) RETURN BOOLEAN
IS
BEGIN
RETURN p_typeid = DBMS_TYPES.TYPECODE_NAMEDCOLLECTION;
END;
FUNCTION get_info(
p_anytype IN ANYTYPE
) RETURN type_info
IS
v_typeid PLS_INTEGER;
v_type_info REFLECTION.TYPE_INFO;
BEGIN
v_typeid := p_anytype.GetInfo (
v_type_info.prec,
v_type_info.scale,
v_type_info.len,
v_type_info.csid,
v_type_info.csfrm,
v_type_info.schema_name,
v_type_info.type_name,
v_type_info.version,
v_type_info.count
);
RETURN v_type_info;
END;
FUNCTION get_size(
p_anydata IN ANYDATA
) RETURN PLS_INTEGER
IS
v_anytype ANYTYPE;
v_typeid PLS_INTEGER;
BEGIN
Get_Type( p_anydata, v_typeid, v_anytype );
RETURN Get_Info( v_anytype ).COUNT;
END;
FUNCTION get_Object_At(
p_anydata IN ANYDATA,
p_index IN PLS_INTEGER DEFAULT 1
) RETURN ANYDATA
IS
v_anydata ANYDATA := p_anydata;
v_anytype ANYTYPE;
v_typeid PLS_INTEGER;
BEGIN
Get_Type( v_anydata, v_typeid, v_anytype );
IF NOT is_Collection(v_typeid) THEN
RAISE_APPLICATION_ERROR(-20000, 'Not a collection');
END IF;
v_anydata.PIECEWISE;
FOR i IN 1 .. p_index LOOP
DECLARE
v_attr_typeid PLS_INTEGER;
v_attr_info REFLECTION.ATTR_INFO;
v_result_code PLS_INTEGER;
v_value ANYDATA;
v_object MY_OBJ;
BEGIN
v_result_code := v_anydata.GetObject( v_object );
v_value := ANYDATA.ConvertObject( v_object );
-- TODO: there is no Get* function that returns an ANYDATA type that
-- would allow abstract parsing to continue. For example:
--
-- v_result_code := v_anydata.Get( v_value );
--
-- You would need to use:
--
-- DECLARE
-- v_object MY_OBJ;
-- BEGIN
-- v_result_code := v_anydata.GetObject( v_object );
-- v_value := ANYDATA.ConvertObject( v_object );
-- END;
--
-- But that hard-codes the concrete object type.
IF i = p_index THEN
RETURN v_value;
END IF;
END;
END LOOP;
RETURN NULL;
END;
FUNCTION get_attr_name_at(
p_anydata IN ANYDATA,
p_index IN PLS_INTEGER DEFAULT 1
) RETURN VARCHAR2
IS
v_anydata ANYDATA := p_anydata;
v_anytype ANYTYPE;
v_typeid PLS_INTEGER;
v_type_info REFLECTION.TYPE_INFO;
v_output VARCHAR2(4000);
v_attr_typeid PLS_INTEGER;
v_attr_info REFLECTION.ATTR_INFO;
BEGIN
Get_Type( v_anydata, v_typeid, v_anytype );
IF NOT is_Object(v_typeid) THEN
RAISE_APPLICATION_ERROR(-20000, 'Not an object');
END IF;
v_type_info := Get_Info( v_anytype );
IF p_index < 1 OR p_index > v_type_info.COUNT THEN
RETURN NULL;
END IF;
v_anydata.PIECEWISE;
v_attr_typeid := v_anytype.getAttrElemInfo(
pos => p_index,
prec => v_attr_info.prec,
scale => v_attr_info.scale,
len => v_attr_info.len,
csid => v_attr_info.csid,
csfrm => v_attr_info.csfrm,
attr_elt_type => v_attr_info.attr_elt_type,
aname => v_attr_info.aname
);
RETURN v_attr_info.aname;
END;
FUNCTION get_attr_value_at(
p_anydata IN ANYDATA,
p_index IN PLS_INTEGER DEFAULT 1
) RETURN VARCHAR2
IS
v_anydata ANYDATA := p_anydata;
v_typeid PLS_INTEGER;
v_anytype ANYTYPE;
v_type_info REFLECTION.TYPE_INFO;
v_output VARCHAR2(4000);
BEGIN
Get_Type( v_anydata, v_typeid, v_anytype );
IF NOT is_Object(v_typeid) THEN
RAISE_APPLICATION_ERROR(-20000, 'Not an object');
END IF;
v_type_info := Get_Info( v_anytype );
IF p_index < 1 OR p_index > v_type_info.COUNT THEN
RETURN NULL;
END IF;
v_anydata.PIECEWISE;
FOR i IN 1 .. p_index LOOP
DECLARE
v_attr_typeid PLS_INTEGER;
v_attr_info REFLECTION.ATTR_INFO;
v_result_code PLS_INTEGER;
BEGIN
v_attr_typeid := v_anytype.getAttrElemInfo(
pos => i,
prec => v_attr_info.prec,
scale => v_attr_info.scale,
len => v_attr_info.len,
csid => v_attr_info.csid,
csfrm => v_attr_info.csfrm,
attr_elt_type => v_attr_info.attr_elt_type,
aname => v_attr_info.aname
);
IF DEBUG THEN
DBMS_OUTPUT.PUT_LINE(
'Attribute ' || i || ': '
|| v_attr_info.aname
|| ' (type ' || v_attr_typeid || ')'
);
END IF;
CASE v_attr_typeid
WHEN DBMS_TYPES.TYPECODE_NUMBER THEN
DECLARE
v_value NUMBER;
BEGIN
v_result_code := v_anydata.GetNumber( v_value );
IF i = p_index THEN
RETURN TO_CHAR( v_value );
END IF;
END;
WHEN DBMS_TYPES.TYPECODE_VARCHAR2 THEN
DECLARE
v_value VARCHAR2(4000);
BEGIN
v_result_code := v_anydata.GetVarchar2( v_value );
IF i = p_index THEN
RETURN v_value;
END IF;
END;
WHEN DBMS_TYPES.TYPECODE_DATE THEN
DECLARE
v_value DATE;
BEGIN
v_result_code := v_anydata.GetDate( v_value );
IF i = p_index THEN
RETURN TO_CHAR( v_value, 'YYYY-MM-DD HH24:MI:SS' );
END IF;
END;
ELSE
NULL;
END CASE;
END;
END LOOP;
RETURN NULL;
END;
END;
/
Then you can parse your object using:
DECLARE
objs MY_OBJ_TABLE;
idx PLS_INTEGER := 1;
p_anydata ANYDATA;
p_attr_name VARCHAR2(30);
p_attr_value VARCHAR2(4000);
p_element ANYDATA;
BEGIN
dbms_output.enable;
objs := MY_OBJ_TABLE(
MY_OBJ('a', 'one'),
MY_OBJ('b', 'two'),
MY_OBJ('c', 'three')
);
p_anydata := ANYDATA.ConvertCollection( objs );
-- Still not worked out how to get the collection size from the ANYDATA.
FOR i IN 1 .. objs.count LOOP
DECLARE
p_element ANYDATA := REFLECTION.get_Object_at(p_anydata, i);
BEGIN
DBMS_OUTPUT.PUT_LINE( 'My Obj ' || i || ':' );
FOR attr_no IN 1 .. REFLECTION.get_size( p_element ) LOOP
p_attr_name := REFLECTION.get_attr_name_at( p_element, attr_no );
p_attr_value := REFLECTION.get_attr_value_at( p_element, attr_no );
DBMS_OUTPUT.PUT_LINE( ' ' || p_attr_name || ': ' || p_attr_value );
END LOOP;
END;
END LOOP;
END;
/
You will note in the middle of the package body the MY_OBJ type has to be hard-coded and there is not a solution in the Oracle documentation to support an abstract solution.
fiddle

How to Chain Member Functions in a PL/SQL Object Type

Perhaps this is a poor use case for what I'm trying to accomplish, but I've read through dozens of pages and I can't figure out if this is possible in Oracle or not.
I would like to accomplish something similar to another stackoverflow question: How to chain calls in a pl/sql object type of functions returning SELF
In my code, I have a type named parseName with a constructor function and 3 member functions: getFirstName, getMiddleName, getLastName. I would like to have a fourth member function called: cleanString which removes all characters not between A-Z or a-z.
Example calls:
SELECT parseName('John123 Doe').getFirstName().cleanString()
FROM dual;
SELECT parseName('John123 Doe').getFirstName()
FROM dual;
Here's the type as I have it written so far.
CREATE OR REPLACE TYPE parseName AS OBJECT
(
g_name VARCHAR2(255),
g_parts NUMBER,
g_first_name VARCHAR2(255),
g_middle_name VARCHAR2(1),
g_last_name VARCHAR2(255),
-- constructor function
CONSTRUCTOR FUNCTION parseName
(p_name IN VARCHAR2)
RETURN self AS result,
-- member functions
MEMBER FUNCTION getFirstName RETURN VARCHAR2,
MEMBER FUNCTION getMiddleName RETURN VARCHAR2,
MEMBER FUNCTION getLastName RETURN VARCHAR2
);
/
SHOW ERRORS
/
CREATE OR REPLACE TYPE BODY parseName IS
-- populateValues
CONSTRUCTOR FUNCTION parseName
(p_name IN VARCHAR2)
RETURN self AS result
IS
-- other variables
v_name VARCHAR2(255);
v_length NUMBER;
v_parts NUMBER;
v_instr NUMBER;
BEGIN
-- save off input
v_name := TRIM(p_name);
-- check
IF v_name IS NULL THEN
self.g_first_name := 'Unknown';
self.g_middle_name := ' ';
self.g_last_name := 'Unknown';
RETURN;
END IF;
-- otherwise, fill our global
self.g_name := v_name;
-- exit
RETURN;
END;
/* getFirstName */
/* --------------------------------------- */
MEMBER FUNCTION getFirstName
RETURN VARCHAR2
IS
v_parts NUMBER;
BEGIN
-- did we get a null on construct?
IF self.g_first_name IS NOT NULL THEN
RETURN self.g_first_name;
END IF;
-- how many spaces do we have?
v_parts := LENGTH(self.g_name) - LENGTH(REPLACE(self.g_name,' ',''));
-- if 0 spaces, return the name
IF v_parts = 0 THEN
RETURN self.g_name;
-- else, return everything up to the space
ELSE
RETURN TRIM(SUBSTR(self.g_name,1, INSTR(self.g_name,' ',1) ));
END IF;
END getFirstName;
/* getMiddleName */
/* --------------------------------------- */
MEMBER FUNCTION getMiddleName
RETURN VARCHAR2
IS
v_parts NUMBER;
v_instr2 NUMBER;
v_instr1 NUMBER;
BEGIN
-- did we get a null on construct?
IF self.g_middle_name IS NOT NULL THEN
RETURN NULL;
END IF;
-- how many spaces do we have?
v_parts := LENGTH(self.g_name) - LENGTH(REPLACE(self.g_name,' ',''));
-- if we have zero spaces, we only have a first name, return null
IF v_parts = 0 THEN
RETURN NULL;
-- don't do middle if we only have 1 space
ELSIF v_parts = 1 THEN
RETURN NULL;
-- else, we've got more than one, so grab between space 1 and 2
ELSE
v_instr2 := INSTR(self.g_name,' ',1,2);
v_instr1 := INSTR(self.g_name,' ',1,1);
RETURN TRIM( SUBSTR(self.g_name, v_instr1, (v_instr2-v_instr1) ));
END IF;
END getMiddleName;
/* getLastName */
/* --------------------------------------- */
MEMBER FUNCTION getLastName
RETURN VARCHAR2
IS
v_parts NUMBER;
BEGIN
-- did we get a null on construct?
IF self.g_last_name IS NOT NULL THEN
RETURN self.g_last_name;
END IF;
-- how many spaces do we have?
v_parts := LENGTH(self.g_name) - LENGTH(REPLACE(self.g_name,' ',''));
-- if we have zero spaces, we only have a first name, return 'Unknown'
IF v_parts = 0 THEN
RETURN 'Unknown';
-- if have 1 space, the space on is the last name
ELSIF v_parts = 1 THEN
RETURN TRIM( SUBSTR(self.g_name, INSTR(self.g_name,' ',1,1), LENGTH(self.g_name)) );
-- else, we've got more than one, go from 2 to end
ELSE
RETURN TRIM( SUBSTR(self.g_name, INSTR(self.g_name,' ',1,2), LENGTH(self.g_name)) );
END IF;
END getLastName;
END;
/
SHOW ERRORS
/
.
Thanks for any advice that you can provide.
I would like to have a fourth member function called: cleanString which removes all characters not between A-Z or a-z.
Example calls:
SELECT parseName('John123 Doe').getFirstName().cleanString()
FROM dual;
When you call .getFirstName() the expectation is that it returns the person's first name (after all that is what the member function's name says) and the name is a VARCHAR2 data type. VARCHAR2 is a primitive data type and is not an object that you could (somehow) extend to have a .cleanString() member function.
You can either define a cleanString() function:
CREATE FUNCTION cleanString( value VARCHAR2 ) RETURN VARCHAR2
IS
BEGIN
RETURN REGEXP_REPLACE( value, '[^[:alpha:]]+' );
END cleanString;
/
And then call:
SELECT cleanString( parseName('John123 Doe').getFirstName() )
FROM DUAL;
Or you could make a member function that cleans the names before you return them:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE OR REPLACE TYPE parseName AS OBJECT
(
g_name VARCHAR2(255),
g_first_name VARCHAR2(255),
g_middle_name VARCHAR2(1),
g_last_name VARCHAR2(255),
-- constructor function
CONSTRUCTOR FUNCTION parseName(p_name IN VARCHAR2) RETURN self AS result,
-- member functions
MEMBER FUNCTION cleanNames RETURN parseName,
MEMBER FUNCTION getFirstName RETURN VARCHAR2,
MEMBER FUNCTION getMiddleName RETURN VARCHAR2,
MEMBER FUNCTION getLastName RETURN VARCHAR2
);
/
CREATE OR REPLACE TYPE BODY parseName IS
-- populateValues
CONSTRUCTOR FUNCTION parseName(p_name IN VARCHAR2) RETURN self AS result
IS
BEGIN
g_name := TRIM(p_name);
g_first_name := REGEXP_SUBSTR( g_name, '^(\S+)\s+((\S*?)\s+)?(.*)$', 1, 1, NULL, 1 );
g_middle_name := REGEXP_SUBSTR( g_name, '^(\S+)\s+((\S*?)\s+)?(.*)$', 1, 1, NULL, 3 );
g_last_name := REGEXP_SUBSTR( g_name, '^(\S+)\s+((\S*?)\s+)?(.*)$', 1, 1, NULL, 4 );
RETURN;
END;
MEMBER FUNCTION cleanNames RETURN parseName
IS
v_name parseName := SELF;
BEGIN
v_name.g_first_name := cleanString( SELF.g_first_name );
v_name.g_middle_name := cleanString( SELF.g_middle_name );
v_name.g_last_name := cleanString( SELF.g_last_name );
RETURN v_name;
END;
MEMBER FUNCTION getFirstName RETURN VARCHAR2
IS
BEGIN
RETURN g_first_name;
END getFirstName;
MEMBER FUNCTION getMiddleName RETURN VARCHAR2
IS
BEGIN
RETURN g_middle_name;
END getMiddleName;
MEMBER FUNCTION getLastName RETURN VARCHAR2
IS
BEGIN
RETURN g_last_name;
END getLastName;
END;
/
Query 1:
SELECT parseName('John123 Doe').getFirstName(),
parseName('John123 Doe').cleanNames().getFirstName()
FROM DUAL
Results:
| PARSENAME('JOHN123DOE').GETFIRSTNAME() | PARSENAME('JOHN123DOE').CLEANNAMES().GETFIRSTNAME() |
|----------------------------------------|-----------------------------------------------------|
| John123 | John |

Recursive call error with stored function

I created this stored function :
create or replace function recuperer_ascendants_structure(p_struct_code varchar2, previous_codes varchar2)
return varchar2
is
sep varchar2(1) := '';
ret varchar2(4000);
v_ascendant_code structure.str_struct_code%type;
begin
execute immediate 'select str_struct_code from structure where struct_code = :1' into v_ascendant_code using p_struct_code;
if v_ascendant_code is null then
if previous_codes is null then
return p_struct_code;
else
return p_struct_code || ',' || previous_codes;
end if;
else
if previous_codes is null then
ret := recuperer_ascendants_structure(v_ascendant_code , v_ascendant_code);
else
ret := recuperer_ascendants_structure(v_ascendant_code , p_struct_code || ',' || v_ascendant_code);
end if;
end if;
end;
At runtime I get this error :
SQL> select recuperer_ascendants_structure('21.12.07',null) prts from dual;
select recuperer_ascendants_structure('21.12.07',null) prts from dual
*
ERROR at line 1:
ORA-06503: PL/SQL: Function returned without value
ORA-06512: at "SSE.RECUPERER_ASCENDANTS_STRUCTURE", line 22
ORA-06512: at "SSE.RECUPERER_ASCENDANTS_STRUCTURE", line 17
So what is wrong ?
This error reason can be read as is: 'Function returned without value'. Is means that execution reachs the final end without any return statement so there is no any value which can be returned to caller.
It looks like you should replace ret := with return to avoid this error.
I'd suggest you to avoid multiple RETURN statements. Collect information about the final result (RET variable in your code) anywhere, but return it only once, at the end of the function.
CREATE OR REPLACE FUNCTION recuperer_ascendants_structure (
p_struct_code VARCHAR2,
previous_codes VARCHAR2)
RETURN VARCHAR2
IS
sep VARCHAR2 (1) := '';
ret VARCHAR2 (4000);
v_ascendant_code structure.str_struct_code%TYPE;
BEGIN
EXECUTE IMMEDIATE
'select str_struct_code from structure where struct_code = :1'
INTO v_ascendant_code
USING p_struct_code;
IF v_ascendant_code IS NULL
THEN
IF previous_codes IS NULL
THEN
ret := p_struct_code;
ELSE
ret := p_struct_code || ',' || previous_codes;
END IF;
ELSE
IF previous_codes IS NULL
THEN
ret :=
recuperer_ascendants_structure (v_ascendant_code,
v_ascendant_code);
ELSE
ret :=
recuperer_ascendants_structure (
v_ascendant_code,
p_struct_code || ',' || v_ascendant_code);
END IF;
END IF;
RETURN ret; --> here!
END;

Oracle Dynamic delete statements with DECODE Function

Hi I have the following procedure:
create or replace procedure
SP_DELETE_FROM_TABLE(pTableName in VARCHAR2, pFieldName in VARCHAR2,
pFieldValue in VARCHAR2,pFieldType in VARCHAR2) is
querystring VARCHAR2(500);
begin
queryString := 'DELETE FROM ' ||pTableName||
' WHERE '||pFieldName ||' = DECODE(:pFieldType,integer,:pFieldValue)' ;
EXECUTE IMMEDIATE queryString USING pFieldType,pFieldValue;
end SP_DELETE_FROM_TABLE;
all my Parameters are of Type VARCHAR2, What I am trying to do is: When I call the procedure with the following values ('users_table','users_id','11','integer')
so by using DECODE I would like to check if pFieldValue is of type pFieldTypeand if yes return pFieldValue
so if pFieldValue is:11 and pfieldType is:integer it should delete users_id 11 if fieldType is string do nothing..
I'd create a function that checks parameter has correct type
and then use it in main procedure
--main procedure
create or replace procedure SP_DELETE_FROM_TABLE(pTableName in VARCHAR2, pFieldName in VARCHAR2,pFieldValue in VARCHAR2,pFieldType in VARCHAR2) is
querystring VARCHAR2(500);
begin
if 'Y' = is_type_correct(pFieldValue, pFieldType ) then
queryString := 'DELETE FROM ' ||pTableName|| ' WHERE '
||pFieldName ||' = :pFieldValue';
EXECUTE IMMEDIATE queryString USING pFieldValue;
end
else
null; --incorrect type and parameter, do nothing
end;
end SP_DELETE_FROM_TABLE;
--check function
CREATE OR REPLACE FUNCTION is_type_correct( p_val IN VARCHAR2, p_type varchar2 )
RETURN VARCHAR2 DETERMINISTIC PARALLEL_ENABLE
IS
l_num NUMBER;
l_date date;
BEGIN
if 'integer' = p_type
then
l_num := to_number( p_val );
elsif 'date' = p_type then
l_date := to_date(p_val, 'YYYY.MM.DD');
elsif 'varchar2' then
null;//do nothing
else
return 'N'; //uknown type
end if;
RETURN 'Y';
EXCEPTION
WHEN value_error THEN
RETURN 'N';
END is_number;
Just try to convert a string to a number, and when an exception occurs, do nothing:
querystring VARCHAR2(500);
some_number number;
begin
some_number := to_number( pFieldValue ); /* trying to convert to a number */
queryString := 'DELETE FROM ' ||pTableName||
' WHERE '||pFieldName ||' = :x' ;
EXECUTE IMMEDIATE queryString USING some_number;
EXCEPTION
WHEN VALUE_ERROR THEN null; /* do nothing when pFieldValue is not a number*/
end SP_DELETE_FROM_TABLE;

Dynamic typing or generics in Oracle PL/SQL

For some reason, I have certain fields in a table, that are collections of chars. Chars of different length. Example:
create or replace type t_charray_1 as varray(5) of char(1);
create or replace type t_charray_2 as varray(5) of char(2);
create or replace type t_charray_3 as varray(5) of char(3);
create or replace type t_charray_4 as varray(5) of char(4);
create table mytable (
field1 number,
field2 t_charray_1,
field3 t_charray_3,
Also, I have a function that returns a (fixed length) string representation of a mytable record. This function calls other functions that are returning the string representation of a given collection-typed field. Examples:
function to_chr(
p_array in t_charray_1,
pad_length in number,
p_list_length in number
) return char as
v_res varchar2(255) := '';
begin
for i in 1 .. p_list_length loop
if p_array is not null and p_array.exists(i) and p_array(i) is not null then
v_res := v_res || rpad(p_array(i), pad_length, ' ');
else
v_res := v_res || rpad(' ', pad_length, ' ');
end if;
end loop;
return v_res;
end to_chr;
------------------------------------------------------------------------------
function to_chr(
p_array in t_charray_2,
pad_length in number,
p_list_length in number
) return char as
v_res varchar2(255) := '';
begin
for i in 1 .. p_list_length loop
if p_array is not null and p_array.exists(i) and p_array(i) is not null then
v_res := v_res || rpad(p_array(i), pad_length, ' ');
else
v_res := v_res || rpad(' ', pad_length, ' ');
end if;
end loop;
return v_res;
end to_chr;
Note that these functions are overloaded versions of each other. The only difference in their signature is the type of the p_array argument.
Please also note that the bodies of these functions are identical.
Motivation
I want to eliminate duplicate code. What are my choices?
EDIT I have heard of sys.anydata but never used it. Can it be a solution?
You could write a procedure that takes the largest type, and explicitly CAST the smaller types to the larger type before passing them. Note that CAST can only be used in SQL
DECLARE
x t_charray_1 := t_charray_1();
y t_charray_2 := t_charray_2();
PROCEDURE foo( p_foo t_charray_2 )
AS
BEGIN
FOR i IN p_foo.FIRST..p_foo.LAST loop
dbms_output.put_line( p_foo(i) );
END LOOP;
END;
BEGIN
x.EXTEND;
x.EXTEND;
x(1) := 'A';
x(2) := 'B';
y.EXTEND;
y.EXTEND;
y(1) := 'AA';
y(2) := 'BB';
foo(y);
SELECT CAST(x AS t_charray_2) INTO y FROM dual;
foo(y);
END;
/
Try:
create or replace function to_chr(p_array in anydata,
pad_length in number,
p_list_length in number) return char as
v_res varchar2(255) := '';
x number;
v_array t_charray_4;
v_array1 t_charray_1;
v_array2 t_charray_2;
v_array3 t_charray_3;
begin
dbms_output.put_line(p_array.GetTypeName);
case p_array.GetTypeName
when '<schema>.T_CHARRAY_1' then
x := p_array.GetCollection(v_array1);
select cast(v_array1 as t_charray_4) into v_array from dual;
when '<schema>.T_CHARRAY_2' then
x := p_array.GetCollection(v_array2);
select cast(v_array2 as t_charray_4) into v_array from dual;
when '<schema>.T_CHARRAY_3' then
x := p_array.GetCollection(v_array3);
select cast(v_array3 as t_charray_4) into v_array from dual;
when '<schema>.T_CHARRAY_4' then
x := p_array.GetCollection(v_array);
end case;
for i in 1 .. p_list_length loop
if v_array is not null and v_array.exists(i) and v_array(i) is not null then
v_res := v_res || rpad(v_array(i), pad_length, ' ');
else
v_res := v_res || rpad(' ', pad_length, ' ');
end if;
end loop;
return v_res;
end to_chr;
you can run it like this:
declare
p_array anydata;
v_array t_charray_3 := new t_charray_3('aaa', 'bbb');
v_res varchar2(255);
begin
p_array := anydata.convertcollection(v_array);
v_res := to_chr(p_array => p_array, pad_length => 2, p_list_length => 3);
end;

Resources