I"m converting an oracle stored procedure to Postgres. Oracle uses an out parameter
O_MTG_CURSOR OUT SYS_REFCURSOR
that is included as a parameter to call to another stored procedure:
GET_MTG(O_MTG_CURSOR ,O_SQLCODE,O_SQLMSG );
When I try to call the same stored procedure in postgres, the cursor is null
CALL GET_MTG(O_MTG_CURSOR,O_SQLCODE,O_SQLMSG );
Is there any way to pass the cursor as a parameter and have it return the results from the called stored procedure?
This Oracle procedure makes 10 different calls to other stored procedures and fills the output cursors with the results of those calls. SO I need to be able to repeat this in postgres.
Minimal code that shows the problem
CREATE OR REPLACE PROCEDURE get_mtg (
i_mtg_id text,
i_lookup_type text,
i_agd text,
i_timestamp timestamp without time zone,
INOUT o_mtg_cursor refcursor DEFAULT NULL::refcursor,
INOUT o_dir_ocursor refcursor DEFAULT NULL::refcursor,
INOUT o_error_code integer DEFAULT 0,
INOUT o_error_msg character varying DEFAULT 'SUCCESS'::character varying)
LANGUAGE 'plpgsql'
AS $BODY$
DECLARE
lookup_type_missing CHARACTER VARYING := 'lookup_type_missing';
mtg_id_missing CHARACTER VARYING := 'mtg_id_missing';
mtg_not_found CHARACTER VARYING := 'mt_not_found';
system_exception CHARACTER VARYING := 'system_exception';
dir_not_found CHARACTER VARYING := 'dir_not_found';
data_is_in_flux_state CHARACTER VARYING := 'data_is_in_flux_state';
l_agd_nbr CHARACTER VARYING(20);
BEGIN
RAISE NOTICE USING MESSAGE = '[GET_MTG] START';
o_error_code := 0;
o_error_msg := CONCAT_WS('', 'SUCESSFUL');
IF i_lookup_type IS NULL THEN
/* RAISE THE EXCEPTION THAT LOOK UP TYPE CANNOT BE NULL */
RAISE USING detail = lookup_type_missing, hint = 1;
ELSIF i_mtg_id IS NULL THEN
/* RAISE THE EXCEPTION THAT MTG_ID CANNOT BE NULL. */
RAISE USING detail = mtg_id_missing, hint = 1;
ELSE
RAISE NOTICE USING MESSAGE = '[GET_MTG] GET THE OUT PUT RESULT SET - START';
CALL GET_MTG_DIR(o_dir_cursor,O_ERROR_CODE,O_ERROR_MSG );
CALL GET_MTG_DTL(o_mtg_cursor,O_ERROR_CODE,O_ERROR_MSG );
END IF;
RAISE NOTICE USING MESSAGE = '[GET_MTG] END';
EXCEPTION
WHEN raise_exception THEN
DECLARE
exc$name CHARACTER VARYING;
exc$code CHARACTER VARYING;
BEGIN
GET STACKED DIAGNOSTICS exc$name := pg_exception_detail,
exc$code := pg_exception_hint;
IF exc$name = mtg_id_missing THEN
o_error_code := 101;
o_error_msg := 'ERROR 101.1 : I_MTG_ID CAN NOT BE NULL : RESOLUTION : PASS ''AGD_NBR''';
END IF;
IF exc$name = mtg_not_found THEN
o_error_code := 101;
o_error_msg := CONCAT_WS('', 'ERROR 101.2 : MTG CANNOT BE FOUND : ', i_agd_nbr, ' : RESOLUTION : CHECK THE MTG TABLE');
END IF;
IF exc$name = dir_not_found THEN
o_error_code := 102;
o_error_msg := CONCAT_WS('', 'ERROR 102.1 : DIR IS EMPTY FOR PREF TYPE ''Z'' ON : ', i_mtg_id, ' & ', l_agd_nbr, ' : RESOLUTION : ESCALATE TO SYSADMIN');
END IF;
IF exc$name = data_is_in_flux_state THEN
o_error_code := 501;
o_error_msg := CONCAT_WS('', 'ERROR 501.1 : AGD/DIR/PREF are in flux state - ', i_mtg_id, ', ', l_agd_nbr, ' : RESOLUTION : ESCALATE TO SYSADMIN ');
END IF;
IF exc$name = system_exception THEN
o_error_code := SQLSTATE * - 1;
o_error_msg := CONCAT_WS('', '[GET_MTG] SYSTEM EXCEPTION OCCURED : ', substr(SQLERRM, 1, 200));
END IF;
END;
WHEN no_data_found THEN
o_error_code := 100;
o_error_msg = 'Error: ' || SQLSTATE || ' - ' || SQLERRM;
WHEN others THEN
o_error_code = -1;
o_error_msg = 'Error: ' || SQLSTATE || ' - ' || SQLERRM;
END;
$BODY$;
Thanks for your input but I am not sure I understand your issue. Maybe my answer will help.
Here is an example how you can use refcursor with stored procedures.
Here is the source code:
create table t(x int, t text);
insert into t values(1, 'ONE');
insert into t values(2, 'TWO');
--
create or replace procedure prc3 (inout p_rc refcursor)
as $$
declare
l_rc refcursor;
begin
open l_rc for select * from t;
p_rc = l_rc;
end;
$$ language plpgsql;
--
\echo
--
create or replace procedure prc2 (inout p_rc refcursor)
as $$
declare
l_rc refcursor;
begin
call prc3(l_rc);
p_rc = l_rc;
end;
$$ language plpgsql;
--
\echo
--
create or replace procedure prc1()
as $$
declare
v refcursor;
vx int;
vy text;
begin
call prc2(v);
fetch next from v into vx, vy;
raise notice 'vx=% vy=%', vx, vy;
end
$$ language plpgsql;
--
call prc1();
And here is an execution:
create table t(x int, t text);
CREATE TABLE
insert into t values(1, 'ONE');
INSERT 0 1
insert into t values(2, 'TWO');
INSERT 0 1
create or replace procedure prc3 (inout p_rc refcursor)
as $$
declare
l_rc refcursor;
begin
open l_rc for select * from t;
p_rc = l_rc;
end;
$$ language plpgsql;
CREATE PROCEDURE
create or replace procedure prc2 (inout p_rc refcursor)
as $$
declare
l_rc refcursor;
begin
call prc3(l_rc);
p_rc = l_rc;
end;
$$ language plpgsql;
CREATE PROCEDURE
create or replace procedure prc1()
as $$
declare
v refcursor;
vx int;
vy text;
begin
call prc2(v);
fetch next from v into vx, vy;
raise notice 'vx=% vy=%', vx, vy;
end
$$ language plpgsql;
CREATE PROCEDURE
call prc1();
psql:trc.sql:44: NOTICE: vx=1 vy=ONE
CALL
Related
Create procedure p(p1 clob) as
(
##code goes here..
);
exec p('100k+ length string...');
When I tried above procedure with 100k+ length string it is throwing ORA-20002: -20002:ORA-20002: -6502:ORA-06502: PL/SQL: numeric or value error\nORA-06512: at
How can we pass the value to the stored procedure?
Do we need to increase the db_block_size to increase the capacity of CLOB datatype?
You can pass the clob to the procedure just like other data types (on db<>fiddle):
create or replace procedure p (p1 clob) as
begin
dbms_output.put_line ('clob len='||length (p1));
end;
/
declare
c clob := '123';
begin
for i in 1..4 loop
dbms_lob.append (c, rpad ('A', 32767, 'A')); end loop;
p (c);
end;
/
clob len=131071
I think you are mistaken what a CLOB datatype can store with the procedure put_line of dbms_output
DBMS_OUTPUT.PUT_LINE ( item IN VARCHAR2);
The item is a varchar2 object. So, if you want to print the output of a clob greater than the limit of a varchar, you need to create a different kind of code. In my case, I use always the below code to print a clob column:
create or replace procedure print_clob_to_output (p_clob in clob)
is
l_offset pls_integer := 1;
l_chars pls_integer;
begin
loop
exit when l_offset > dbms_lob.getlength(p_clob);
l_chars := dbms_lob.instr(p_clob, chr(10), l_offset, 1);
if l_chars is null or l_chars = 0 then
l_chars := dbms_lob.getlength(p_clob) + 1;
end if;
dbms_output.put_line(dbms_lob.substr(p_clob, l_chars - l_offset, l_offset));
l_offset := l_chars + 1;
end loop;
end print_clob_to_output;
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;
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 writing this procedure and I have question how can I overwrite old value on new Value and return the new line with new big one string with new mails.
Procedure split one big email which have got emails on few single mails and change domain.
Perception: I got one table with atributes example Values which have one big string with emails. I must change this emails to domain NewDomain.pl when its diffrent from aaa.pl and bbb.pl when its the same I leave this emails. example:
Old Value: 'zamowienia#kicius.pl mickey.mouse#aaa.pl kimus.walus#domek.pl'
and result I want update:
**New Value: 'zamowienia#NewDomain.pl mickey.mouse#aaa.pl kimus.walus#NewDomain.pl'
First procedure:
CREATE OR REPLACE PROCEDURE changeMailAll
AS
BEGIN
DECLARE delim char(3);
cur_position INTEGER(11) DEFAULT 1;
r_remainder VARCHAR(250);
cur_string VARCHAR(1000);
delim_length INTEGER;
length_remainder INTEGER;
mail VARCHAR(255);
MAILs VARCHAR(20000);
v_value VARCHAR2(255);
v_valueNew VARCHAR2(255);
v_customerId VARCHAR(20);
c INTEGER;
d INTEGER;
positionMonkey INTEGER;
v_identity VARCHAR(50);
domena VARCHAR2(50);
v_loop VARCHAR(100);
adres VARCHAR(255);
**str PKT_StringFnc.t_array;**
CURSOR cursorMails IS
SELECT Customer_Id, Value FROM PKT_userTrue where method_id = 'E_MAIL';
BEGIN
OPEN cursorMails;
LOOP
FETCH cursorMails INTO v_customerId, v_value;
**str := PKT_StringFnc.SPLIT(v_value,' ');
FOR i IN 1..str.COUNT LOOP
dbms_output.put_line('XXX1' || str(i));
END LOOP;**
EXIT WHEN cursorMails%NOTFOUND;
END LOOP;
CLOSE cursorMails;
END;
END;
End Second procedure where I split mail from first procedure
CREATE OR REPLACE PACKAGE BODY PKT_StringFnc
IS
FUNCTION SPLIT (p_in_string VARCHAR2, p_delim VARCHAR2) RETURN t_array
IS
i number :=0;
pos number :=0;
lv_str varchar2(255) := p_in_string;
positionMonkey INTEGER;
domena VARCHAR2(50);
adres VARCHAR(255);
lv_str_new VARCHAR2(255);
aaa VARCHAR(20) := '#aaa.pl ';
bbb VARCHAR(30) := '#bbb.pl ';
result VARCHAR(1000);
strings t_array;
BEGIN
pos := instr(lv_str,p_delim,1,1);
WHILE ( pos != 0) LOOP
i := i + 1;
strings(i) := substr(lv_str,1,pos);
positionMonkey := INSTR(strings(i),'#');
domena := SUBSTR(strings(i), positionMonkey);
adres := RTRIM(strings(i),domena);
lv_str := substr(lv_str,pos+1,length(lv_str));
pos := instr(lv_str,p_delim,1,1);
IF pos = 0 THEN
strings(i+1) := lv_str;
ELSE
strings(i+1) := lv_str_new;
END IF;
IF domena = aaa OR domena = bbb THEN
lv_str_new := REPLACE(strings(i),domena,'#NewDomain.com');
END IF;
DBMS_OUTPUT.PUT_LINE('lv_str_newREPLACE:'|| lv_str_new);
END LOOP;
RETURN strings;
END SPLIT;
END;
/
When I return one big string I want update in table and where I can do it ?
Thanks for help
Maybe somebody can rewrite easiest procedure from two to one procedure
I wonder if something like
UPDATE pktTrue
SET Value = REGEXP_REPLACE(Value, '(#aaa.pl)|(#bbb.pl)|(#[a-zA-Z0-9._%-]+\.[a-zA-Z]{2,4})', '\1\2#NewDomain.pl', 1, 0, 'c')
WHERE method_id = 'E_MAIL';
will work for you?
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;