I'm trying to call an Oracle stored proc using SQL Developer. The proc outputs results using a sys_refcursor. I right click in the proc window which brings up the Run PL/SQL window. When I choose the proc I want it creates all the input params etc for me. Below is the code I'm using to try and loop through the sys_refcursor and output the results, but I'm getting an error on the 'v_rec v_Return%rowtype;' line :
ORA-06550: line 6 column 9:
PLS-00320: the declaration of the type of this expression is incomplete or malformed.
ORA-06550: line 6 column 9:
PL/SQL: Item ignored
vendor code 6550
I found the looping code on a couple of other websites and it seems to be the way to do it but it's not working for me no matter what I try. Another question - on the DBMS_OUTPUT.PUT_LINE('name = ' || v_rec.ADM) am I referencing the v_rec correctly i.e. is v_rec."column_name" the correct way??
I'm not that used to Oracle and have never used SQL plus. Any suggestions appreciated.
DECLARE
P_CAE_SEC_ID_N NUMBER;
P_PAGE_INDEX NUMBER;
P_PAGE_SIZE NUMBER;
v_Return sys_refcursor;
v_rec v_Return%rowtype;
BEGIN
P_CAE_SEC_ID_N := NULL;
P_PAGE_INDEX := 0;
P_PAGE_SIZE := 25;
CAE_FOF_SECURITY_PKG.GET_LIST_FOF_SECURITY(
P_CAE_SEC_ID_N => P_CAE_SEC_ID_N,
P_PAGE_INDEX => P_PAGE_INDEX,
P_PAGE_SIZE => P_PAGE_SIZE,
P_FOF_SEC_REFCUR => v_Return
);
-- Modify the code to output the variable
-- DBMS_OUTPUT.PUT_LINE('P_FOF_SEC_REFCUR = ');
loop
fetch v_Return into v_rec;
exit when v_Return%notfound;
DBMS_OUTPUT.PUT_LINE('name = ' || v_rec.ADM);
end loop;
END;
Your problem is here:
v_Return sys_refcursor;
v_rec v_Return%rowtype;
v_Return is a cursor variable and has no specific structure (list of columns), so v_Return%rowtype is not a valid record structure to declare v_rec. It is even possible for different calls to the procedure to return cursors with different structures.
You know what you are expecting the structure of the returned cursor to be (but Oracle doesn't) so you need to explicitly define the appropriate record structure e.g.
type t_row is record (empno number, ename varchar2(30));
v_rec t_row;
You need a strongly typed ref cursor to be able to define it as a %ROWTYPE.
Example here
#Tony Andrews thanks for this it gave me a better idea where I was going wrong. Still having problems though - here's a shortened version of my proc. It's a bit complex in that it's selecting all fields from a subquery and 2 other values:
open p_fof_sec_refcur for
SELECT *
FROM(
SELECT securities.*, rownum rnum, v_total_count
FROM
(
SELECT
CFS.CAE_SEC_ID,
CFS.FM_SEC_CODE,
...
FROM
CAEDBO.CAE_FOF_SECURITY CFS
INNER JOIN caedbo.CAE_DATA_SET_ELEMENT CDSE_STAT
ON (CDSE_STAT.DATA_SET_ELEMENT_ID = CFS.APPR_STATUS)
...
WHERE APPR_STATUS = NVL(p_appr_status, APPR_STATUS)
...
)securities
)
WHERE rnum between v_pgStart and v_pgEnd;
I explicitly defined the output structure as below to match the return fields from the proc but I'm still getting an error:
v_Return sys_refcursor;
type t_row is record (CAE_SEC_ID NUMBER,FM_SEC_CODE VARCHAR2(7),...rnum number, v_total_count number);
v_rec t_row;
The error I get is
ORA-06504: PL/SQL: Return types of Result Set variables or query do not match
ORA-06512: at line 45
I'm just wondering is the "rownum rnum, v_total_count" part tripping me up. I'm pretty sure I have all the other fields in the output structure correct as I copied them directly from the proc.
Related
I am going to access the values from oracle record using the dot notation and identifier as variable, i am getting error V_COL must be declared.
declare
cursor c1 is select * from temp_sample_records;
type t is table of temp_sample_records%rowtype;
l_tab t;
rec c1%rowtype;
term term_cell_data#ktkcm%rowtype;
term_reverify term_cell_data_reverify#ktkcm%rowtype;
type t_cols IS VARRAY(100) OF VARCHAR2(100);
cols t_cols;
V_COL varchar2(100);
begin
cols := t_cols('GSMNUMBER','CAF_SERIAL_NO','CUSTOMER_NAME');
open c1;
loop
fetch c1 bulk collect into l_tab limit 5000;
exit when l_tab.count=0;
for i in l_tab.first..l_tab.last loop
begin
select * into term from term_cell_data#ktkcm where gsmnumber=l_tab(i).msisdn and caf_serial_no=l_tab(i).caf_serial_no;
for c in 1..cols.last loop
V_COL :=cols(c);
dbms_output.put_line(term.V_COL);
end loop;
exception
when no_data_found then null;
end;
end loop;
end loop;
close c1;
end;
/
kindly help where i am making a mistake
Error ORA-06550: line 22, column 51:
PLS-00302: component 'V_COL' must be declared
ORA-06550: line 22, column 25:
PL/SQL: Statement ignored
where i am making a mistake
term.V_COL is not valid as, I am assuming that, you do not have a column named V_COL in the term_cell_data#ktkcm table and you are trying to dynamically access the columns. You cannot do this.
You could look at reflection using ANYDATA, however that does not work with record data types and would need Object data types defined in the SQL scope.
You could also look at the DBMS_SQL package but I think that, even if you worked to get the data using position indexes rather than column names, you would still need to know the data type of every column.
You would probably be best to just statically name each column in the %ROWTYPE variable (rather than trying to dynamically reference columns). So in your inner-most loop, you could use:
select *
into term
from term_cell_data#ktkcm
where gsmnumber=l_tab(i).msisdn
and caf_serial_no=l_tab(i).caf_serial_no;
dbms_output.put_line(term.GSMNUMBER);
dbms_output.put_line(term.CAF_SERIAL_NO);
dbms_output.put_line(term.CUSTOMER_NAME);
I made a package that compiles fine but when I try to test it it gives me "invalid data type".
I've tried two different ways, first one like this
select pkg_contabilidad.f_totalizar_Detalle(100) FROM DUAL;
It gives me the ORA-00902 'invalid data type'
Also I've tried this
DECLARE
TYPE r_registro IS RECORD
(rubro_contable CN_RUBROS_CONTABLES.COD_RUBRO%TYPE,
tipo VARCHAR2(1),
monto NUMBER(16));
resultao r_registro;
numero NUMBER :=100;
BEGIN
resultao := pkg_contabilidad.f_totalizar_detalle(numero);
END;
It gives me another error PLS-00382 'expression is of wrong type'
I don't know what am I doing wrong, cause my function receives just one parameter and is of type NUMBER, so I dont know where's my mistake. I'll leave the code of my package just in case
CREATE OR REPLACE PACKAGE pkg_contabilidad AS
TYPE r_registro IS RECORD
(rubro_contable CN_RUBROS_CONTABLES.COD_RUBRO%TYPE,
tipo VARCHAR2(1),
monto NUMBER(16));
TYPE t_detalle IS TABLE OF
r_registro INDEX BY BINARY_INTEGER;
FUNCTION f_totalizar_detalle(p_clave NUMBER)RETURN t_detalle;
END pkg_contabilidad;
/
CREATE OR REPLACE PACKAGE BODY pkg_contabilidad AS
B_detalle t_detalle;
i integer :=1;
FUNCTION f_totalizar_detalle(p_clave NUMBER) RETURN t_detalle IS
v_detalle t_detalle;
CURSOR c_facturado IS
SELECT c.cod_rubro, 'H', CASE WHEN SUM(d.gravada)=0 THEN SUM(d.iva) ELSE SUM(d.gravada) END
FROM fn_documentos_det d JOIN fn_conceptos c ON d.cod_concepto = c.cod_concepto
WHERE d.clave_doc=p_clave
GROUP BY c.cod_rubro;
CURSOR c_datos IS
SELECT SUM(d.total_doc), 'D',r.cod_rubro
FROM fn_documentos d JOIN fn_cajas_ctas r ON d.num_caja_cta = r.num_caja_cta
WHERE d.clave_doc = p_clave
GROUP BY r.cod_rubro;
BEGIN
open c_datos;
LOOP
FETCH c_datos INTO v_detalle(1);
END LOOP;
CLOSE c_datos;
FOR fila IN c_facturado LOOP
i := i + 1;
v_detalle(i) := fila;
END LOOP;
END;
END PKG_CONTABILIDAD;
The function returns a pkg_contabilidad.t_detalle, so the test needs to be:
declare
resultao pkg_contabilidad.t_detalle;
numero number := 100;
begin
resultao := pkg_contabilidad.f_totalizar_detalle(numero);
end;
It doesn't work in SQL because pkg_contabilidad.t_detalle is a PL/SQL type, not a SQL type (create or replace type). The database can perform some automatic conversions, but there are still limitations.
By the way, this loop will never complete because it lacks an exit condition:
open c_datos;
loop
fetch c_datos into v_detalle(1);
end loop;
close c_datos;
Your function returns a PL/SQL table type, with a table of a PL/SQL record type, which is defined in your package, which plain SQL doesn't know about and can't display - hence your invalid datatype error. If you need to call the function and access the data from SQL you can create schema-level object and collection types instead.
In your anonymous block you are a declaring a new record type. That looks the same to you because the structure is the same, but Oracle expects the exact type the function returns. That makes your test code shorter and simpler though. But you are also trying to return the whole collection into a single record.
DECLARE
l_detalle pkg_contabilidad.t_detalle;
l_registro pkg_contabilidad.r_registro;
l_idx pls_integer;
numero NUMBER :=100;
BEGIN
l_detalle := pkg_contabilidad.f_totalizar_detalle(numero);
l_idx := l_detalle.FIRST;
WHILE l_idx is not null LOOP
l_registro := l_detalle(l_idx);
-- do something with this record
dbms_output.put_line(l_registro.tipo);
l_idx := l_detalle.NEXT(l_idx);
END LOOP;
END;
db<>fiddle with dummy cursors.
Your function is a bit strange and probably isn't doing quite what you want; but also has two fatal problems: it isn't returning anything, and it has an infinite loop. I've fixed those for the fiddle but not anything else, as this seems to be an exercise.
This is my first (edited) stackoverflow question, so please bear with me.
In Oracle 11g, I have a need to describe/interrogate the underlying columns of a reference cursor returned from a procedure call on another database over a dblink, in which the actual SQL is not always "explicit", but sometimes dynamically generated.
For example:
declare
v_ref_cur sys_refcursor;
v_cur_num number;
v_col_count number;
v_col_table dbms_sql.desc_tab3;
begin
myProc#myDblink(v_ref_cur, 'myvalue');
v_cur_num := dbms_sql.to_cursor_number(v_ref_cur);
dbms_sql.describe_columns3(v_cur_num, v_col_count, v_col_table);
...
end
If myProc() on the other database has an "explicit" SQL statement like:
open cursor for select * from foo where bar = myParam;
The cursor conversion and description (still) work just fine - I can determine the column names, types, lengths, etc returned by the procedure.
BUT, if myProc() on the other database involves dynamic SQL, like:
v_sql := 'select * from foo where bar = ''' || myParam || '''';
open cursor for v_sql;
I get an ORA-01001 invalid cursor error when attempting to call dbms_sql.to_cursor_number().
Is there a way to convert/describe a reference cursor derived from dynamic SQL as called from a remote procedure? If so, how? If not, why not?
Thanks for any/all assistance!
Using DBMS_SQL across a database link raises many different errors, at least some of which are Oracle bugs. Those problems can be avoided by putting all of the logic in a function compiled on the remote server. Then call that function remotely.
--Create and test a database link
create database link myself connect to <schema> identified by "<password>"
using '<connect string or service name>';
select * from dual#myself;
--myProc
create procedure myProc(p_cursor in out sys_refcursor, p_value varchar2) is
begin
--open p_cursor for select * from dual where dummy = p_value;
open p_cursor for 'select * from dual where dummy = '''||p_value||'''';
end;
/
--Simple function that counts and displays the columns. Expected value is 1.
create or replace function count_columns return number is
v_ref_cur sys_refcursor;
v_cur_num number;
v_col_count number;
v_col_table dbms_sql.desc_tab3;
begin
--ORA-01001: invalid cursor
--myProc#myself(v_ref_cur, 'myvalue');
myProc(v_ref_cur, 'myvalue');
--ORA-03113: end-of-file on communication channel
--v_cur_num := dbms_sql.to_cursor_number#myself(v_ref_cur);
v_cur_num := dbms_sql.to_cursor_number(v_ref_cur);
--Compilation error: PLS-00306:
-- wrong number or types of arguments in call to 'DESCRIBE_COLUMNS3'
--dbms_sql.describe_columns3#myself(v_cur_num, v_col_count, v_col_table);
dbms_sql.describe_columns3(v_cur_num, v_col_count, v_col_table);
return v_col_count;
end;
/
begin
dbms_output.put_line('Number of columns: '||count_columns#myself());
end;
/
Number of columns: 1
I don't understand why service is complaining with
Fehler(36,11): PL/SQL: ORA-00904: "FOUND_VP": invalid identifier
Variable is declared in the first begin...
Is it not possible to use variable directly in queries ?
when trying store following procedure :
create or replace PROCEDURE fpwl_update_vp(
my_zn IN NUMBER, my_verwaltung IN VARCHAR2 , my_variante IN NUMBER, my_vp IN NUMBER
) IS
BEGIN
DECLARE
search_VP IFT_INFO_LAUF.VP%TYPE;
found_VP IFT_INFO_LAUF.VP%TYPE;
INFOversion number := 25;
BEGIN -- search SYFA_VP
SELECT SYFA_VP
INTO found_VP
FROM FPWL_VP_MAPPING
WHERE INFO_VP=search_VP ;
exception
when no_data_found then
dbms_output.put_line ('Kein SYFA VP : Importiere aus SYFA');
--found_VP:=:=cus_info25.pa_info_data.fn_insert_syfa_vp(my_vp,25);
WHEN OTHERS THEN
ROLLBACK;
RETURN;
END; -- SYFA VP
-- Update VP
UPDATE IFT_INFO_LAUF
SET vp = found_VP
WHERE id_kopf IN
(SELECT id_kopf
FROM ift_info_kopf
WHERE fahrtnummer= my_zn
AND verwaltung= my_verwaltung
AND variante = my_variante
)
;
--COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
END ;
Your problem is that found_VP is going out of scope.
Move the contents of the "DECLARE" block to just after the "IS":
create or replace PROCEDURE fpwl_update_vp(
my_zn IN NUMBER, my_verwaltung IN VARCHAR2 , my_variante IN NUMBER, my_vp IN NUMBER
) IS
search_VP IFT_INFO_LAUF.VP%TYPE;
found_VP IFT_INFO_LAUF.VP%TYPE;
INFOversion number := 25;
BEGIN
BEGIN -- search SYFA_VP
etc
Make sure that
FPWL_VP_MAPPING.SYFA_VP
is the same type with
IFT_INFO_LAUF.VP
and make sure that
SELECT SYFA_VP INTO found_VP FROM FPWL_VP_MAPPING WHERE INFO_VP=search_VP ;
does not return multiple rows.
But I doubt that is the case with the error that you have given.
Since the error message refers to line 36 and the reference to found_VP in your code sample is on line 18, you've omitted the part of the code that actually has the problem.
It looks like you have a scope problem; you're declaring found_VP in an inner block (one level of DECLARE/BEGIN/END) and referring to it outside that block, either in the parent block or another one at the same level. The issue isn't where you're selecting into found_VP, it's (I think) that you're referring to it again later on, beyond the code you've posted, and therefore outside the block the variable is declared in.
To demonstrate, I'll declare l_name in an inner block, as you seem to have done:
create or replace procedure p42 is
begin
declare
l_name all_tables.table_name%TYPE;
begin
select table_name
into l_name -- this reference is OK
from all_tables
where table_name = 'EMPLOYEES';
end;
select table_name
into l_name -- this reference errors
from all_tables
where table_name = 'JOBS';
end;
/
Warning: Procedure created with compilation errors.
show errors
Errors for PROCEDURE P42:
LINE/COL ERROR
-------- -----------------------------------------------------------------
12/2 PL/SQL: SQL Statement ignored
13/7 PLS-00201: identifier 'L_NAME' must be declared
14/2 PL/SQL: ORA-00904: : invalid identifier
Notice that the error is reported against line 13, which is in the outer block; it doesn't complain about it in the inner block because it is in-scope there.
So, you need to declare the variable at the appropriate level. As Colin 't Hart says that is probably right at the top, between the IS and the first BEGIN, as that is the procedure-level DECLARE section (it doesn't need an explicit DECLARE keyword).
Is it possible to have conditional compilation in Oracle, where the condition is the existence of a database object (specifically, a table or view or synonym)? I'd like to be able to do something like this:
sp_some_procedure is
$IF /*check if A exists.*/ then
/* read from and write to A as well as other A-related non-DML stuff...*/
$ELSE /*A doesn't exist yet, so avoid compiler errors*/
dbms_output.put_line('Reminder: ask DBA to create A!')
$ENDIF
end;
Yes it is. Here a sample where the first stored procedure wants to select from XALL_TABLES, but if this table doesn't exist, select from dual. Finally, because I haven't got an XALL_TABLES object, the first stored procedure selects from dual. The second one, does the same thing on the ALL_TABLES object. Because the ALL_TABLES exists, the second stored procedure selects from all_tables but not from DUAL.
This kind of construction is useful where the package have to be deployed on all your database and use tables that are not deployed everywhere ... (ok, perhaps there is a conceptual problem, but it happens).
--conditionals compilation instructions accept only static condition (just with constants)
--passing sql bind variable doesn't work
--To pass a value to a conditional compilation instruction, I bypasses the use of input parameters of the script
--these 4 next lines affect a value to the first and the second input parameter of the script
--If your originally script use input script parameter, use the next free parameter ...
column param_1 new_value 1 noprint
select nvl(max(1), 0) param_1 from all_views where owner = 'SYS' and view_name = 'XALL_TABLES';
column param_2 new_value 2 noprint
select nvl(max(1), 0) param_2 from all_views where owner = 'SYS' and view_name = 'ALL_TABLES';
CREATE or replace PACKAGE my_pkg AS
function test_xall_tables return varchar2;
function test_all_tables return varchar2;
END my_pkg;
/
CREATE or replace PACKAGE BODY my_pkg AS
function test_xall_tables return varchar2 is
vch varchar2(50);
begin
$IF (&1 = 0) $THEN
select 'VIEW XALL_TABLES D''ONT EXISTS' into vch from dual;
$ELSE
select max('VIEW XALL_TABLES EXISTS') into vch from XALL_TABLES;
$END
return vch;
end test_xall_tables;
function test_all_tables return varchar2 is
vch varchar2(50);
begin
$IF (&2 = 0) $THEN
select 'VIEW ALL_TABLES D''ONT EXISTS' into vch from dual;
$ELSE
select max('VIEW ALL_TABLES EXISTS') into vch from ALL_TABLES;
$END
return vch;
end test_all_tables;
END my_pkg;
/
the test :
select my_pkg.test_xall_tables from dual;
give
VIEW XALL_TABLES D'ONT EXISTS
select my_pkg.test_all_tables from dual;
give
VIEW ALL_TABLES EXISTS
I would use 'EXECUTE IMMEDIATE' and a EXCEPTION clause.
Use dynamic SQL to create package constants to track which objects exist, and then use those constants in conditional compilation.
--E.g., say there are two possible tables, but only one of them exists.
--create table table1(a number);
create table table2(a number);
--Create a package with boolean constants to track the objects.
--(Another way to do this is to use ALTER SESSION SET PLSQL_CCFLAGS)
declare
table1_exists_string varchar2(10) := 'true';
table2_exists_string varchar2(10) := 'true';
temp number;
begin
begin
execute immediate 'select max(1) from table1 where rownum <= 1' into temp;
exception when others then
table1_exists_string := 'false';
end;
begin
execute immediate 'select max(1) from table2 where rownum <= 1' into temp;
exception when others then
table2_exists_string := 'false';
end;
execute immediate '
create or replace package objects is
table1_exists constant boolean := '||table1_exists_string||';
table2_exists constant boolean := '||table2_exists_string||';
end;
';
end;
/
--Look at the results in the source:
select * from user_source where name = 'OBJECTS';
--Create the object that refers to the tables.
create or replace function compile_test return varchar2 is
v_test number;
begin
$if objects.table1_exists $then
select max(1) into v_test from table1;
return 'table1 exists';
$elsif objects.table2_exists $then
select max(1) into v_test from table2;
return 'table 2 exists';
$else
return 'neither table exists';
$end
end;
/
--Check the dependencies - only TABLE2 is dependent.
select * from user_dependencies where name = 'COMPILE_TEST';
--Returns 'table 2 exists'.
select compile_test from dual;
Mixing dynamic SQL, dynamic PL/SQL, and conditional compilation is usually a very evil idea. But it will allow you to put all of your ugly dynamic SQL in one installation package, and maintain real dependency tracking.
This may work well in a semi-dynamic environment; for example a program that is installed with different sets of objects but does not frequently change between them.
(Also, if the whole point of this is just to replace scary error messages with friendly warnings, in my opinion that is a very bad idea. If your system is going to fail, the failure should be obvious so it can be immediately fixed. Most people ignore anything that starts with "Reminder...".)
No - that is not possible... but if you create a stored procedure referencing a non-existent DB object and try to compile it the compilation will show errors... the stored procedure will be there but "invalid"... and the compilation errors are accessible for the DBA whenever he looks at it... so I would just go ahead and create all needed stored procedures, if any compilation errors arise ask the DBA (sometimes the object exists but the stored procedure need permissions to access it...)... after the reason for the error(s) is fixed you can just recompile the stored procedure (via ALTER PROCEDURE MySchema.MyProcName COMPILE;) and all is fine...
IF you don't want code to be there you can just DROP the strored procedure and/or replace is via CREATE OR REPLACE... with dbms_output.put_line('Reminder: ask DBA to create A!') in the body.
The only other alternative is as kevin points out EXECUTE IMMEDIATE with proper EXCEPTION handling...
What I would do is check the existence via all_objects, something like:
declare
l_check_sql varchar2(4000);
l_cnt number;
begin
l_check_sql := q'{
select count(1)
from all_objects
where object_name = 'MY_OBJ'
and owner = 'MY_OWNER'
}';
execute immediate l_check_sql into l_cnt;
if (l_cnt > 0) then
-- do something referring to MY_OBJ
else
-- don't refer to MY_OBJ
end if;
end;