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
Related
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"]
I created a package which loads data from a staging table to a live table in Oracle DB. The package comprises of four functions which perform live load, update, insert and error check on the records. For some reason, I am only able to load one record from the staging table and then the following error occurs:
ORA-02290: check constraint (VIQDATA.SYS_C0012762) violated ORA-01403: no data found
create or replace PACKAGE VAL_PROC
AS
pi_sif_id NUMBER := 0;
CURSOR v_d_c (pi_sif_id NUMBER)
IS
SELECT *
FROM S_V_C_R
WHERE SIF_ID =PI_SIF_ID
AND SVC_PROCESS_FLAG='N';
TYPE v_d_c_t IS TABLE OF v_d_c%ROWTYPE;
FUNCTION vr_live_load (pi_sif_id NUMBER,
po_err_msg OUT VARCHAR2,
po_success_count OUT NUMBER,
po_failed_count OUT NUMBER)
RETURN NUMBER;
FUNCTION check_error_record (p_v_d_r IN vr_dtl_cur%ROWTYPE,
lv_prc_code OUT VARCHAR2,
lv_err_flag OUT VARCHAR2)
RETURN NUMBER;
FUNCTION insert_v (p_v_d_r IN v_d_c%ROWTYPE, po_err_msg OUT VARCHAR2, po_prc_code OUT VARCHAR2)
RETURN NUMBER;
FUNCTION update_v (p_v_d_r IN v_d_c%ROWTYPE, po_err_msg OUT VARCHAR2, po_prc_code OUT VARCHAR2)
RETURN NUMBER;
END VAL_PROC;
create or replace PACKAGE BODY VAL_PROC
AS
LV_X1_ID SERIALS.X1_ID%TYPE;
LV_VC_ID VR_COM.X1_ID%TYPE; --change name of this variable later
FUNCTION vr_live_load(pi_sif_id NUMBER, --live load function
po_err_msg OUT VARCHAR2,
po_success_count OUT NUMBER,
po_failed_count OUT NUMBER)
RETURN NUMBER
IS
v_d_r v_d_c_tbl;
dup_prc NUMBER := 0;
E_DUP_PRC EXCEPTION;
RETVAL NUMBER;
pi_d_co VARCHAR2(10);
ERR_FLAG VARCHAR2 (1);
ret_prc_code VARCHAR2 (15);
lv_err_code VARCHAR2 (1000);
lv_found NUMBER;
lv_PRS_ID NUMBER;
lv_sif_filename VARCHAR2 (200);
lv_proc_success_count NUMBER := 0;
lv_proc_failed_count1 NUMBER := 0;
BEGIN
BEGIN
SELECT COUNT (1)
INTO DUP_PRC
FROM s_fi
WHERE SIF_ID = PI_SIF_ID AND s_p_f = 'P';
IF dup_prc > 0
THEN
RAISE e_dup_prc;
END IF;
SELECT sif_filename
INTO lv_sif_filename
FROM s_fi
WHERE sif_id = pi_sif_id;
INSERT INTO p_s (PCG_CODE,
SIF_ID,
PRS_START_DATETIME,
sif_filename)
VALUES ('V_ODL',
pi_sif_id,
SYSDATE,
lv_sif_filename)
RETURNING PRS_ID
INTO lv_PRS_ID;
COMMIT;
OPEN v_d_c(pi_sif_id);
LOOP
FETCH v_d_c
BULK COLLECT INTO v_d_r
LIMIT 1000;
FOR i IN 1 .. v_d_r.COUNT
LOOP
retval :=
check_error_record (p_v_d_r => v_d_r(i),
lv_prc_code => ret_prc_code,
lv_err_flag => err_flag);
IF retval = 0
THEN
IF ERR_FLAG = 'N'
THEN
BEGIN
SELECT 1
INTO lv_found
FROM VR_COM
WHERE X1_ID = LV_X1_ID; --v_d_r(i).SVC_d_co = D.X1_ID;
retval :=
update_vr (p_v_d_r => v_d_r (i),
po_err_msg => lv_err_code,
po_prc_code => ret_prc_code);
UPDATE STG_COM_RECORDS
SET PRC_CODE = ret_prc_code,
S_E_M = lv_err_code,
S_P_F = 'P'
WHERE S_ID = v_d_r (i).S_ID;
IF retval = 0
THEN
lv_proc_success_count := lv_proc_success_count + 1;
ELSE
lv_proc_failed_count1 := lv_proc_failed_count1 + 1;
END IF;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
retval :=
insert_v (
p_v_d_r => v_d_r (i),
po_err_msg => lv_err_code,
po_prc_code => ret_prc_code);
UPDATE STG_COM_RECORDS
SET PRC_CODE = ret_prc_code,
S_E_M = lv_err_code,
S_P_F = 'P'
WHERE S_ID = v_d_r(i).S_ID;
IF retval = 0
THEN
lv_proc_success_count :=
lv_proc_success_count + 1;
ELSE
lv_proc_failed_count1 :=
lv_proc_failed_count1 + 1;
END IF;
END;
ELSE /*update table with prc_code when err_flag <> 'N' */
UPDATE STG_COM_RECORDS
SET PRC_CODE = ret_prc_code,
S_E_M = lv_err_code,
S_P_F = 'E'
WHERE S_ID = v_d_r (i).S_ID;
lv_proc_failed_count1 := lv_proc_failed_count1 + 1;
END IF;
ELSE --if check_error_record return non-zero value
UPDATE STG_COM_RECORDS
SET PRC_CODE = ret_prc_code,
S_E_M = lv_err_code,
S_P_F = 'E'
WHERE S_ID = v_d_r (i).S_ID;
lv_proc_failed_count1 := lv_proc_failed_count1 + 1;
END IF;
COMMIT;
END LOOP;
EXIT WHEN v_d_c%NOTFOUND;
END LOOP;
CLOSE v_d_c;
COMMIT;
EXCEPTION
WHEN e_dup_prc
THEN --file already processed
UPDATE s_fi
SET s_p_f = 'E'
WHERE sif_id = pi_sif_id;
COMMIT;
po_err_msg := 'This File has previously been processed.';
END;
UPDATE s_fi
SET s_p_f = 'P'
WHERE sif_id = pi_sif_id;
UPDATE p_s
SET PLS_END_DATETIME = SYSDATE, PRS_ERRORED = 'Successful'
WHERE PRS_ID = lv_PRS_ID;
--Populate file load summary
BEGIN
p_p_f_l_s (P_SIF_ID => pi_sif_id);
EXCEPTION
WHEN OTHERS
THEN
NULL;
END;
COMMIT;
po_success_count := lv_proc_success_count;
po_failed_count := lv_proc_failed_count1;
RETURN 0;
EXCEPTION
WHEN OTHERS
THEN
lv_err_code := SQLERRM;
UPDATE s_fi
SET s_p_f = 'E'
WHERE sif_id = pi_sif_id;
UPDATE p_s
SET PLS_END_DATETIME = SYSDATE, PRS_ERRORED = lv_err_code
WHERE PRS_ID = lv_PRS_ID;
COMMIT;
po_err_msg := SQLERRM;
CLOSE v_d_c;
RETURN SQLCODE;
END vr_live_load;
FUNCTION insert_v (p_v_d_r IN v_d_c%ROWTYPE, po_err_msg OUT VARCHAR2,po_prc_code OUT VARCHAR2) --inserts records to live table
RETURN NUMBER
IS
RETNUM number := 0;
begin
INSERT INTO VR_COM (VC_ID,
co_pe,
COUNTRY_CODE,
X1_ID,
TIME_PERIOD
)
VALUES (VC_ID_SQ.NEXTVAL,
p_v_d_r.s_perc,
p_v_d_r.s_coun,
(select X1_ID from SERIALS where d_co=p_v_d_r.SVC_d_co),
p_v_d_r.s_time
);
po_prc_code := 'S11';
RETURN retnum;
EXCEPTION
WHEN OTHERS
THEN
PO_ERR_MSG := SQLERRM;
PO_PRC_CODE := 'O11';
DBMS_OUTPUT.PUT_LINE('SQLERRM :'||SQLERRM);
RETURN SQLCODE;
END insert_v;
FUNCTION update_vr (p_v_d_r IN v_d_c%ROWTYPE, po_err_msg OUT VARCHAR2, po_prc_code OUT VARCHAR2) --update the live table if data matches by ID
RETURN NUMBER
IS
LV_d_co SERIALS.d_co%TYPE;
BEGIN
SELECT VRC.X1_ID, D.d_co
INTO LV_VC_ID, LV_d_co
FROM VR_COM VRC, SERIALS D
WHERE VRC.X1_ID = D.X1_ID;
update VR_COM
set co_pe = p_v_d_r.s_perc,
COUNTRY_CODE = p_v_d_r.s_coun,
TIME_PERIOD = p_v_d_r.s_time
WHERE X1_ID = LV_VC_ID;
return 0;
EXCEPTION
WHEN OTHERS
THEN
po_err_msg := SQLERRM;
PO_PRC_CODE := 'O11';
DBMS_OUTPUT.PUT_LINE('SQLERRM :'||SQLERRM);
RETURN SQLCODE;
END update_vr;
FUNCTION check_error_record (p_v_d_r IN v_d_c%ROWTYPE,
lv_prc_code OUT VARCHAR2,
lv_err_flag OUT VARCHAR2) --check error
RETURN NUMBER
IS
--LV_d_co SERIALS.d_co%TYPE;
retnum NUMBER := 0;
lv_found VARCHAR2 (10);
LV_FLAG varchar2 (2);
LV_PROC_FLAG VARCHAR2 (2);
BEGIN
lv_prc_code := NULL;
lv_err_flag := NULL;
BEGIN
IF LV_X1_ID IS NOT NULL THEN
SELECT d_co, d_a_s_c
INTO LV_X1_ID, LV_FLAG
FROM SERIALS
WHERE p_v_d_r.SVC_d_co =d_co;
END IF;
lv_err_flag := 'N';
EXCEPTION
WHEN NO_DATA_FOUND
THEN
lv_prc_code := 'P5';
lv_err_flag := 'Y';
lv_proc_flag := 'E';
END;
IF p_v_d_r.SVC_d_co IS NULL
THEN
lv_prc_code := 'V2';
lv_err_flag := 'Y';
ELSIF p_v_d_r.s_perc IS NULL
THEN
lv_prc_code := 'V6';
lv_err_flag := 'Y';
ELSIF p_v_d_r.s_time IS NULL
THEN
lv_prc_code := 'V4';
lv_err_flag := 'Y';
ELSIF p_v_d_r.s_coun IS NULL
THEN
lv_prc_code := 'V1';
lv_err_flag := 'Y';
ELSE
lv_prc_code := 'S11';
lv_err_flag := 'N';
end if;
RETURN retnum;
EXCEPTION
WHEN OTHERS
THEN
lv_err_flag := 'Y';
RETURN SQLCODE;
END check_error_record;
END VAL_PROC;
P.S SYS_C0012762 constraint Check SVC_PROCESS_FLAG IN ('E', 'N', 'S')
Looks like you are trying to introduce a new value into that flag.
At the moment, the database table only allows a value of E, N or S in the column SVC_PROCESS_FLAG.
If you're trying to put a value of P in that field, you would need to replace the check constraint to also allow it as valid.
how to modify an existing check constraint?
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;
I have a package specification:
G_PKG_NAME CONSTANT VARCHAR2(30) := 'XX_CUST_PKG';
PROCEDURE customer_load
( errbuff OUT NOCOPY VARCHAR2
, retcode OUT NOCOPY VARCHAR2);
And body with procedure which calls to HZ_PARTY_V2PUB API. It uses cursor to take data from a table and then sends it to API :
PROCEDURE create_customer
( errbuff OUT NOCOPY VARCHAR2
, retcode OUT NOCOPY VARCHAR2)
IS
ERR_SOURCE CONSTANT VARCHAR2(100) := G_PKG_NAME ||'.create_customer';
CURSOR c_load
IS
SELECT rowid row_id
, person_first_name
, person_last_name
, title
, known_as
, person_identifier
, gender
FROM xx_customer_info
WHERE NVL(status_flag, 'X') <> 'S';
r_load c_load%ROWTYPE;
--p_init_msg_list VARCHAR2(1) := FND_API.G_TRUE;
v_gender VARCHAR2(30); --hz_parties.sex%TYPE;
v_title VARCHAR2(60); --hz_parties.title%TYPE;
--API record type
person_rec HZ_PARTY_V2PUB.PERSON_REC_TYPE;
-- API output variables
x_return_status VARCHAR2(1);
x_msg_count NUMBER;
x_msg_data VARCHAR2(2000);
x_party_id NUMBER;
x_party_number VARCHAR2(30);
x_profile_id NUMBER;
EXC_VALDN_ERR EXCEPTION;
BEGIN
errbuff := ' ';
retcode := RTN_SUCCESS;
msg_log ('Inside '||ERR_SOURCE);
FOR r_load in c_load LOOP
BEGIN
x_msg_data := NULL;
x_return_status := fnd_api.G_RET_STS_SUCCESS;
fnd_msg_pub.initialize;
-- example validation:
IF r_load.person_first_name IS NULL THEN
x_msg_data := ' "First name" cannot be null';
RAISE EXC_VALDN_ERR;
END IF;
-- Same validation for person_last_name here
-- Record Type:
person_rec.person_first_name := r_load.person_first_name;
person_rec.person_last_name := r_load.person_last_name;
person_rec.person_title := v_title;
person_rec.known_as := null;
person_rec.gender := v_gender;
person_rec.created_by_module := 'TCA_V2_API';
HZ_PARTY_V2PUB.create_person ( p_init_msg_list => FND_API.G_TRUE
, p_person_rec => person_rec
, x_party_id => x_party_id
, x_party_number => x_party_number
, x_profile_id => x_profile_id
, x_return_status => x_return_status
, x_msg_count => x_msg_count
, x_msg_data => x_msg_data);
msg_log('==========================');
msg_log('first name / last_name : '||r_load.person_first_name||' | '||r_load.person_last_name);
msg_log('x_return_status: '||x_return_status);
msg_log('x_msg_count: '||x_msg_count);
msg_log('x_msg_data: '||x_msg_data);
IF NVL(x_return_status, FND_API.G_RET_STS_ERROR) <> FND_API.G_RET_STS_SUCCESS THEN
IF NVL(x_msg_count, 0) > 1 THEN
FOR i IN 1..x_msg_count LOOP
x_msg_data := x_msg_data||i||'. '||substr(fnd_msg_pub.get(p_encoded => fnd_api.g_false ), 1, 255)||' , ';
msg_log(x_msg_data);
END LOOP;
END IF;
END IF;
msg_log('==========================');
EXCEPTION
WHEN OTHERS THEN
x_msg_data := 'EXC: '||NVL(x_msg_data, SQLERRM);
x_return_status := FND_API.G_RET_STS_ERROR;
END;
UPDATE xx_customer_info
SET status_flag = x_return_status
, error_message = x_msg_data
WHERE rowid = r_load.row_id;
END LOOP;
COMMIT;
msg_log ('Exit '||ERR_SOURCE);
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
msg_log('ERROR : '||ERR_SOURCE||' : '||NVL(SQLERRM, x_msg_data));
errbuff := 'ERROR : '||ERR_SOURCE||' : '||NVL(SQLERRM, x_msg_data);
retcode := RTN_ERROR;
END create_customer;
It should return errors or success.
When I test and run this in anonymous block:
begin
XX_CUST_PKG.create_customer;
end;
I get error message PLS-00306: wrong number or types of arguments in call to 'CREATE_CUSTOMER'. I can't see clearly where this error is referring to. I only have 2 OUT parameters, it should only give errbuff (which is x_msg_data) and retcode which is RTN_SUCCESS, RTN_WARNING or RTN_ERROR (I have this declared as constants '0', '1', '2') to output.
This was rewritten from initial package to the above example code, so that it handles exceptions, and few things had to be modified, but now I'm confused when testing it.
What did I leave out?
Any help?
The error you get is a PL/SQL compilation error, as you can see the error code starts with PLS-XXXX.. So it is not returned from your procedure. You missed to send the variables to hold the values from your procedure (out arguments)
Declare
errbuf varchar2(4000);
retcode varchar2(10);
begin
XX_CUST_PKG.create_customer(errbuf,retcode);
--printing the values
DBMS_OUTPUT.PUT_LINE(retcode||' '||errbuf);
end;
/
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;