Oracle - In CLAUSE question when using with multiple values, making it dynamic - oracle

I just spent an hour on google and here trying to get a straight answer for how to do this in Oracle. What I need is the ability to use select in clause that constructed automatically such as
select col1 from table1 where id.table IN ('1','2','3');
where the id values are passed to the stored procedure inside the array. The associative array has been defined as such:
TYPE varchar_array_type IS TABLE OF VARCHAR2 (40) INDEX BY BINARY_INTEGER;
Is there a simple, concrete way to do that? thanks

Unfortunately, if your collection type is defined in PL/SQL (rather than SQL), you cannot use it in SQL because the SQL engine doesn't know how to handle it.
If instead you defined the collection type in SQL, i.e.
CREATE TYPE varchar_tbl
IS TABLE OF varchar2(40);
Then you can do something like
SELECT col1
FROM table1 t1
WHERE t1.id IN (SELECT column_value
FROM TABLE( <<variable of type varchar2_tbl>> ) )
depending on the Oracle version-- the syntax for using collections in SQL has evolved over time-- older versions of Oracle had more complex syntax.
You can convert a PL/SQL associative array (your VARCHAR_ARRAY_TYPE) to a SQL nested table collection in PL/SQL, but that requires iterating through the associative array and filling the nested table, which is a bit of a pain. Assuming that the VARCHAR_TBL nested table collection has been created already
SQL> CREATE OR REPLACE TYPE varchar_tbl
IS TABLE OF varchar2(40);
you can convert from the associative array to the nested table and use the nested table in a SQL statement like this (using the SCOTT.EMP table)
declare
type varchar_array_type
is table of varchar2(40)
index by binary_integer;
l_associative_array varchar_array_type;
l_index binary_integer;
l_nested_table varchar_tbl := new varchar_tbl();
l_cnt pls_integer;
begin
l_associative_array( 1 ) := 'FORD';
l_associative_array( 10 ) := 'JONES';
l_associative_array( 100 ) := 'NOT A NAME';
l_associative_array( 75 ) := 'SCOTT';
l_index := l_associative_array.FIRST;
while( l_index IS NOT NULL )
loop
l_nested_table.EXTEND;
l_nested_table( l_nested_table.LAST ) :=
l_associative_array( l_index );
l_index := l_associative_array.NEXT( l_index );
end loop;
SELECT COUNT(*)
INTO l_cnt
FROM emp
WHERE ename IN (SELECT column_value
FROM TABLE( l_nested_table ) );
dbms_output.put_line( 'There are ' || l_cnt || ' employees with a matching name' );
end;
Because converting between collection types is a bit of a pain, however, you would generally be better off just using the nested table collection (and passing that to the stored procedure) unless there is a particular reason that the associative array is needed.

Related

PLS-00382: expression is of wrong type - when using an array as a var in oracle function

I get 'expression is of wrong type' error when I'm trying to execute the following sql procedure:
DECLARE
v_date TIMESTAMP(6) := to_date('03-11-2011', 'dd-mm-yyyy');
BEGIN
for newiD in (select tabA.id from tabA where tabA.prop = 1)
loop
insert into tabB values (newId, 'testval', 'testval', newId, v_date);
end loop;
END;
Is there another way I can declare the array I'm trying to store into the newId variable?
Update: The data type of the tabA.id and the next tabB.newId is the same - NUMBER(19,0)
A better way, with no loops and just SQL, could be:
DECLARE
v_date TIMESTAMP(6) := to_date('03-11-2011', 'dd-mm-yyyy');
BEGIN
insert into tabB /* here it would be better to list the columns */
select tabA.id, 'testval', 'testval', tabA.id, v_date
from tabA
where tabA.prop = 1
END;
In this case newId is a record wich has one field: id. That's why you need to put newId.id in your insert clause.

How it insert result of select statement into table of records (associative array)

