ORA-00932 facing while compiling the oracle function - oracle

I have made below code.
CREATE OR REPLACE TYPE CAL IS OBJECT(
EMPLOYEE_NAME VARCHAR2(30),
R_DATE DATE,
COMMENTS VARCHAR2(50)
);
CREATE OR REPLACE TYPE T_REC is table of cal;
create or replace function CALENDAR(v_team_name varchar2)
return t_rec
IS
v_rec t_rec;
v_COMM VARCHAR2(50);
v_name VARCHAR2(30);
BEGIN
FOR i in (Select ID,EMPLOYEE_NAME from EMPLOYEE where TEAM_NAME=v_team_name)
LOOP
v_name:=i.EMPLOYEE_NAME;
FOR k in (select EMP_ID, START_DT,END_DT-START_DT+1 DAYS,NAME,COMMENTS from EMP_ROTA where EMP_ID=i.ID)
LOOP
v_COMM:=k.NAME||', '||k.COMMENTS;
select t_rec(v_name,k.START_DT+level-1,v_COMM)
into v_rec
from dual connect by level < k.DAYS;
END LOOP;
END LOOP;
Return v_rec;
END;
Facing below error in compiling the function.
Compilation failed,line 14 (04:33:54)
PL/SQL: ORA-00932: inconsistent datatypes: expected UDT got CHARCompilation failed,line 14 (04:33:54)
PL/SQL: SQL Statement ignored
While using below query in function
select v_name,k.START_DT + level -1,v_COMM
into v_REC
from dual connect by level < k.DAYS;
Facing below error
PL/SQL: ORA-00947: not enough valuesCompilation failed,line 14 (04:50:49)
Trying small snippet of code stil facing ORA-00932.
create or replace function ROTA_CALENDAR
return t_CAL
IS
v_CAL t_CAL;
BEGIN
select t_cal('v_name',SYSDATE,'NAME')
into v_CAL
from dual;
Return v_CAL;
END;
what am I doing wrong?

I hope below snippet will help. Couple of things to take in consideration.
1 Don't use "" while any nomenclature
2 You have to select from Object type in the SELECT query as mentioned below.
3 Use BULK COLLECT instead of INTO only...
CREATE OR REPLACE
FUNCTION CALENDAR(
V_TEAM_NAME VARCHAR2)
RETURN t_rec
IS
v_rec t_rec;
v_COMM VARCHAR2(50);
v_name VARCHAR2(30);
BEGIN
FOR i IN
(
SELECT
EMPNO,
'AVRAJIT' ENAME
FROM
EMP
WHERE
JOB=v_team_name
)
LOOP
v_name:=i.ENAME;
FOR k IN
(
SELECT
EMPNO,
SYSDATE,
SYSDATE+1 DAYS,
ENAME,
JOB
FROM
EMP_V1
WHERE
EMPNO=i.EMPNO
)
LOOP
v_COMM:=k.ENAME||', '||k.JOB;
SELECT
CAL(V_NAME,SYSDATE+1,V_COMM) BULK COLLECT
INTO
v_rec
FROM
dual
CONNECT BY level < 10;
END LOOP;
END LOOP;
RETURN v_rec;
END;
-------------------------------OUTPUT-------------------------------------------
SELECT OBJECT_NAME,STATUS FROM ALL_OBJECTS
WHERE OBJECT_NAME = 'CALENDAR';
OBJECT_NAME STATUS
CALENDAR VALID
-------------------------------OUTPUT-------------------------------------------

Related

Error when calling procedure from procedure

