Oracle - PLS-00642: local collection types not allowed in SQL statements - oracle

I am new to programming in ORACLE and I am trying to compare a table column value to a passed in array and I am having a rather frustrating time in doing so.
Here is the Type Declaration from the package head.
TYPE T_STRING_ARRAY IS TABLE OF VARCHAR2(5);
and here is the the function that is using it.
create or replace PACKAGE BODY TEST_PACK IS
FUNCTION TEST_LOG_FN
(
PI_START_DATE IN VARCHAR2,
PI_END_DATE IN VARCHAR2,
PI_LOG_TYPE IN T_STRING_ARRAY
)
RETURN T_REF_CURSOR
AS
PO_RESULT T_REF_CURSOR;
BEGIN
OPEN PO_RESULT FOR
SELECT
EL.ENTRY_BASE_LOG_ID,
EL.APP_NAME,
EL.APP_MODULE,
EL.CREATION_DATE,
EL.APP_STATUS,
EL.LOG_TYPE
FROM
LG_ENTRY_BASE_LOG EL
WHERE
CREATION_DATE > PI_START_DATE AND
CREATION_DATE < PI_END_DATE AND
(EL.LOG_TYPE IN PI_LOG_TYPE OR PI_LOG_TYPE = NULL);
RETURN
PO_RESULT;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN NULL;
END TEST_LOG_FN;
END;
The error I am getting is PLS-00642: local collection types not allowed in SQL statements. I have read online
"To avoid the PLS-00642, the collection will need to be defined at the schema level; therefore, you would need to define the varray table as a real table, using Oracle DDL with the CREATE TYPE syntax. "
http://www.dba-oracle.com/t_pls_00642_local_collection_types_not_allowed_in_sql_statement.htm
I am not sure how to do that nor have I found any references online that I could use. Can someone help me with that? If someone knows an easier way to see if a string exists in an array, that is a perfectly acceptable answer as well.

You can use types defined in the package spec in Oracle 12C or later.
This line:
(EL.LOG_TYPE IN PI_LOG_TYPE OR PI_LOG_TYPE = NULL)
Needs to be:
(EL.LOG_TYPE IN (select column_value from table(PI_LOG_TYPE))
OR (select count(*) from table(PI_LOG_TYPE)) = 0)
Prior to 12C you need to define the type in the database using CREATE TYPE. The syntax for the select is the same either way.

Rather than using IN you can use the MEMBER OF operator designed specifically for use with collections:
(PI_LOG_TYPE = NULL OR EL.LOG_TYPE MEMBER OF PI_LOG_TYPE);
As noted by #TonyAndrews If you are using Oracle 12c then you can use collections defined in a package in PL/SQL but in earlier versions you will need to define them in SQL using the CREATE TYPE statement.

Related

PL/SQL reusable dynamic sql program for same type of task but different table and column

