Oracle PL/SQL - Bulk Collection usage into WHERE and FROM clause inside conditions and inner views - oracle

I have a strange problem using bulk collection as element of FROM clause.
When I execute this code example, I get, just at run-time, the error "invalid table name".
If I replace the collection with a table everything works well.
Is there any restriction about bulk collection that I'm missing?
Maybe I cannot use anonymous block in FROM clause?
In the sql debugger I see that l_vol(i) has values but l_vol(i).FIELD doesn't exists.
Thanks.
TYPE t_bulk_vol is table of vol%ROWTYPE;
l_vol t_bulk_vol;
...
cursor cur is SELECT * FROM vol where ... ;
OPEN CUR;
LOOP
FETCH CUR BULK COLLECT INTO l_vol;
....
insert into dest
select col1, col2, ... from
(inner view with some pivot, unpivot and l_vol(i).FIELD ...) src where l_vol(i).FIELD = src.FIELD;
PS: I cannot paste original code.

TYPE t_bulk_vol is a PL/SQL type. That means you can only use it in PL/SQL constructs. You cannot use it in SQL, even if it's SQL in a PL/SQL program.
If you want to use a nested table in the FROM clause of a SELECT you will need to define a SQL TYPE. This is a pain, because it means you can't use the %ROWTYPE definition (that's a PL/SQL only keyword). So you'll have to create an object whose signature matches the projection of the table, and then create nested table of that type. Find out more.
Your cursor is defined wrongly. It should just be a SELECT statement.
cursor cur is SELECT * FROM vol where ... ;
Save the BULK COLLECT INTO l_vol for the actual fetch.
Although presumably this is just a artefact of you faking some PL/SQL because you "cannot paste original code."