The following procedure compilation error occurred:
Procedure A receives the result from table B, inputs it to the GLOBAL TEMPORARY TABLE, and retrieves the final result after the operation.
Procedure B is a function that manipulates and retrieves the source data.
When I run the A procedure, I get the following compilation error:
/* GLOBAL TEMPORARY TABLE */
CREATE GLOBAL TEMPORARY TABLE TT_TB_TMP
(
TABLE_NAME VARCHAR2(200)
,COLUMN_NAME VARCHAR2(200)
)
ON COMMIT DELETE ROWS
NOPARALLEL;
/* PROCEDURE B(SP_TEST_H2)*/
create or replace PROCEDURE SP_TEST_H2
(
p_TBL_NAME IN VARCHAR
)
AS
v_cursor SYS_REFCURSOR;
BEGIN
OPEN v_cursor FOR
SELECT TABLE_NAME, COLUMN_NAME
FROM ALL_TAB_COLUMNS
WHERE TABLE_NAME = 'ALL_XML_SCHEMAS'; -- p_TBL_NAME
DBMS_SQL.RETURN_RESULT(v_cursor);
END SP_TEST_H2;
/* PROCEDURE A(SP_TEST_H1)*/
create or replace PROCEDURE SP_TEST_H1
(
p_TBL_NAME IN VARCHAR
)
AS
v_cursor SYS_REFCURSOR;
BEGIN
DECLARE
cv_ins SYS_REFCURSOR;
v_temp TT_TB_TMP%ROWTYPE;
BEGIN
cv_ins := SP_TEST_H2('XXX');
LOOP
FETCH cv_ins INTO v_temp;
EXIT WHEN cv_ins%NOTFOUND;
INSERT INTO TT_TB_TMP VALUES v_temp;
END LOOP;
CLOSE cv_ins;
/*
OPEN v_cursor FOR
SELECT * FROM TT_TB_TMP;
DBMS_SQL.RETURN_RESULT(v_cursor);
*/
END;
END SP_TEST_H1
PLS-00222: Function with name 'SP_TEST_H2' does not exist in scope
What did I do wrong?
If you're returning something, then use a function - they are designed for such a purpose.
That's what Oracle told you:
Function with name 'SP_TEST_H2' does not exist in scope
which is related to this line in your code:
cv_ins := SP_TEST_H2('XXX');
Function:
SQL> CREATE OR REPLACE FUNCTION sf_test_h2 (p_tbl_name IN VARCHAR)
2 RETURN SYS_REFCURSOR
3 AS
4 v_cursor SYS_REFCURSOR;
5 BEGIN
6 OPEN v_cursor FOR SELECT table_name, column_name
7 FROM all_tab_columns
8 WHERE table_name = p_tbl_name;
9
10 RETURN v_cursor;
11 END sf_test_h2;
12 /
Function created.
Procedure:
SQL> CREATE OR REPLACE PROCEDURE sp_test_h1 (p_tbl_name IN VARCHAR)
2 AS
3 cv_ins SYS_REFCURSOR;
4 v_temp tt_tb_tmp%ROWTYPE;
5 BEGIN
6 cv_ins := sf_test_h2 (p_tbl_name);
7
8 LOOP
9 FETCH cv_ins INTO v_temp;
10
11 EXIT WHEN cv_ins%NOTFOUND;
12
13 INSERT INTO tt_tb_tmp (table_name, column_name)
14 VALUES (v_temp.table_name, v_temp.column_name);
15 END LOOP;
16
17 CLOSE cv_ins;
18 END sp_test_h1;
19 /
Procedure created.
Testing:
SQL> EXEC sp_test_h1('DEPT');
PL/SQL procedure successfully completed.
SQL> SELECT * FROM tt_tb_tmp;
TABLE_NAME COLUMN_NAME
-------------------- --------------------
DEPT LOC
DEPT DNAME
DEPT DEPTNO
SQL>

How to pass string of comma-separated numbers to stored procedure in condition for numeric field?

