I am trying to lookup values from an associative array to update a table, but am getting error. Here is a bare minimum code to demonstrate the issue (my actual query is much more complex).
Am I missing something, or if this is an Oracle limitation is there a way around it?
CREATE TABLE t_employee AS SELECT cast('john' as varchar2(30)) emp_name, CAST (NULL AS NUMBER (5)) emp_id FROM DUAL;
DECLARE
TYPE emp_typ IS TABLE OF NUMBER INDEX BY VARCHAR2(30);
emp_lookup emp_typ;
BEGIN
emp_lookup ('john') := 1234;
UPDATE t_employee e
SET emp_id = (SELECT emp_lookup (e.emp_name) FROM DUAL);
END;
/
--DROP TABLE t_employee;
> PLS-00201: identifier 'E.EMP_NAME' must be declared
and when I change the update to a simpler construct:
UPDATE t_employee SET emp_id = emp_lookup (t_employee.emp_name);
I get this weirdness: PLS-00382: expression is of wrong type
Same with SELECT. It seems to me that we cannot use associative arrays directly in SQL statements.
An associative array collection is a PL/SQL only data type; it cannot be used in SQL statements.
From the Oracle documentation:
Table 5-1 PL/SQL: Collection Types
Collection Type
Number of Elements
Index Type
Dense or Sparse
Uninitialized Status
Where Defined
Can Be ADT Attribute Data Type
Associative array (or index-by table)
Unspecified
String or PLS_INTEGER
Either
Empty
In PL/SQL block or package
No
VARRAY (variable-size array)
Specified
Integer
Always dense
Null
In PL/SQL block or package or at schema level
Only if defined at schema level
Nested table
Unspecified
Integer
Starts dense, can become sparse
Null
In PL/SQL block or package or at schema level
Only if defined at schema level
...
An associative array (formerly called PL/SQL table or index-by table) is a set of key-value pairs.
...
Unlike a database table, an associative array:
Does not need disk space or network operations
Cannot be manipulated with DML statements
VARRAY and nested table collections can be used in SQL statements.
Related
Why to I asked this question?
I have a table which as key that have a lot of field. Every time, I'm making a jointure, I miss a field. Therefore I have defined a pipelined function that take the key as an argument so that I am sure that I get only one element when I'm doing a jointure.
But the query take more time now. The table a has an index on some fields but not the table type used by pipelined function. I would like to know if it is possible to created a index on some fields of the table%rowtype
code:
create table a ( a1 integer);
create package p_a
as
type t_a iS TABLE of a%ROWTYPE;
function f(i_a1 integer) return t_a pipelined;
end;
CREATE PACKAGE BODY p_a
AS
CURSOR c_A (i_a1 INTEGER)
RETURN a%ROWTYPE
IS
SELECT t.*
FROM a t
WHERE t.a1 = i_a1;
FUNCTION f (i_a1 INTEGER)
RETURN t_a
PIPELINED
IS
BEGIN
FOR c IN c_a (i_a1)
LOOP
PIPE ROW (c);
END LOOP;
END;
END;
with b as( select 1 b1 from dual) select * from b cross apply (table(p_a.f(b.b1)));
the question
I've tried to index the type table by a field of a table like this
create table a ( a1 integer);
create package p_a2
as
type t_a iS TABLE of a%ROWTYPE index by a.a1%type;
function f(i_a1 integer) return t_a pipelined;
end;
PLS-00315: Implementation restriction: unsupported table index type
Is what I want to do possible. If not how to solve the performance problems mentioned in the introduction?
code
A TYPE is NOT a table and cannot be indexed.
When you do:
create package p_a
as
type t_a iS TABLE of a%ROWTYPE;
end;
/
You are defining a type and the type is a collection data type; an instance of that type is NOT a physical table and but is more like an in-memory array.
When you create a PIPELINED function:
function f(i_a1 integer) return t_a pipelined;
It does NOT return a table; it returns the collection data type.
When you do:
type t_a iS TABLE of a%ROWTYPE index by a.a1%type;
You are NOT creating an index on a table; you are changing to a different collection data type that is an associative array (like a JavaScript object or a Python dictionary) that stores key-value pairs.
An associative array is a PL/SQL data type and (with limited exceptions in later versions for insert, update and delete statements) cannot be used in SQL statements.
When you do:
SELECT * FROM TABLE(SYS.ODCIVARCHAR2LIST('a', 'b', 'c'));
or:
SELECT * FROM TABLE(p_a.f(1));
Then you are passing a collection data type to an SQL statement and the table collection expression TABLE() is treating the collection expression as if it was a table. It is still NOT a table.
If you want to use an index on the table then use the table (without a cursor or a pipeline function):
WITH b (b1) AS (
SELECT 1 FROM DUAL
)
SELECT *
FROM b
CROSS APPLY (
SELECT a.*
FROM a
WHERE a.a1 = b.b1;
);
I think the first line of your question says it all: "key that have a lot of field". If I understand correctly, the table has a primary key that consists of a large number of columns and because of that writing queries becomes a challenge.
It sounds like you're trying to do something pretty complex that should not be an issue at all.
Take a step back and ask yourself - does this need to be the primary key of the table ? Or can you use a surrogate key (identity column, sequence), use that as the primary key and just create a unique index on the set of field that currently make up the primary key. It will (1) simplify your data model and (2) make writing the queries a lot easier.
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.
I have a table that has one of its columns as a nested table.
I want to copy data of this table to another. How do we frame the INSERT clause for this seems challenging:
Consider. The field that is a nested table is phone_list whose type is a user defined type "TBL_PHONE_EXTN" which is a table of "typ_phone_extn".
CREATE OR REPLACE TYPE typ_phone_extn AS OBJECT
(phone_number VARCHAR2 (20), extension VARCHAR2 (10));
/
CREATE OR REPLACE TYPE tbl_phone_extn AS TABLE OF typ_phone_extn;
/
Obviously below fails: (with a ORA-00904: : invalid identifier)
INSERT INTO sch2.sub_pat_address (
pat_address_id,
pat_id,
**phone_list,**
last_updated_by
)
SELECT pat_address_id,
pat_id,
**phone_list,**
last_updated_by
FROM sch1.sub_pat_address ;
So i try:
SELECT pat_address_id,
pat_id,
**tbl_phone_extn(typ_phone_extn (phone_number,extension)),**
last_updated_by
FROM sch1.sub_pat_address, **table(phone_list)** ;
What this does is unnest the nested table. So i end up with more records than i want - meaning if a specific pat_address_id had a phone_list of 5 phone,extn combination this gives me 5 records that i cannot and should not be inserting.
So question is, how to keep the nest (nested table column) as-is and insert into the new table? Well, CTAS may be one option but that requires a whole new table instead of a INSERT. Any help will be greatly appreciated.
You can use the COLLECT function to reassemble the unnested elements into a nested table, casting that back to your actual collection type:
SELECT pat_address_id,
pat_id,
cast(collect(typ_phone_extn(phone_number,extension)) as tbl_phone_extn),
last_updated_by
FROM sch1.sub_pat_address, table(phone_list)
GROUP BY pat_address_id, pat_id, last_updated_by;
And you can then use that for your insert, obviously.
The only reason I can see you'd have a problem with your original simple insert would be if each schema had its own types and their tables were built using their own types. But then you'd get ORA-00932: inconsistent datatypes or ORA-01031: insufficient privileges rather than ORA-00904.
Even if you have privileges on the types across the schemas, Oracle UDTs have to be the exact same type - it's not enough for them to be constructed identically. If they are different entries in ALL_OBJECTS then they are not interchangeable.
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.
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.