I am working on HR scheme of oracle. I have table of records
type emp_record is RECORD(emp_first_name employees.first_name%type,
emp_last_name employees.last_name%type,
);
type emp_record_table is table of emp_record
index by pls_integer;
I want to insert into emp_record_table results of the next select statement
select first_name, last_name
from employees
where department_id=30;
can you explain me how to solve this problem? thank you.
Simplest approach is to use bulk collect:
declare
type emp_record is RECORD(emp_first_name employees.first_name%type,
emp_last_name employees.last_name%type
);
type emp_record_table is table of emp_record
index by pls_integer;
l_recs emp_record_table;
begin
select first_name, last_name
bulk collect into l_recs
from employees
where department_id=30;
for idx in l_recs.first()..l_recs.last() loop
dbms_output.put_line(l_recs(idx).emp_first_name ||' '|| l_recs(idx).emp_last_name);
end loop;
end;
/
Note that you don't really need an associative array to process records like this. You can ditch the index by pls_integer and things will still work just fine. The value of associative arrays is when we need to maintain an access path to specific rows. For instance, we might want to use the primary key of the employees table to index the array. This would create a sparse array, because the selected employee IDs are not guaranteed to form a contiguous sequence. Consequently the logic for wrangling the array is more verbose:
declare
type emp_record is RECORD(emp_first_name employees.first_name%type,
emp_last_name employees.last_name%type
);
type emp_record_table is table of emp_record
index by pls_integer;
l_recs emp_record_table;
idx pls_integer;
begin
for r in (select emp_id, first_name, last_name
from employees
where department_id=30 )
loop
l_recs(r.emp_id).emp_first_name := r.first_name;
l_recs(r.emp_id).emp_last_name := r.last_name;
end loop;
idx := l_recs.first();
while idx is not null loop
dbms_output.put_line(l_recs(idx).emp_first_name ||' '|| l_recs(idx).emp_last_name);
idx := l_recs.next(idx);
end loop;
end;
/
Here is a demo on db<>fiddle.
Oracle Setup:
CREATE TABLE employees (
id NUMBER(8,0) PRIMARY KEY,
first_name VARCHAR2(50),
last_name VARCHAR2(80)
);
CREATE PACKAGE test_pkg IS
TYPE emp_record IS RECORD(
emp_first_name employees.first_name%type,
emp_last_name employees.last_name%type
);
TYPE emp_record_table IS TABLE OF emp_record INDEX BY pls_integer;
END;
/
INSERT INTO employees( id, first_name, last_name )
SELECT -1, 'a', 'aaa' FROM DUAL UNION ALL
SELECT +3, 'b', 'bbb' FROM DUAL;
PL/SQL Block:
DECLARE
x PLS_INTEGER;
emps test_pkg.emp_record_table;
BEGIN
-- Populate the associative array
FOR row IN ( SELECT * FROM employees ) LOOP
emps(row.id).emp_first_name := row.first_name;
emps(row.id).emp_last_name := row.last_name;
END LOOP;
-- Read the associative array
x := emps.FIRST;
WHILE x IS NOT NULL LOOP
DBMS_OUTPUT.PUT_LINE( x || ': ' || emps(x).emp_first_name || ' ' || emps(x).emp_last_name );
x := emps.NEXT(x);
END LOOP;
END;
/
Output:
-1: a aaa
3: b bbb
db<>fiddle here
You don't need associative array for that operation. Nested table will be perfect for that. Just declare your table type as following:
type emp_record_table is table of emp_record;
Firstly you have to declare table of your created type. Now you have only declared type or record and type of table of these records. You don't have yet your table. You can daclare it following way in the DECLARE section:
l_emp_table emp_record_table;
Then initialize it:
l_emp_table := emp_record_table();
Then you create cursor that will get data from your SELECT query. If you don't know how to do it, please read about cursor declaration, and fetching.
Next step will be: for each cursor row insert its data into table. You write simple loop that will do following steps:
Extend your declared table l_emp_table.extend()
Save data into table l_emp_table(i).emp_first_name := FETCHED_ROW.first_name and so on...

How to Insert multiple columns data into table containing single column using cursor in pl/sql?

I'm trying to insert data from table 1 to another table using cursor and purpose is to understand objects in Oracle.
However, I'm getting error like "Invalid no. of arguments" while inserting data using cursor loop.
create type airport_t as object
(
Rank number,
Airport varchar2(80),
Location varchar2(60),
Country varchar2(50),
Code_iata varchar2(3),
Code_icao varchar2(4),
Total_Passenger number,
Rank_change number,
Percent_change number
);
create table AIRPORTS2017OO
(
AIRPORT airport_t // Look above code..
);
declare
cursor insert_cr is select * from AIRPORTS2017;
begin
open insert_cr;
for i in insert_cr
loop
insert into Airports2017oo values( airport_t(i.Rank || '
'||i.airport ||' '||
i.Location ||' '|| i.Country ||' '|| i.code_iata ||' '||
i.code_icao ||' '||
i.Total_Passenger ||' '|| i.Rank_change ||' '||
i.Percent_change));
end loop;
end;
/
Table 1 contains 50 rows and 9 columns. However, table 2 has only 1 column and I want all data from table 1 into table 2 in single column only.
Error image:[1]
Your column in the AIRPORTS2017OO table is of type AIRPORT_T. The AIRPORT_T type, meanwhile, has a number of different parameters of various types. However, when inserting the row you are concatenating all fields into a single string to pass as a single parameter. You need to either pass them as separate parameters, or redefine your object type as having a single VARCHAR2 attribute. If the former is correct, the following should work.
DECLARE
CURSOR insert_cr IS
SELECT * FROM airports2017;
BEGIN
FOR i IN insert_cr LOOP
INSERT INTO airports2017oo
VALUES
(airport_t(i.rank
,i.airport
,i.location
,i.country
,i.code_iata
,i.code_icao
,i.total_passenger
,i.rank_change
,i.percent_change));
END LOOP;
END;
/
Also, see Oracle Object-Relational Developer's Guide for details on the system-defined constructor.

