PLS-00382: expression is of wrong type by executing function and try to put the returntype in a variable - oracle

When I execute the function I get that the expression is of the wrong type but I don't know why. The return type that I use in the function is the same as where I try to put it in after the function is executed.
Below you find the record and table type.
TYPE department_id_table_type IS TABLE of DEPARTMENTS.DEPARTMENT_ID%TYPE;
TYPE managers_rec_type IS RECORD (
employee_id employees.employee_id%TYPE,
first_name employees.first_name%TYPE,
last_name employees.last_name%TYPE,
department_id_table_type DEPARTMENTS.DEPARTMENT_ID%TYPE);
TYPE managers_table_type IS TABLE OF managers_rec_type INDEX BY BINARY_INTEGER;
Below you find the function
FUNCTION managers_multiple_departments RETURN managers_table_type
IS
cursor department_curs is SELECT DEPARTMENT_NAME,MANAGER_ID,DEPARTMENT_ID FROM DEPARTMENTS WHERE MANAGER_ID IN (SELECT MANAGER_ID FROM DEPARTMENTS dep GROUP BY (MANAGER_ID) HAVING COUNT(MANAGER_ID) >1);
department_name departments.department_name%TYPE;
department_id departments.department_id%TYPE;
managerid departments.manager_id%TYPE;
employeeid employees.employee_id%TYPE;
firstname employees.first_name%TYPE;
lastname employees.last_name%TYPE;
count NUMBER;
rec managers_rec_type;
managers_rec managers_rec_type;
teller NUMBER := 1;
managers_table managers_table_type;
BEGIN
OPEN department_curs;
LOOP
FETCH department_curs INTO department_name, managerid,department_id;
EXIT WHEN department_curs%NOTFOUND;
Select EMPLOYEE_ID,FIRST_NAME,LAST_NAME,department_id into managers_rec from EMPLOYEES where MANAGER_ID = managerid;
managers_table(managers_rec.employee_id) := managers_rec;
FOR i IN managers_table.FIRST .. managers_table.LAST LOOP
IF managers_table.EXISTS(i) THEN
DBMS_OUTPUT.PUT_LINE(managers_table(i).first_name);
END IF;
END LOOP;
IF teller = 1 THEN
DBMS_OUTPUT.PUT_LINE(managers_rec.first_name ||' '|| managers_rec.last_name || ' Lijst van departments:');
teller := 2;
END IF;
DBMS_OUTPUT.PUT_LINE(department_id ||' '|| department_name);
END LOOP;
FOR i IN managers_table.FIRST .. managers_table.LAST LOOP
IF managers_table.EXISTS(i) THEN
DBMS_OUTPUT.PUT_LINE(managers_table(i).first_name);
DBMS_OUTPUT.PUT_LINE('test');
END IF;
END LOOP;
return managers_table;
END managers_multiple_departments;`enter code here`
Below is where I execute the function but this is where it is giving me the error on: managers := hr_package.managers_multiple_departments;
DECLARE
TYPE managers_rec_type IS RECORD (
employee_id employees.employee_id%TYPE,
first_name employees.first_name%TYPE,
last_name employees.last_name%TYPE,
department_id_table_type DEPARTMENTS.DEPARTMENT_ID%TYPE);
TYPE managers_table_type IS TABLE OF managers_rec_type INDEX BY BINARY_INTEGER;
man_rec managers_rec_type;
managers managers_table_type;
twee NUMBER;
BEGIN
managers := hr_package.managers_multiple_departments;
END;

You are declaring the record type, collection/table type and function all within the same package.
When you call the function you have to use the same type, from that package.
The return type that I use in the function is the same as where I try to put it in after the function is executed.
But it isn't. It has the same structure - fields and datatypes - but is not the same as far as Oracle is concerned. Oracle needs to know that exactly the same type is being used, partly so that it can keep track of dependencies between objects.
Your anonymous block needs to refer to the package types, rather than declaring its own - similar but conflicting - type(s):
DECLARE
managers hr_package.managers_table_type;
BEGIN
managers := hr_package.managers_multiple_departments;
END;
As a bonus it involves much less typing, and means you don't have to manage duplicate types.
It does also mean, though, that the type declarations have to be in the package specification - which is the case for anything you want to be publicly visible, of course.

Related

PLS-00357: Table,View Or Sequence reference 'JANUARY_2020' not allowed in this context