I have a stored procedure like below where multiple employee IDs will be passed as comma-separated value (multiple IDs). It is throwing error as "ORA-01722: invalid number". I know it's because of passing varchar2 variable for the numeric ID column. But is there any way we can achieve this simply?
create or replace PROCEDURE Fetch_Emp_Name(Emp_id in varchar2)
IS
BEGIN
select Name from EMP where id in (emp_id);
END;
You can use dynamic sql.
create or replace PROCEDURE Fetch_Emp_Name(emp_id in varchar2) IS
v_result varchar2;
begin
execute immediate
'select Name from EMP where id in (' || 'emp_id' || ')'
into
v_result;
end;
Also you can use package dbms_sql for dynamic sql.
Update
Another approach. I think may be better.
create or replace PROCEDURE Fetch_Emp_Name(emp_id in varchar2) IS
v_result varchar2;
begin
select
Name
from
EMP
where
id in
(
select
to_number(regexp_substr(emp_id, '[^,]+', 1, level))
from
dual
connect by regexp_substr(emp_id, '[^,]+', 1, level) is not null
);
exception
when no_data_found then
-- error1;
when too_many_rows then
-- error2;
end;
Sorry for before, I did not get the question in the right way. If you get a lot of IDs as different parameters, you could retrieve the list of names as an string split by comma as well. I put this code where I handled by regexp_substr the name of different emp_ids you might enter in the input parameter.
Example ( I am assuming that the IDs are split by comma )
create or replace PROCEDURE Fetch_Emp_Name(p_empid in varchar2) IS
v_result varchar2(4000);
v_append emp.name%type;
v_emp emp.emp_id%type;
counter pls_integer;
i pls_integer;
begin
-- loop over the ids
counter := REGEXP_COUNT(p_empid ,'[,]') ;
--dbms_output.put_line(p_empid);
if counter > 0
then
i := 0;
for r in ( SELECT to_number(regexp_substr(p_empid,'[^,]+',1,level)) as mycol FROM dual CONNECT BY LEVEL <= REGEXP_COUNT(p_empid ,'[,]')+1 )
loop
--dbms_output.put_line(r.mycol);
v_emp := r.mycol ;
select name into v_append from emp where emp_id = v_emp;
if i < 1
then
v_result := v_append ;
else
v_result := v_result ||','|| v_append ;
end if;
i := i + 1;
end loop;
else
v_emp := to_number(p_empid);
select name into v_result from emp where emp_id = v_emp;
end if;
dbms_output.put_line(v_result);
exception
when no_data_found
then
raise_application_error(-20001,'Not Employee found for '||v_emp||' ');
when too_many_rows
then
raise_application_error(-20002,'Too many employees for id '||v_emp||' ');
end;
Test
SQL> create table emp ( emp_id number, name varchar2(2) ) ;
Table created.
SQL> insert into emp values ( 1 , 'AA' );
1 row created.
SQL> insert into emp values ( 2 , 'BB' ) ;
1 row created.
SQL> commit;
SQL> insert into emp values ( 3 , 'CC' ) ;
1 row created.
SQL> select * from emp ;
EMP_ID NA
---------- --
1 AA
2 BB
3 CC
SQL> exec Fetch_Emp_Name('1') ;
AA
PL/SQL procedure successfully completed.
SQL> exec Fetch_Emp_Name('1,2,3') ;
AA,BB,CC
PL/SQL procedure successfully completed.
SQL>

PLS-00306: wrong number or types of arguments in call to 'ADD_MONTHS'