ORACLE PL/SQL for each passing tablename to procedure

I need to do a FOR EACH loop in a procedure, but I need to pass the table name dynamically.
This is the declaration
CREATE OR REPLACE PROCEDURE MIGRATE_PRIMITIVES_PROPS
(
FromTable IN VARCHAR2,
ToTable IN VARCHAR2
)
When I try and do this
FOR EachRow IN (SELECT * FROM FromTable) It says the table isn't valid
The table coming into the procedure is dynamic, columns are added and deleted all the time so I can't spell out the columns and use a cursor to populate them.
You have to use dynamic SQL to query a table whose name you don't know at compile time. You can do that with a dynamic cursor:
as
l_cursor sys_refcursor;
begin
open l_cursor for 'select * from ' || fromtable;
loop
fetch l_cursor into ...
... but then it breaks down because you can't define a record type to fetch into based on a weak ref cursor; and you don't know the column names or types you're actually interested in - you're using select * and have specific names to exclude, not include. You mentioned an inner loop that works and gets the column names, but there is no way to refer to a field in that cursor variable dynamically either.
So you have to work a bit harder and use the dbms_sql package instead of native dynamic SQL.
Here's a basic version:
create or replace procedure migrate_primitives_props
(
fromtable in varchar2,
totable in varchar2
)
as
l_cursor pls_integer;
l_desc_tab dbms_sql.desc_tab;
l_columns pls_integer;
l_value varchar2(4000);
l_status pls_integer;
begin
l_cursor := dbms_sql.open_cursor;
-- parse the query using the parameter table name
dbms_sql.parse(l_cursor, 'select * from ' || fromtable, dbms_sql.native);
dbms_sql.describe_columns(l_cursor, l_columns, l_desc_tab);
-- define all of the columns
for i in 1..l_columns loop
dbms_sql.define_column(l_cursor, i, l_value, 4000);
end loop;
-- execute the cursor query
l_status := dbms_sql.execute(l_cursor);
-- loop over the rows in the result set
while (dbms_sql.fetch_rows(l_cursor) > 0) loop
-- loop over the columns in each row
for i in 1..l_columns loop
-- skip the columns you aren't interested in
if l_desc_tab(i).col_name in ('COL_NAME', 'LIB_NAME', 'PARTNAME',
'PRIMITIVE', 'PART_ROW')
then
continue;
end if;
-- get the column value for this row
dbms_sql.column_value(l_cursor, i, l_value);
-- insert the key-value pair for this row
execute immediate 'insert into ' || totable
|| '(key, value) values (:key, :value)'
using l_desc_tab(i).col_name, l_value;
end loop;
end loop;
end;
/
I've assumed you know the column names in your ToTable but still used a dynamic insert statement since that table name is unknown. (Which seems strange, but...)
Creating and populating sample tables, and then calling the procedure with their names:
create table source_table (col_name varchar2(30), lib_name varchar2(30),
partname varchar2(30), primitive number, part_row number,
col1 varchar2(10), col2 number, col3 date);
create table target_table (key varchar2(30), value varchar2(30));
insert into source_table (col_name, lib_name, partname, primitive, part_row,
col1, col2, col3)
values ('A', 'B', 'C', 0, 1, 'Test', 42, sysdate);
exec migrate_primitives_props('source_table', 'target_table');
End up with the target table containing:
select * from target_table;
KEY VALUE
------------------------------ ------------------------------
COL1 Test
COL2 42
COL3 2015-05-22 15:29:31
It's basic because it isn't sanitising the inputs (look up the dbms_assert package), and isn't doing any special handling for different data types. In my example my source table had a date column; the target table gets a string representation of that date value based on the calling session's NLS_DATE_FORMAT setting, which isn't ideal. There's a simple but slightly hacky way to get a consistent date format, and a better but more complicated way; but you may not have date values so this might be good enough as it is.

How to initialise a nested table collection which resides inside a PL/SQL Record?