I am using this code to see if it will work for a procedure. I want to be able to make a procedure in which i can decide what data to extract by typing the time ('jan-2020') in which it is recorded and also to decide in which table i want to place the data in (january_2020). i get the error that the table is not able to be used in this context. What do i have to change in the code to be in the right context?
Is it because i am using dynamic sql in a loop that requires the loop to be executed to put the data in the table? or is it because i am using %rowtype as the attribute for the table ALL_DATA to create its own columns? If it is any of these what should i do to change it?
DECLARE
time_v varchar2(9);
table_v varchar2(200);
sql_code varchar2(300);
TYPE Copied_Table IS TABLE OF Gastos%ROWTYPE;
All_Data Copied_Table;
BEGIN
time_v := 'jan-2020';
SELECT *
BULK COLLECT INTO All_Data FROM Gastos
Where TO_CHAR(DATE_, 'MON-YYYY') = UPPER(time_v);
FOR I in All_Data.First .. All_Data.Last LOOP
sql_code := 'INSERT INTO :table_v ( DATE_, DESCRIPTION, ORIGINAL_DESCRIPTION, AMOUNT,
TRANSACTION_TYPE, CATEGORY, ACCOUNT_NAME)
Values ( ALL_Data(i).date_, ALL_Data(i).description, ALL_Data(i).original_description,
ALL_Data(i).amount, ALL_Data(i).transaction_type, ALL_Data(i).category, ALL_Data(i).account_name)';
table_v := january_2020;
execute immediate sql_code
using table_v;
END LOOP;
END upload_monthly_expenses;
Pass table name as input parameter and replace bind variable with normal variable for the table name and concatenate it to the DML statement.Modify your code as below,
CREATE OR REPLACE PROCEDURE upload_monthly_expenses(table_v IN VARCHAR2,time_v IN VARCHAR2) AS
DECLARE
sql_code varchar2(300);
TYPE Copied_Table IS TABLE OF Gastos%ROWTYPE;
All_Data Copied_Table;
BEGIN
SELECT *
BULK COLLECT INTO All_Data FROM Gastos
Where TO_CHAR(DATE_, 'MON-YYYY') = UPPER(time_v);
FOR I in All_Data.First .. All_Data.Last LOOP
sql_code := 'INSERT INTO '||table_v||' ( DATE_, DESCRIPTION, ORIGINAL_DESCRIPTION, AMOUNT,
TRANSACTION_TYPE, CATEGORY, ACCOUNT_NAME)
Values ( ALL_Data(i).date_, ALL_Data(i).description, ALL_Data(i).original_description,
ALL_Data(i).amount, ALL_Data(i).transaction_type, ALL_Data(i).category, ALL_Data(i).account_name)';
execute immediate sql_code;
END LOOP;
END;
From a PL/SQL block procedure can be executed as below,
BEGIN
upload_monthly_expenses('jan-2020','january_2020');
END;

Oracle table type to nested table cast error

I declared table type and set a value in it with using loop. I am having an error while I was casting this t_table
DECLARE
TYPE t_row IS RECORD
(
id NUMBER,
description VARCHAR2(50)
);
TYPE t_table IS TABLE OF t_row;
l_tab t_table := t_table();
BEGIN
FOR i IN 1 .. 10 LOOP
l_tab.extend();
l_tab(l_tab.last).id := i;
l_tab(l_tab.last).description := 'Description for ' || i;
END LOOP;
SELECT * from TABLE(CAST(l_tab AS t_table));
END
Best regards
Why do you want to do a select onto the the type? You would use the the TABLE() and the CAST rather if you have a collection in a column stored in a table.
You could just loop through the table in your code. Example:
for i in l_tab.first .. l_tab.last
loop
dbms_output.put_line(l_tab(i).id||' '||l_tab(i).description);
end loop;
Since l_tab is of type t_table, there's no need for the cast. But that's not your problem.
Your problem is that you're trying to reference a PL/SQL type in SQL, which you simply can't do. You can either remove the select as #hol suggested or make the type a database object (which will allow SQL to access it):
CREATE OR REPLACE TYPE t_row AS OBJECT
(
id NUMBER,
description VARCHAR2 (50)
);
CREATE OR REPLACE TYPE t_table AS TABLE OF t_row;
DECLARE
l_tab t_table := t_table ();
BEGIN
FOR i IN 1 .. 10 LOOP
l_tab.EXTEND ();
l_tab (l_tab.LAST) := t_row (i, 'Description for ' || i);
END LOOP;
FOR r IN (SELECT * FROM TABLE (l_tab)) LOOP
DBMS_OUTPUT.put_line (r.id);
END LOOP;
END;
There is a second problem with the initial code, in that you are running a select without telling the code what to do with it. Unlike some other procedural SQL extensions, PL/SQL does not allow you to implicitly return a handle to a resultset (prior to 12c). You must either handle it directly or explicitly return a ref_cursor that points to it. The code above has been update to primitively handle the result of the query.