I have created the below procedure to only retain the last two months data only and delete the rest one against a table in oracle, below is the procedure but i am getting exception, please advise how to overcome from this
create or replace package TEST_TABLE AS
PROCEDURE TEST_TABLE;
END TEST_TABLE;
create or replace PACKAGE BODY TEST_TABLE AS
PROCEDURE TEST_TABLE IS
BEGIN
FOR cc IN
(
SELECT partition_name, high_value
FROM user_tab_partitions
WHERE table_name = 'TEST_TABLE'
)
LOOP
BEGIN
IF sysdate >= ADD_MONTHS(cc.high_value,2) THEN
EXECUTE IMMEDIATE
'ALTER TABLE TEST_TABLE DROP PARTITION ' || cc.partition_name;
Dbms_Output.Put_Line('Dropping partition is completed.');
END IF;
END;
END LOOP;
EXCEPTION WHEN Others THEN Dbms_Output.Put_Line( SQLERRM );
END TEST_TABLE;
END TEST_TABLE;
The error that I am getting is:
Error(12,6): PL/SQL: Statement ignored
Error(12,20): PLS-00306: wrong number or types of arguments in call to 'ADD_MONTHS'
Firstly, It's insane to call table name, package name and procedure name all by TEST_TABLE as being done by you, as if there's no other name available. I've named them appropriately.
HIGH_VALUE cannot be directly used in DATE related functions as it's of LONG TYPE. There's a simple method to convert it to date using dynamic SQL(EXECUTE IMMEDIATE)
CREATE OR replace PACKAGE BODY PKG_test_table AS
PROCEDURE pr_test_table
IS
v_high_value DATE;
BEGIN
FOR cc IN (
SELECT partition_name,
high_value
FROM user_tab_partitions
WHERE table_name = 'TEST_TABLE'
) LOOP
BEGIN
EXECUTE IMMEDIATE 'BEGIN :v_high_val := '|| cc.high_value || '; END;'
USING OUT v_high_value;
IF
SYSDATE >= add_months(v_high_value,2)
THEN
EXECUTE IMMEDIATE 'ALTER TABLE TEST_TABLE DROP PARTITION '
|| cc.partition_name;
dbms_output.put_line('Dropping partition is completed.');
END IF;
END;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(sqlerrm);
END pr_TEST_TABLE;
END PKG_test_table;
/
Calling the procedure
BEGIN
PKG_test_table.pr_test_table;
END;
/
Your procedure does not accept any parameter. You can't pass any arguments to it.
The HIGH_VALUE column from USER_TAB_PARTITIONS is a long data type, I'm not going copy code from another web site, but if you google "oracle convert high value to date" you should get some ideas on how to create a function that you can use to convert the 'long' to a date.
My reputation is too low to post this as a comment, so I added it as an answer, it should help though it is not a good answer :(
As the error says it all ADD_MONTHS takes a DATE and you are passing in as LONG.
Try something like this and it should be ok.
Example:
DECLARE
DT LONG(1000) := 'TO_DATE('||''''||'2018-08-01 00:00:00'||''''||',' ||''''|| 'SYYYY-MM-DD HH24:MI:SS'||''''||','||''''||'NLS_CALENDAR=GREGORIAN'||''''||')';
BEGIN
DBMS_OUTPUT.PUT_LINE(DT);
EXECUTE IMMEDIATE
'BEGIN
DBMS_OUTPUT.PUT_LINE(TO_CHAR(ADD_MONTHS('||DT||',2),'||''''||'YYYY-MM-DD HH24:MI:SS'||''''||
')); END;';
END;
Output:
TO_DATE('2018-08-01 00:00:00','SYYYY-MM-DD HH24:MI:SS','NLS_CALENDAR=GREGORIAN')
2018-10-01 00:00:00
Oracle does not allow functions over long such as cast, substr, add_months over long type however … read below.
Long type
describe user_tab_partitions;
...
SUBPARTITION_COUNT NUMBER
HIGH_VALUE LONG
HIGH_VALUE_LENGTH NUMBER
...
Function to convert long to varchar2
FUNCTION long_to_varchar2 ( p_table_owner IN VARCHAR2,p_table_name IN VARCHAR2, p_partition_name IN VARCHAR2) RETURN VARCHAR2
is
l_tmp long;
BEGIN
select high_value
into l_tmp
from all_tab_partitions
where table_owner = p_table_owner
and table_name = p_table_name
and partition_name = p_partition_name ;
RETURN l_tmp;
END long_to_varchar2;
3.Use your new function
select tpar."OWNER",tpar."TABLE_NAME",tpar."PART_MIN",tpar."PART_MIN_HV",tpar."PART_MAX",tpar."PART_MAX_HV",tpar."NR_PART"
,pkey.column_name as partitioned_by
,ptab.partitioning_type as partition_type
,ptab.status
from
(select p1.table_owner as owner
,p1.table_name
,pmin.partition_name as part_min
,to_date(substr(long_to_varchar2(p1.table_owner,p1.table_name,pmin.partition_name),11,10),'yyyy-mm-dd') as part_min_hv
,pmax.partition_name as part_max
,to_date(substr(long_to_varchar2(p1.table_owner,p1.table_name,pmax.partition_name),11,10),'yyyy-mm-dd') as part_max_hv
,p1.nr_part+1 as nr_part
from (select min(part.partition_position) as minp
,max(part.partition_position) as maxp
,count(*) as nr_part
,part.table_name
,part.table_owner
from all_tab_partitions part,
dba_tables tbls
where part.table_name=tbls.table_name
and part.table_owner=tbls.owner
and part.PARTITION_NAME <> 'P_CURRENT'
group by part.table_name, part.table_owner) p1
,all_tab_partitions pmin
,all_tab_partitions pmax
where p1.table_name = pmin.table_name
and p1.table_owner = pmin.table_owner
and p1.minp=pmin.partition_position
and p1.table_name = pmax.table_name
and p1.table_owner = pmax.table_owner
and p1.maxp = pmax.partition_position) tpar
,ALL_PART_KEY_COLUMNS pkey
,ALL_PART_TABLES ptab
where tpar.owner=pkey.owner
and tpar.table_name=pkey.name
and tpar.owner=ptab.owner
and tpar.table_name=ptab.table_name
and pkey.object_type='TABLE';
The only issue is that you will be doing an implicit varchar2 to date conversion and I see no way of doing it otherwise.

How to fetch a ref_cursor into Oracle's WITH CLAUSE

Is it possible to use a ref_cursor with ORACLE WITH CLAUSE. For example, I have the following scenario. First I have a procedure which returns a ref_cursor
PROCEDURE p_return_cursor(p_id IN NUMBER, io_cursor OUT t_cursor)
AS
BEGIN
OPEN io_cursor FOR
SELECT col1, col2
FROM Table1 t
WHERE t.id = p_id;
END;
Second, I have another procedure on which I make a call to p_return_cursor:
PROCEDURE p_test(p_cid IN NUMBER)
AS
l_cursor t_cursor;
l_rec Table1%ROWTYPE;
BEGIN
p_return_cursor(p_id => p_cid, io_cursor => l_cursor);
-- CODE GOES HERE
...
Now, my question is, can I make a temp table using the Oracle's WITH CLAUSE using the cursor; something like:
...
WITH data AS (
LOOP
FETCH l_cursor INTO l_rec;
EXIT WHEN l_cursor%NOTFOUND;
SELECT l_rec.col1, l_rec.col2 FROM DUAL;
END LOOP;
CLOSE l_cursor;
)
You cannot do it directly. You can, however, BULK COLLECT your cursor into a PL/SQL table variable and use that in a WITH clause.
Be careful of memory usage if the cursor contains many rows.
Full example:
CREATE TABLE table1 ( col1 NUMBER, col2 NUMBER );
INSERT INTO table1 ( col1, col2 ) SELECT rownum, 100+rownum FROM DUAL CONNECT BY ROWNUM <= 15;
COMMIT;
CREATE OR REPLACE PACKAGE se_example AS
TYPE t_cursor IS REF CURSOR
RETURN table1%ROWTYPE;
TYPE l_rec_tab IS TABLE OF table1%ROWTYPE;
PROCEDURE p_test (p_cid IN NUMBER);
END se_example;
CREATE OR REPLACE PACKAGE BODY se_example AS
-- private
PROCEDURE p_return_cursor (p_id IN NUMBER, io_cursor OUT t_cursor) AS
BEGIN
OPEN io_cursor FOR
SELECT col1,
col2
FROM table1 t;
--WHERE t.id = p_id; -- I didn't put "id" column in my sample table, sorry...
END p_return_cursor;
PROCEDURE p_test (p_cid IN NUMBER) IS
l_cursor t_cursor;
l_tab l_rec_tab;
l_dummy NUMBER;
BEGIN
p_return_cursor (p_id => p_cid, io_cursor => l_cursor);
FETCH l_cursor BULK COLLECT INTO l_tab;
-- *** instead of this
-- WITH data AS (
-- LOOP
-- FETCH l_cursor INTO l_rec;
-- EXIT WHEN l_cursor%NOTFOUND;
-- SELECT l_rec.col1, l_rec.col2 FROM DUAL;
-- END LOOP;
-- CLOSE l_cursor;
-- )
-- '
--
-- *** do this
WITH data AS
( SELECT col1, col2 FROM TABLE(l_tab) )
SELECT sum(col1 * col2) INTO l_dummy
FROM data;
dbms_output.put_line('result is ' || l_dummy);
END p_test;
END se_example;
begin
se_example.p_test(100);
end;

passing in table name as plsql parameter

I want to write a function to return the row count of a table whose name is passed in as a variable. Here's my code:
create or replace function get_table_count (table_name IN varchar2)
return number
is
tbl_nm varchar(100) := table_name;
table_count number;
begin
select count(*)
into table_count
from tbl_nm;
dbms_output.put_line(table_count);
return table_count;
end;
I get this error:
FUNCTION GET_TABLE_COUNT compiled
Errors: check compiler log
Error(7,5): PL/SQL: SQL Statement ignored
Error(9,8): PL/SQL: ORA-00942: table or view does not exist
I understand that tbl_nm is being interpreted as a value and not a reference and I'm not sure how to escape that.
You can use dynamic SQL:
create or replace function get_table_count (table_name IN varchar2)
return number
is
table_count number;
begin
execute immediate 'select count(*) from ' || table_name into table_count;
dbms_output.put_line(table_count);
return table_count;
end;
There is also an indirect way to get number of rows (using system views):
create or replace function get_table_count (table_name IN varchar2)
return number
is
table_count number;
begin
select num_rows
into table_count
from user_tables
where table_name = table_name;
return table_count;
end;
The second way works only if you had gathered statistics on table before invoking this function.

Resources