Friends,
Hope you can help.
What I am trying to achieve is to use a collection type(s) that can be accessed either inside and outside of PL/SQL so that an external program can declare a type of this collection and work with it's contents.
The collection will contain some scaler and one composite datatype.
Using the scott schema as an example, the goal is that each record within the collection should contain the department information and within the same record a collection containing the employee information for that department.
I have got the output I require when using PL/SQL associative arrays but they can only be used from with PL/SQL.
When I convert the code to use another type of collection, nested table, I receive a ORA-06531: Reference to uninitialized collection Which is because I haven't initialised the collection held within the record.
Is it possible to achieve this using this design? Or (as I increasing feel!) have I gone down the wrong path?
Two code samples follow.
Firstly the one that works with PL/SQL associative arrays:
DECLARE
TYPE emp_tab_type IS TABLE OF emp%ROWTYPE
INDEX BY BINARY_INTEGER;
TYPE dept_emp_rec IS RECORD (dept_id dept.deptno%TYPE,
dept_name dept.dname%TYPE,
dept_loc dept.loc%TYPE,
emp_data emp_tab_type);
TYPE dept_emp_tab_type IS TABLE OF dept_emp_rec
INDEX BY BINARY_INTEGER;
l_dept_emp_tab dept_emp_tab_type;
CURSOR dept_cur IS
SELECT d.*
FROM dept d
ORDER BY d.deptno;
CURSOR emps_cur (p_dept_id IN NUMBER ) IS
SELECT e.*
FROM emp e
WHERE e.deptno = p_dept_id
ORDER BY e.ename;
j PLS_INTEGER := 1;
k PLS_INTEGER;
BEGIN
FOR dept_rec IN dept_cur
LOOP
-- populate dept data
l_dept_emp_tab(j).dept_id := dept_rec.deptno;
-- other assignment statements
dbms_output.put_line('dept no ' || l_dept_emp_tab(j).dept_id);
-- populate emp data
k := 1;
FOR emp_row_rec IN emps_cur(dept_rec.deptno)
LOOP
l_dept_emp_tab(j).emp_data(k).empno := emp_row_rec.empno;
-- other assignment statements
dbms_output.put_line( l_dept_emp_tab(j).emp_data(k).empno);
k := k + 1;
END LOOP;
j := j + 1;
END LOOP;
END;
This is the example using nested tables that DOESN'T work
DECLARE
TYPE emp_tab_type IS TABLE OF emp%ROWTYPE;
--INDEX BY BINARY_INTEGER;
TYPE dept_emp_rec IS RECORD (dept_id dept.deptno%TYPE,
dept_name dept.dname%TYPE,
dept_loc dept.loc%TYPE,
emp_data emp_tab_type);
TYPE dept_emp_tab_type IS TABLE OF dept_emp_rec;
--INDEX BY BINARY_INTEGER;
l_dept_emp_tab dept_emp_tab_type := dept_emp_tab_type();
CURSOR dept_cur IS
SELECT d.*
FROM dept d
ORDER BY d.deptno;
CURSOR emps_cur (p_dept_id IN NUMBER ) IS
SELECT e.*
FROM emp e
WHERE e.deptno = p_dept_id
ORDER BY e.ename;
j PLS_INTEGER := 1;
k PLS_INTEGER;
BEGIN
FOR dept_rec IN dept_cur
LOOP
l_dept_emp_tab.EXTEND;
-- populate dept data
l_dept_emp_tab(j).dept_id := dept_rec.deptno;
-- other assignment statements
dbms_output.put_line('dept no ' || l_dept_emp_tab(j).dept_id);
-- populate emp data
k := 1;
FOR emp_row_rec IN emps_cur(dept_rec.deptno)
LOOP
l_dept_emp_tab(j).emp_data(k).empno := emp_row_rec.empno;
-- other assignment statements
dbms_output.put_line( l_dept_emp_tab(j).emp_data(k).empno);
k := k + 1;
END LOOP;
j := j + 1;
END LOOP;
END;
I am using Oracle Enterprise Edition 10.2.0.4
Thanks
You are indeed getting the ORA-06531 error because you haven't initialised the collections within each record. To do this, try adding the line
l_dept_emp_tab(j).emp_data := emp_tab_type();
to the other assignments to fields of l_dept_emp_tab(j).
You'll also need to add a call to l_dept_emp_tab(j).emp_data.EXTEND within the inner loop, to make space for the new entry about to be added. Insert this above all the assignments within the inner loop. If you don't add this, you'll get an ORA-06533: Subscript beyond count error.
You seem to be handling the outer nested table type (dept_emp_tab_type) correctly, by calling its constructor (in the DECLARE section) and by calling EXTEND to grow the nested table. All you need to do is to do the same for each instance of the inner nested table type,emp_tab_type .
Here is a different way, this accomplishes everything pretty much within a query (do note it requires the types to be created outside of the block)
http://download.oracle.com/docs/cd/B10501_01/appdev.920/a96624/05_colls.htm
Creation and cleanup of table and types used
/*
CREATE TABLE EMP (ENAME VARCHAR2(50) , DEPTNO NUMBER, empno number);
INSERT INTO EMP VALUES('m1e',1,1);
INSERT INTO EMP VALUES('m2e',1,2);
insert into emp values('m3e',2,3);
INSERT INTO EMP VALUES('m2e',2,4);
insert into emp values('m3e',3,5);
CREATE TABLE DEPT(deptno NUMBER, dname VARCHAR2(50), loc VARCHAR2(50));
INSERT INTO DEPT VALUES(1 ,'portland','tt');
INSERT INTO DEPT VALUES(2 ,'astoria','tt');
INSERT INTO DEPT VALUES(3 ,'eugene','tt');
Creation of types (note this is not within the package/block so that it is available to SQL)
---
drop type emptable force;
DROP TYPE EMP_TAB_TYPE force;
drop type emptable ;
DROP TYPE DEPT_EMP_REC force;
drop type dep_emp_rec_table force;
DROP TABLE DEPT;
drop table emp;
*/
Now create the types outside the package/block so the types are available to SQL
create or replace TYPE emp_tab_type as object (ENAME VARCHAR2(50) , DEPTNO NUMBER);
create or replace type emptable as table of emp_tab_type ;
CREATE OR REPLACE TYPE DEPT_EMP_REC AS OBJECT (
DEPT_ID NUMBER,
dept_name varchar2(50),
dept_loc varchar2(50),
emp_data emptable);
create or replace type dep_emp_rec_table as table of dept_emp_rec;
Now we can directly select the types into the query (note the use of the cast/MULTISET)
SELECT
DEPT_EMP_REC(
deptno,
dname ,
loc ,
CAST(MULTISET(SELECT ENAME, DEPTNO
FROM EMP e
WHERE e.DEPTNO = d.deptno)
AS emptable))
FROM DEPT D ;
/
DEPT_EMP_REC(DEPTNO,DNAME,LOC,CAST(MULTISET(SELECTENAME,DEPTNOFROMEMPEWHEREE.DEPTNO=D.DEPTNO)ASEMPTABLE)) DEPT_EMP_REC(1,'portland','tt',EMPTABLE(EMP_TAB_TYPE('m1e',1),EMP_TAB_TYPE('m2e',1)))
DEPT_EMP_REC(2,'astoria','tt',EMPTABLE(EMP_TAB_TYPE('m3e',2),EMP_TAB_TYPE('m2e',2)))
DEPT_EMP_REC(3,'eugene','tt',EMPTABLE(EMP_TAB_TYPE('m3e',3)))
Now the block is a bit simpler (putting it all together)
set serveroutput on
DECLARE
p_dep_emp_rec_table dep_emp_rec_table;
BEGIN
SELECT
DEPT_EMP_REC(
DEPTNO,
DNAME,
LOC,
CAST( MULTISET
(
SELECT
ENAME,
DEPTNO
FROM EMP E
WHERE E.DEPTNO = D.DEPTNO
) AS EMPTABLE )
)
BULK COLLECT INTO p_dep_emp_rec_table
FROM
DEPT d ;
FOR I IN P_DEP_EMP_REC_TABLE.FIRST..P_DEP_EMP_REC_TABLE.LAST LOOP
DBMS_OUTPUT.PUT_LINE(I || ':' || P_DEP_EMP_REC_TABLE(I).DEPT_ID || '|' || P_DEP_EMP_REC_TABLE(I).DEPT_NAME || '|' || P_DEP_EMP_REC_TABLE(I).DEPT_LOC);
DBMS_OUTPUT.PUT_LINE('-----------------------');
FOR J IN P_DEP_EMP_REC_TABLE(I).EMP_DATA.FIRST..P_DEP_EMP_REC_TABLE(I).EMP_DATA.LAST LOOP
NULL;
dbms_output.put_line(P_DEP_EMP_REC_TABLE(i).emp_data(j).ENAME || '/' || P_DEP_EMP_REC_TABLE(i).emp_data(j).DEPTNO);
end loop;
END LOOP;
END;
anonymous block completed
1:1|portland|tt
-----------------------
m1e/1
m2e/1
2:2|astoria|tt
-----------------------
m3e/2
m2e/2
3:3|eugene|tt
-----------------------
m3e/3

Resources