Oracle PL/SQL return array of rows

Dear Oracle Developers,
I have searched and googled to find a solution for my problem but nothing helped me.
Situation :
TABLE : CUSTOMER(....);
My problem is : I want to create a stored procedure say get_customers to return an array of customer rows. I have no idea how to get this working.
I tried to create a type customer_rec and using a cursor to retrieve no more than maxRows
create or replace procedure get_customers(maxRows IN NUMBER, ??? OUT ????)
How to define the OUT parameter ?
How to retrieve the rows in the array using a cursor ?
Thanks a lot
I would like to answer this in a way that discourages passing around arrays, when passing around cursors is a more sound approach. It doesn't exactly answer the question as posed, but it is an answer. Thinking cursors instead of thinking arrays is more efficient and thus more scalable. Also, it can be much easier code to maintain.
create table customer (
customer_id number(2) primary key,
customer_name varchar2(200) );
insert into customer values (1, 'Customer One');
insert into customer values (2, 'Customer Two');
insert into customer values (3, 'Customer Three');
insert into customer values (4, 'Customer Four');
insert into customer values (5, 'Customer Five');
insert into customer values (6, 'Customer Six');
insert into customer values (7, 'Customer Seven');
CREATE OR REPLACE PACKAGE cursor_not_array IS
FUNCTION get_customers(p_max_records INTEGER, p_id_start INTEGER, p_id_end INTEGER DEFAULT NULL) RETURN SYS_REFCURSOR;
END cursor_not_array;
CREATE OR REPLACE PACKAGE BODY cursor_not_array IS
c_max_customer_id CONSTANT NUMBER(2) := 99;
FUNCTION get_customers(p_max_records INTEGER, p_id_start INTEGER, p_id_end INTEGER DEFAULT NULL) RETURN SYS_REFCURSOR IS
v_result SYS_REFCURSOR;
BEGIN
OPEN v_result FOR
SELECT customer_id,
customer_name
FROM customer
WHERE customer_id BETWEEN p_id_start AND nvl(p_id_end, c_max_customer_id)
ORDER BY customer_id;
RETURN v_result;
END;
END cursor_not_array;
You could create a package like this:
create or replace package customers is
type customers_array is table of customer%rowtype index by binary_integer;
procedure get_customers(maxRows IN NUMBER, customer_array OUT customers_array);
end customers;
create or replace package body customers is
procedure get_customers(maxRows IN NUMBER, customer_array OUT customers_array) is
cursor c_customers is
select *
from customers;
where rownum <= maxRows;
i number := 1;
begin
for r in c_customers loop
customer_array(i) := r;
i := i + 1;
end loop;
end get_customers;
end customers;
And then call the get_customers procedure from wherever you want to...
first time create VARRAY type.
'create TYPE CUSTARRAY is VARRAY(100) OF VARCHAR2(30);'
varray limit is depends on you.
then create procedure that return CUSTARRAY type parameter.
`create
procedure prc_get_arr(p_maxrow in number, p_customers out custarray)
as
my_cust custarray := custarray();
cursor c_cust is select name from CUSTOMER where rownum<p_maxrow;
v_customer varchar2(64);
begin
open c_cust;
loop
fetch c_cust into v_customer;
exit when c_cust%notfound;
my_cust.extend;
my_cust(my_cust.count) := v_customer;
end loop;
close c_cust;
p_customers:=my_cust;
end;`
Now call this procedure
DECLARE
P_MAXROW NUMBER;
p_customers custarray;
v_cnt number:=0;
begin
P_MAXROW := 22;
prc_get_arr( p_maxrow => p_maxrow, p_customers => p_customers );
v_cnt:=p_customers.count;
for i in p_customers.first..p_customers.last loop
dbms_output.put_line('P_CUSTOMERS = ' || p_customers(i));
end loop;
end;
You can, using the SYS_REFCURSOR on your function output.
First you have to define a collection :
TYPE customers_array IS TABLE OF customer%ROWTYPE
INDEX BY BINARY_INTEGER;
Then your procedure simply have to fetch the result into that collection.
You're procedure could be written as follow:
CREATE OR REPLACE PACKAGE your_pkg
AS
TYPE customers_array IS TABLE OF customer%ROWTYPE
INDEX BY BINARY_INTEGER;
PROCEDURE get_customers(pn_max_rows IN NUMBER,
pt_coustomers OUT customers_array);
END your_pkg;
CREATE OR REPLACE PACKAGE BODY your_pkg
AS
PROCEDURE get_customers(pn_max_rows IN NUMBER,
pt_coustomers OUT customers_array)
IS
BEGIN
SELECT *
BULK COLLECT INTO pt_coustomers
FROM customers
WHERE rownum <= pn_max_rows;
END get_customers;
END your_pkg;