I have created the type as you said, but I get the same error at the same point (ORA-00903 - invalid table name).
This is an example of what I've done:
CREATE TYPE REC_VOL AS OBJECT (
FIELD1 VARCHAR2(25),
...
);
create TYPE T_BULK IS TABLE OF REC_VOL;
....
l_vol t_bulk;
...
This is the way I collect the records (I don't use the cursor anymore):
SELECT REC_VOL(FIELD1, ...) BULK COLLECT INTO l_vol
FROM vol where ...;
The exception is still raised at the insert-select statement.

Related

INTO error in SELECT while creating stored procedure in PL/SQL

I am trying to create stored procedure where I want to join two tables and save the result into one of the tables, but I am getting INTO clause is required error.
This is my code:
CREATE PROCEDURE DiorItemMaster
AS
SELECT *FROM pcdo_dior_item_master
INNER JOIN pcdo_itemdata on pcdo_itemdata.vpn = pcdo_dior_item_master.vpn;
GO;
ERROR:
Error(4,1): PLS-00428: an INTO clause is expected in this SELECT statement
You have a query that selects columns from a table in your pl/sql block. What do you want to do with the result of that query ? You cannot just select and not do anything with the results in pl/sql (you can in sql). Oracle expects that you to store the results of that select in variables. That can be done using the SELECT INTO clause.
Example (based on sample schema emp/dept):
DECLARE
l_emp emp%ROWTYPE;
BEGIN
SELECT e.* INTO l_emp FROM emp e WHERE e.ename = 'KING';
END;
/
Note that you can SELECT INTO individual columns and into rows. You cannot use SELECT INTO arrays.
A couple of other remarks about your code:
You perform a SELECT * from a table with a join to another table without using aliases. This will return all columns from both tables. It is a lot more readable to prefix the "*" with a table alias like in the example.
The GO; is not part of the oracle syntax - this will cause a compilation error.

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.

trying to copy data from a prepopulated collection to an associated array in a function

I am getting below error while trying to copy data from one collection to other.
Error(17,8): PL/SQL: ORA-00904: "COLUMN_VALUE": invalid identifier
Please help me providing a better way.
create or replace type type_record as object(employee_id NUMBER(6),
first_name VARCHAR2(20));
create or replace type type_tbl as table of type_record;
create or replace function scrub_final_2 return sys_refcursor IS
x type_tbl;
test1 type_tbl;
y sys_refcursor;
z sys_refcursor;
begin
x:=type_tbl();
z:=scrub_final_1; /*This is a function which returns a refcursor*/
loop
fetch z bulk collect into test1;
exit when z%NOTFOUND;
select column_value bulk collect into x from table(test1);
end loop;
open y for select employee_id,first_name from employees a where not exists
(select employee_id from table(x) where a.employee_id=employee_id);
return y;
end;
First, using x, y, z, and test1 as variable names makes it relatively hard to understand your code since it is not obvious at any point which variables represent a cursor and which represent a collection. Calling an object type_record is also confusing since it is not, in fact, a record which is a PL/SQL structure very similar to a SQL object. Additionally, your title is rather confusing since neither of your collections are actually associative arrays.
Second, your loop as currently constructed doesn't make any sense. If you're going to have a loop, you'd want to do a bulk collect with a limit. If you're not going to use a limit in your bulk collect, there is no point in looping since you'll only ever have one iteration of the loop.
Third, there appears to be no reason to copy the data from one collection to another. You can use the data in test1 to open y rather than using x in the query. Using a second collection just means that you're wasting valuable space in the PGA.
Fourth, if you really do want to copy the data from one collection to another, you can do a simple assignment
x := test1;
Fifth, if you're going to write a select against a collection defined on an object type, the columns in the result will be the names of the object type's attributes. column_value is only a column name for collections of built-in types. If you really, really wanted to so the assignment the hard way with a select statement, you'd do something like
SELECT type_record( employee_id, first_name )
BULK COLLECT INTO x
FROM TABLE( test1 );

plsql table type with index of is complaining

Pl/SQL:
Intent: My intent was to access employee tuple object defied as cursor below by using key as the employee_id.
Problem: I created a cursor - *l_employees_cur* and want to create type table as below type *l_employees_t*, as below but the compiler is complaining saying that PLS-00315 implementation restriction unsupported table index type.
CURSOR l_employees_cur
IS
SELECT employee_id,manager_id,first_name,last_name FROM employees;
type l_employees_t
IS
TABLE OF l_employees_cur%rowtype INDEX BY employees.employee_id%TYPE;
The definition of employees.employee_id is:
EMPLOYEE_ID NUMBER(6) NOT NULL
why can't I do this ? or Am I doint something wrong.
From the Oracle Documenation:
Associative Arrays
An associative array (formerly called PL/SQL table or index-by table) is a set of key-value pairs. Each key is a unique index, used to locate the associated value with the syntax variable_name(index).
The data type of index can be either a string type or PLS_INTEGER. Indexes are stored in sort order, not creation order. For string types, sort order is determined by the initialization parameters NLS_SORT and NLS_COMP.
I think that your mistake is the declaration of the plsql table.
Why don't you try the next one:
type l_employees_t
IS
TABLE OF l_employees_cur%rowtype INDEX BY pls_integer;
I also have a question for you:
What is the meaning of EMPLOYEE_ID NOT NULL NUMBER(6) in your code above?
Greetings
Carlos
Storing and Retreiving SQL Query Output in a PL/SQL Collection
The example in the OP looks a lot like Oracle's new sample HR data schema. (For those old-timers who know, the successor to the SCOTT-TIGER data model). This solution was developed on an Oracle 11g R2 instance.
The Demo Table Design - EMP
Demonstration Objectives
This example will show how to create a PL/SQL collection from an object TYPE definition. The complex data type is derived from the following cursor definition:
CURSOR l_employees_cur IS
SELECT emp.empno as EMPLOYEE_ID, emp.mgr as MANAGER_ID, emp.ename as LAST_NAME
FROM EMP;
After loading the cursor contents into an index-by collection variable, the last half of the stored procedure contains an optional step which loops back through the collection and displays the data either through DBMS_OUTPUT or an INSERT DML operation on another table.
Stored Procedure Example Source Code
This is the stored procedure used to query the demonstration table, EMP.
create or replace procedure zz_proc_employee is
CURSOR l_employees_cur IS
SELECT emp.empno as EMPLOYEE_ID, emp.mgr as MANAGER_ID, emp.ename as LAST_NAME
FROM EMP;
TYPE employees_tbl_type IS TABLE OF l_employees_cur%ROWTYPE INDEX BY PLS_INTEGER;
employees_rec_var l_employees_cur%ROWTYPE;
employees_tbl_var employees_tbl_type;
v_output_string varchar2(80);
c_output_template constant varchar2(80):=
'Employee: <<EMP>>; Manager: <<MGR>>; Employee Name: <<ENAME>>';
idx integer;
outloop integer;
BEGIN
idx:= 1;
OPEN l_employees_cur;
FETCH l_employees_cur INTO employees_rec_var;
WHILE l_employees_cur%FOUND LOOP
employees_tbl_var(idx):= employees_rec_var;
FETCH l_employees_cur INTO employees_rec_var;
idx:= idx + 1;
END LOOP;
CLOSE l_employees_cur;
-- OPTIONAL (below) Output Loop for Displaying The Array Contents
-- At this point, employees_tbl_var can be handed off or returned
-- for additional processing.
FOR outloop IN 1 .. idx LOOP
-- Build the output string:
v_output_string:= replace(c_output_template, '<<EMP>>',
to_char(employees_tbl_var(outloop).employee_id));
v_output_string:= replace(v_output_string, '<<MGR>>',
to_char(employees_tbl_var(outloop).manager_id));
v_output_string:= replace(v_output_string, '<<ENAME>>',
employees_tbl_var(outloop).last_name);
-- dbms_output.put_line(v_output_string);
INSERT INTO zz_output(output_string, output_ts)
VALUES(v_output_string, sysdate);
COMMIT;
END LOOP;
END zz_proc_employee;
​
I commented out the dbms_output call due to problems with the configuration of my server beyond my control. The alternate insert command to a output table is a quick way of visually verifying that the data from the EMP table found its way successfully into the declared collection variable.
Results and Discussion of the Solution
Here is my output after calling the procedure and querying my output table:
While the actual purpose behind the access to this table isn't clear in the very terse detail of the OP, I assumed that the first approach was an attempt to understand the use of collections and custom data types for efficient data extraction and handling from structures such as PL/SQL cursors.
The portion of this example procedure is very reusable, and the initial steps represent a working way of making and loading PL/SQL collections. If you notice, even if your own version of this EMP table is different, the only place that requires redefinition is the cursor itself.
Working with types, arrays, nested tables and other collection types will actually simplify work in the long run because of their dynamic nature.

Can a table variable be used in a select statement where clause?

I have a stored procedure that is doing a two-step query. The first step is to gather a list of VARCHAR2 type characters from a table and collect them into a table variable, defined like this:
TYPE t_cids IS TABLE OF VARCHAR2(50) INDEX BY PLS_INTEGER;
v_cids t_cids;
So basically I have:
SELECT item BULK COLLECT INTO v_cids FROM table_one;
This works fine up until the next bit.
Now I want to use that collection in the where clause of another query within the same procedure, like so:
SELECT * FROM table_two WHERE cid IN v_cids;
Is there a way to do this? I am able to select an individual element, but I would like to use the table variable like a would use a regular table. I've tried variations using nested selects, but that doesn't seem to work either.
Thanks a lot,
Zach
You have several choices as to how you achieve this.
If you want to use a collection, then you can use the TABLE function to select from it but the type of collection you use becomes important.
for a brief example, this creates a database type that is a table of numbers:
CREATE TYPE number_tab AS TABLE OF NUMBER
/
Type created.
The next block then populates the collection and performs a rudimentary select from it using it as a table and joining it to the EMP table (with some output so you can see what's happening):
DECLARE
-- Create a variable and initialise it
v_num_tab number_tab := number_tab();
--
-- This is a collection for showing the output
TYPE v_emp_tabtype IS TABLE OF emp%ROWTYPE
INDEX BY PLS_INTEGER;
v_emp_tab v_emp_tabtype;
BEGIN
-- Populate the number_tab collection
v_num_tab.extend(2);
v_num_tab(1) := 7788;
v_num_tab(2) := 7902;
--
-- Show output to prove it is populated
FOR i IN 1 .. v_num_tab.COUNT
LOOP
dbms_output.put_line(v_num_tab(i));
END LOOP;
--
-- Perform a select using the collection as a table
SELECT e.*
BULK COLLECT INTO v_emp_tab
FROM emp e
INNER JOIN TABLE(v_num_tab) nt
ON (e.empno = nt.column_value);
--
-- Display the select output
FOR i IN 1 .. v_emp_tab.COUNT
LOOP
dbms_output.put_line(v_emp_tab(i).empno||' is a '||v_emp_tab(i).job);
END LOOP;
END;
You can see from this that the database TYPE collection (number_tab) was treated as a table and could be used as such.
Another option would be to simply join your two tables you are selecting from in your example:
SELECT tt.*
FROM table_two tt
INNER JOIN table_one to
ON (to.item = tt.cid);
There are other ways of doing this but the first might suit your needs best.
Hope this helps.
--Doesn't work.
--SELECT item BULK COLLECT AS 'mySelectedItems' INTO v_cids FROM table_one;
SELECT table_two.*
FROM table_two INNER JOIN v_cids
ON table_two.paramname = v_cids.mySelectedItems;
Unless I'm misunderstanding the question, this should only return results that are in the table variable.
Note: I've never used Oracle, but I imagine this case would be the same.

Resources