Thank you for reply guys. I kind of solved my problem.
I used to try to update data with ref cursor in dynamic SQL using "where current of" but I now know that won't work.
Then I tried to use %rowtype to store both 'id' and 'clob' in one variable for future updating but turns out weak ref cursor can't use that type binding either.
After that I tried to use record as return of an ref cursor and that doesn't work on weak cursor either.
On the end, I created another cursor to retrieve 'id' separately along with cursor to retrieve 'clob' on the same time then update table with that id.
I'm now working on a Oracle data cleaning task and have a requirement like below:
There are 38 tables(maybe more in the future) and every table has one or multiple column which type is Clob. I need to find different keyword in those columns and according to a logic return binary label of the column and store it in a new column.
For example, there is a table 'myTable1' which has 2 Clob columns 'clob1' and 'clob2'. I'd like to find keyword 'sky' from those columns and store '0'(if not found) or '1'(if found) in two new columns 'clob1Sky','clob2Sky'.
I know if I could write it on a static way which will provide higher efficiency but I have to modify it for those very similar tasks every time. I want save some time on this so I'm trying to write it in a reusable way and not binding to certain table.
But I met some problem when writing the program. My program is like below:
create or replace PACKAGE body LABELTARGETKEYWORD
as
/**
#param varcher tableName: the name of table I want to work on
#param varchar colName: the name of clob column
#param varchar targetWord: the word I want to find in the column
#param varchar newColName: the name of new column which store label of clob
*/
PROCEDURE mainProc(tableName varchar, colName varchar,targetWord varchar,newColName varchar2)
as
type c_RecordCur is ref cursor;
c_sRecordCur c_recordCur;
/*other variables*/
begin
/*(1) check whether column of newColName exist
(2) if not, alter add table of newColName
(3) open cursor for retrieving clob
(4) loop cursor
(5) update set the value in newColName accroding to func labelword return
(6) close cursor and commit*/
end mainProc;
function labelWord(sRecord VARCHAR2,targetWord varchar2) return boolean...
function ifColExist(tableName varchar2,newColName varchar2) return boolean...
END LABELTARGETKEYWORD;
Most DML and DDL are written in dynamic sql way.
The problem is when I write the (5) part, I notice 'Where current of' clause can not be used in a ref cursor or dynamic sql statement. So I have to change the plan.
I tried to use a record(rowid,label) to store result and alter the table later.(the table only be used by two people in my group, so there won't be problem of lock and data changes). But I find because I'm trying to use dynamic sql so actually I have to define ref cursor with return of certain %rowtype and basically all other variables, %type in dynamic sql statement. Which makes me feel my method has something wrong.
My question are:
If there a way to define %type in dynamic sql? Binding type to variable in dynamic SQL?
Could anybody give me a hint how to write that (5) part in dynamic SQL?
Should not I design my program like that?
Is it not the way how to use dynamic SQL or PLSQL?
I'm very new to PL/SQL. Thank you very much.
According to Tom Kyte's advice, to do it in one statement if it can be done in one statement, I'd try to use a single UPDATE statement first:
CREATE TABLE mytable1 (id NUMBER, clob1 CLOB,
clob2 CLOB, clob1sky NUMBER, clob2sky NUMBER )
LOB(clob1, clob2) STORE AS SECUREFILE (ENABLE STORAGE IN ROW);
INSERT INTO mytable1(id, clob1, clob2)
SELECT object_id, object_name, object_type FROM all_objects
WHERE rownum <= 10000;
CREATE OR REPLACE
PROCEDURE mainProc(tableName VARCHAR2, colName VARCHAR2, targetWord VARCHAR2, newColName VARCHAR2)
IS
stmt VARCHAR2(30000);
BEGIN
stmt := 'UPDATE '||tableName||' SET '||newColName||'=1 '||
'WHERE DBMS_LOB.INSTR('||colName||','''||targetWord||''')>1';
dbms_output.put_line(stmt);
EXECUTE IMMEDIATE stmt;
END mainProc;
/
So, calling it with mainProc('MYTABLE1', 'CLOB1', 'TAB', 'CLOB1SKY'); fires the statement
UPDATE MYTABLE1 SET CLOB1SKY=1 WHERE DBMS_LOB.INSTR(CLOB1,'TAB')>1
which seems to do the trick:
SELECT * FROM mytable1 WHERE clob1sky=1;
id clob1 clob2 clob1sky clob2skiy
33 I_TAB1 INDEX 1
88 NTAB$ TABLE 1
89 I_NTAB1 INDEX 1
90 I_NTAB2 INDEX 1
...
I am not sure with your question-
If this job is suppose to run on daily or hourly basis ,running query through it will be very costly. One thing you can do - put all your clob data in a file and save it in your server(i guess it must be linux). then you can create a shell script and schedule a job to run gerp command and fetch your required value and "if found then update your table".
I think you should approaches problem another way:
1. Find all columns that you need:
CURSOR k_clobs
select table_name, column_name from dba_tab_cols where data_type in ('CLOB','NCLOB');
Or 2 cursor(you can build you query if you have more than 1 CLOB per table:
CURSOR k_clobs_table
select DISTINCT table_name from dba_tab_cols where data_type in ('CLOB','NCLOB');
CURSOR k_clobs_columns(table_namee varchar(255)) is
select column_name from dba_tab_cols where data_type in ('CLOB','NCLOB') and table_name = table_namee;
Now you are 100% that column you are checking is clob, so you don't have to worry about data type ;)
I'm not sure what you want achieve, but i hope it may help you.

How can use array type in where clause of update statement

I want a procedure get a list of disabling privilege, and update their record in table. For doing this scenario, I defined an array as database object with below code:
CREATE OR REPLACE TYPE T_DISABLE_LIST IS TABLE OF NUMBER(32)
Then I defined an input parameter in procedure signature, and got passed value.
PROCEDURE PRC_ROLE_PRIVILAGE_MANAGEMENT(P_REQ_USER_ID IN VARCHAR2,
P_DISABLE_LIST IN T_DISABLE_LIST,
P_RES_DESC OUT VARCHAR2)
BEGIN
UPDATE T_ PRIVILAGE p
SET P.ENABLE_STATUS = 0, P.GRANT_USERID = P_REQ_USER_ID
WHERE P.ID IN (SELECT * FROM TABLE(P_DISABLE_LIST));
COMMIT;
EXCEPTION
WHEN OTHERS THEN
RES_DESC := SUBSTR(SQLERRM,1, 400);
END;
This procedure will be compile successfully. But when I test it, I got this error:
ORA-22905: cannot access rows from a non-nested table item
Any body can help me? And say why this code don't work correctly?
And finally, how can i resolve this problem?
P.S: My orcale version is 9.2!!!
This assumes you are using Oracle 10g or later (and was written before the OP clarified what version they are using)
Use the MEMBER OF operator:
UPDATE T_PRIVILAGE
SET ENABLE_STATUS = 0,
GRANT_USERID = P_REQ_USER_ID
WHERE ID MEMBER OF P_DISABLE_LIST;
You can also use the COLUMN_VALUE pseudo-column:
UPDATE T_PRIVILAGE
SET ENABLE_STATUS = 0,
GRANT_USERID = P_REQ_USER_ID
WHERE ID IN ( SELECT COLUMN_VALUE FROM TABLE( P_DISABLE_LIST ) );
why this code does not work correctly?
SELECT * FROM TABLE( P_DISABLE_LIST )
Is selecting the row from the table. However, the table is generated by a table collection expression and there is no underlying database table to reference a row of so Oracle generates an ORA-22905 exception.(there would be an underlying table if the collection was stored in a nested table; which is why that situation is specifically mentioned in the exception).
Update: PL/SQL solution:
FOR i IN 1 .. P_DISABLE_LIST.COUNT LOOP
UPDATE T_PRIVILAGE
SET ENABLE_STATUS = 0,
GRANT_USERID = P_REQ_USER_ID
WHERE ID = P_DISABLE_LIST(i);
END LOOP;

Create Type based on an exiting Table

As the title said : I want to create a type in oracle based on an existing Table.
I did as follow :
create or replace type MY_NEW_TYPE as object( one_row EXISTING_TABLE%rowtype);
The Aim is to be able to use this into a function which will return a table containing sample row of the table EXISTING_TABLE :
create or replace function OUTPUT_FCT() return MY_NEW_TYPE AS
...
If you only need to create a function that returns a row from your table, you could try something like the following, without creating types.
setup:
create table EXISTING_TABLE( a number, b varchar2(100));
insert into EXISTING_TABLE values (1, 'one');
function:
create or replace function OUTPUT_FCT return EXISTING_TABLE%rowtype AS
retVal EXISTING_TABLE%rowType;
begin
select *
into retVal
from EXISTING_TABLE
where rownum = 1;
--
return retVal;
end;
function call
SQL> begin
2 dbms_output.put_line(OUTPUT_FCT().a);
3 dbms_output.put_line(OUTPUT_FCT().b);
4 end;
5 /
1
one
However, I would not recommend such an approach, because things like select * can be really dangerous; I would much prefer defining a type with the fields I need, and then explicitly query my table for the needed columns.
No, you can't do that, you'll get a compilation error:
create or replace type my_new_type as object(one_row t42%rowtype);
/
Type MY_NEW_TYPE compiled
Errors: check compiler log
show errors
Errors for TYPE STACKOVERFLOW.MY_NEW_TYPE:
LINE/COL ERROR
-------- -----------------------------------------------------------------------
0/0 PL/SQL: Compilation unit analysis terminated
1/36 PLS-00329: schema-level type has illegal reference to MYSCHEMA.T42
You will need to specify each field in the object type, and you will have to specify the data types manually too - you can't use table.column%type either.
You could create the type dynamically based on column and data type information from the data dictionary, but as this will (hopefully) be a one-off task and not something you'd do at runtime, that doesn't really seem worth it.
You can create a PL/SQL table type based on your table's rowtype, but you would only be able to call a function returning that from PL/SQL, not from plain SQL - so you couldn't use it in a table collection expression for example. If you were only returning a single sample row you could return a record rather than a table, but the same applies. You can also have a function that returns a ref cursor which could match the table's structure, but you wouldn't be able to treat that as a table either.
Read more about object type creation in the documentation. Specifically the attribute and datatype sections.

Error with distinct, oracle and CLOB in Grails

I have an application written in grails 2.2.5 that needs to connect with MySQL, Oracle and SQL Server depending on my customers. We have more than 1000 queries that uses distinct returning instances of classes.
Example:
import br.com.aaf.auditoria.*
def query="select distinct tipo from Atividade c join c.tipoAtividade tipo order by tipo.nome"
def ret=Atividade.executeQuery(query)
So far so good, but now I need to include some CLOBs columns in oracle to expand some fields from VarChar 4000. When I do that these queries stop working because of the problem that Oracle does not compare CLOB columns.
Error:
ORA-00932: inconsistent datatypes: expected - got CLOB
I understand that Grails/Hibernate uses all properties of the domain class to make the sql to send to the database and return as an instance of that class.
The case is that I only need to compare or group the id of the domain class to make a distinct, but I need the result to be an instance of the class and not the id, so I don´t need to change all the queries.
Any of you know a way to change the behaviour of a distinct in HQL even if I need to customize a dialect to capture what Hibernate is doing in transforming HQL in SQL?
What I´m thinking is capture the SQL, change it to return and group only the id of the instance and execute a "get" in the Domain class before return this to "executeQuery".
The solution only fits to oracle db. You have to grant some privileges to your schema. "Create types" and " execute on DBMS_CRYPTO"
create table clob_test (id number, lob clob);
insert all
into clob_test values(1,'AAAAAAAA')
into clob_test values (2,'AAAAAAAA')
into clob_test values(3,'BBBBBBBB')
into clob_test values(4,'BBBBBBBB')
select * from dual;
commit;
CREATE OR REPLACE
type wrap_lob as object(
lob clob,
MAP MEMBER FUNCTION get_hash RETURN RAW
)
;
/
CREATE OR REPLACE
TYPE BODY wrap_lob is
MAP MEMBER FUNCTION get_hash RETURN RAW is
begin
return DBMS_CRYPTO.HASH(lob,1);
end;
end;
/
select tab.dist_lob.lob from (select distinct wrap_lob(lob) dist_lob from clob_test) tab;

Table-Valued Functions in ORACLE 11g ? ( parameterized views )

I've seen discussions about this in the past, such as here. But I'm wondering if somewhere along the line, maybe 10g or 11g (we are using 11g), ORACLE has introduced any better support for "parameterized views", without needing to litter the database with all sorts of user-defined types and/or cursor definitions or sys_context variables all over.
I'm hoping maybe ORACLE's added support for something that simply "just works", as per the following example in T-SQL:
CREATE FUNCTION [dbo].[getSomeData] (#PRODID ROWID)
RETURNS TABLE AS
RETURN SELECT PRODID, A, B, C, D, E
FROM MY_TABLE
WHERE PRODID = #PRODID
Then just selecting it as so:
SELECT * FROM dbo.getSomeData(23)
No need for SYS_CONTEXT or cursor definitions.
You do need a type so that, when the SQL is parsed, it can determine which columns are going to be returned.
That said, you can easily write a script that will generate type and collection type definitions for one or more tables based on the data in user_tab_columns.
The closest is
create table my_table
(prodid number, a varchar2(1), b varchar2(1),
c varchar2(1), d varchar2(1), e varchar2(1));
create type my_tab_type is object
(prodid number, a varchar2(1), b varchar2(1),
c varchar2(1), d varchar2(1), e varchar2(1))
.
/
create type my_tab_type_coll is table of my_tab_type;
/
create or replace function get_some_data (p_val in number)
return my_tab_type_coll pipelined is
begin
FOR i in (select * from my_table where prodid=p_val) loop
pipe row(my_tab_type(i.prodid,i.a,i.b,i.c,i.d,i.e));
end loop;
return;
end;
/
SELECT * FROM table(get_Some_Data(3));
It is possible to define a kind of "parametrized" views in Oracle.
The steps are:
Define a package containing as public members that are in fact the needed parameters (there is no need for functions or procedures in that package),
Define a view that is based on that package members.
To use this mechanism one user should:
open a session,
assign the desired values to that package members,
SELECT data from the view,
do other stuff or close the session.
REMARK: it is essential for the user to do all the three steps in only one session as the package members scope is exactly a session.
There are TWO types of table-valued functions in SQL SERVER:
Inline table-valued function: For an inline table-valued function, there is no function body; the table is the result set of a single SELECT statement. This type can be named as
'parameterized view' and it has no equivalent in ORACLE as I know.
Multistatement table-valued function: For a multistatement table-valued function, the function body, defined in a BEGIN...END block, contains a series of Transact-SQL statements that build and insert rows into the table that will be returned.
The above sample (By Gary Myers) creates a table function of the second type and it is NOT a 'parameterized view'.

Resources