errors when trying to set column of record with INTO clause

Hi when I try to compile the procedure below I always receive errors It works when I replace
TYPE NameCombine IS RECORD(FIRST_NAME employees.FIRST_NAME%TYPE, LAST_NAME employees.LAST_NAME%TYPE, Combined_NAME employees.LAST_NAME%TYPE);
with first_name employees.first_name%TYPE; and use where rownum=1 in the select statement but the record way does not seem to work for me what am I doing wrong here?
CREATE OR REPLACE PROCEDURE Combined_name
IS
TYPE NameCombine IS RECORD(FIRST_NAME employees.FIRST_NAME%TYPE, LAST_NAME
employees.LAST_NAME%TYPE, Combined_NAME employees.LAST_NAME%TYPE);
BEGIN
SELECT
emp.FIRST_NAME,
emp.LAST_NAME,
emp.FIRST_NAME +' '+ emp.LAST_NAME
INTO
NameCombine.FIRST_NAME,
NameCombine.LAST_NAME,
NameCombine.Combined_NAME
FROM
EMPLOYEES emp;
dbms_output.put_line( NameCombine.FIRST_NAME );
END;
Website with similar syntax example as above:
http://plsql-tutorial.com/plsql-records.htm
Errors
Error(7,1): PL/SQL: SQL Statement ignored
Error(12,2): PLS-00330: invalid use of type name or subtype name
Error(14,34): PL/SQL: ORA-00904: : invalid identifier
Error(17,1): PL/SQL: Statement ignored
Error(17,23): PLS-00330: invalid use of type name or subtype name
Thank you in advance for your feedback.
This compilation error usually occurs when a datatype or subtype specifier was mistakenly used in place of a constant, variable, or expression.
When you Declare something as Type in PL/SQl, what you do is, create a custom data type. you cannot be assigning values to a data type. You need to first create a variable of that custom type and then you can assign values to it.
TYPE NameCombine IS RECORD(FIRST_NAME employees.FIRST_NAME%TYPE, LAST_NAME
employees.LAST_NAME%TYPE, Combined_NAME employees.LAST_NAME%TYPE);
NameCombineVar NameCombine;
...
INTO
NameCombineVar.FIRST_NAME,
NameCombineVar.LAST_NAME,
NameCombineVar.Combined_NAME
1) You need to ensure the combined name will have maximum or less length of last_name ,
or else it raise numeric value error
2) Oracle doesn't have +' '+ format to append , instead use emp.first_name||' 'emp.last_name
3)
Your code will work only for a single row data, or use rownum = 1 in the where condition in the query.
4) declare an object for the record type you've created.
CREATE OR REPLACE
PROCEDURE Combined_name
IS
TYPE NAMECOMBINE
IS RECORD(
FIRST_NAME employees.FIRST_NAME%TYPE,
LAST_NAME employees.LAST_NAME%TYPE,
COMBINED_NAME EMPLOYEES.LAST_NAME%TYPE
);
obj NAMECOMBINE;
BEGIN
SELECT emp.FIRST_NAME,emp.LAST_NAME,emp.FIRST_NAME||' '||emp.LAST_NAME
INTO OBJ.FIRST_NAME,OBJ.LAST_NAME,OBJ.Combined_NAME
FROM EMPLOYEES EMP
WHERE ROWNUM = 1;
DBMS_OUTPUT.PUT_LINE( OBJ.FIRST_NAME );
END;
/
if you need to display records for multiple rows, you have bulk collect into clause as follows:
CREATE OR REPLACE
PROCEDURE Combined_name
is
TYPE NameCombine IS
RECORD
(
FIRST_NAME VARCHAR2(20),
LAST_NAME VARCHAR2(20),
Combined_NAME VARCHAR2(20));
obj NameCombine;
CURSOR test_cur
IS
SELECT emp.FIRST_NAME,emp.LAST_NAME,emp.FIRST_NAME||' '||emp.LAST_NAME
FROM EMPLOYEES EMP;
type test_rec_arr IS TABLE OF NameCombine INDEX BY pls_integer;
test_rec test_rec_arr;
BEGIN
OPEN test_cur;
LOOP
FETCH test_cur bulk collect INTO test_rec limit 10;
FOR i IN test_rec.first..test_rec.last
LOOP
dbms_output.put_line(test_rec(i).FIRST_NAME);
END LOOP;
EXIT
WHEN test_rec.count = 0;
END LOOP;
CLOSE test_cur;
END;

How can this piece of PLSQL be made to compile?

I need to return the names of employees in string format for all those employees whose manager ID depends on the passed parameter. When I compile the function I get an error. Here is the function code:
create or replace function Employee(v_manid IN employees.manager_id%type)
return varchar2
AS
cursor cur_emp is select last_name from employees where manager_id = v_manid;
v_names varchar2(10);
begin
for emp_rec in cur_emp
loop
v_name = v_name || emp_rec.last_name ||', ';
end loop;
return v_name
end;
/
The error is:
Error(8,8): PLS-00103: Encountered the symbol "=" when expecting one
of the following: := . ( # % ; Error(8,44): PLS-00103:
Encountered the symbol ";" when expecting one of the following: )
, * & - + / at mod remainder rem and or ||
Could anyone help me with this?
As stated in the other answers the reason why your function won't compile is threefold.
You've declared the variable v_names and are referencing it as v_name.
The assignment operator in PL/SQL is :=, you're using the equality operator =.
You're missing a semi-colon in your return statement; it should be return v_name;
It won't stop the function from compiling but the variable v_names is declared as a varchar2(10). It's highly unlikely that when a manager with multiple subordinates all their last names will fit into this. You should probably declare this variable with the maximum size; just in case.
I would like to add that you're doing this a highly inefficient way. If you were to do the string aggregation in SQL as opposed to a PL/SQL loop it would be better. From 11g release 2 you have the listagg() function; if you're using a version prior to that there are plenty of other string aggregation techniques to achieve the same result.
create or replace function employee ( p_manid in employees.manager_id%type
) return varchar2 is
v_names varchar2(32767); -- Maximum size, just in case
begin
select listagg(lastname, ', ') within group ( order by lastname )
into v_names
from employees
where manager_id = p_manid;
return v_names;
exception when no_data_found then
return null;
end;
/
Please note a few other changes I've made:
Prepend a different letter onto the function parameter than the variable to make it clear which is which.
Add in some exception handling to deal with there being no data for that particular manager.
You would have returned , if you had no data I return NULL. If you want to return a comma instead simply put this inside the exception.
Rather than bother to create a cursor and loop through it etc I let Oracle do the heavy lifting.
It's rather curious that you would want to return a comma delimited list as there is little that you would be able to do with it in Oracle afterwards. It might be more normal to return something like an array or an open cursor containing all the surnames. I assume, in this answer, that you have a good reason for doing what you are.
There are a couple of things to be noted.
Declared as v_names but used as v_name
Assignemnt should be like v_name := v_name || emp_rec.last_name ||
', ';
v_name is declared with size of 10, it would be too small and would
give an error when you execute, so you could declare as
v_name employees.last_name%TYPE;
You could create your function as
CREATE OR REPLACE FUNCTION employee (v_manid IN employees.manager_id%TYPE)
RETURN VARCHAR2
AS
v_name employees.last_name%TYPE;
CURSOR cur_emp
IS
SELECT last_name
FROM employees
WHERE manager_id = v_manid;
BEGIN
FOR emp_rec IN cur_emp
LOOP
v_name := v_name || emp_rec.last_name || ', ';
END LOOP;
RETURN v_name;
END;
/
I guess you should use := instead of =
like
v_name := v_name || emp_rec.last_name ||', ';
one more thing you also need to add semicolon ; at the end of return v_name like
return v_name;

Resources