Error in Initializing oracle collection - sql

I have following self contained code which gives following error. I don't seem to find where do I have to initialize the collection.
create or replace type address_type is object
(
address_line varchar2(100),
city varchar2(100),
state varchar2(100)
)
/
create or replace type emp_rec_type is object
(
emp_name varchar2(100),
addr_rec address_type
)
/
create or replace type emp_arr is table of emp_rec_type
/
create table employees
(
emp_name varchar2(100),
addr_rec address_type
)
/
insert into employees values ( 'dave', address_type ( '30 br','br','nj'));
commit;
-- Create a function to return an array
create or replace function get_emp_rec ( p_name in varchar2 )
return
emp_arr
is
i integer;
l_arr emp_arr := emp_arr();
begin
i := 1;
l_arr.extend;
l_arr(l_arr.last).emp_name := 'a';
l_arr(l_arr.last).addr_rec := address_type ( 'a','b','c');
return l_arr;
end;
/
select emp_name from table ( get_emp_rec( 'dave' ))
* ERROR at line 1: ORA-06530: Reference to uninitialized composite ORA-06512: at "DBADMIN.GET_EMP_REC", line
22

You have to initialize emp_rec_type in the collection.
These two line
l_arr(l_arr.last).emp_name := 'a';
l_arr(l_arr.last).addr_rec := address_type ( 'a','b','c');
replace with this linie
l_arr(l_arr.last) := emp_rec_type('a', address_type ( 'a','b','c'));

Related

Procedure to insert data if not exist pl sql

I have a table with these columns (id,first_name,birth). I want to create a procedure that insert a new customer only if the id inserted doesn't exist in the table. If it already exist, then don't insert it. This is my code so far, but I got an error 'line 3 sql statement ignored'. Any idea? I need to use procedure and pl sql in oracle. Thanks!
CREATE OR REPLACE PROCEDURE add_emp(v_id IN int,
v_name IN varchar2,
v_bday IN date) IS
BEGIN
INSERT INTO Employees
(Id, First_name, Birth)
SELECT *
FROM (SELECT v_id, v_name, v_bday) AS tmp
WHERE NOT EXISTS (SELECT Id FROM Employees WHERE Id = v_id);
END;
/
DECLARE
m_id int := 3;
m_name varchar2 := 'John';
m_bday date := '16-Dec-1990';
BEGIN
add_cust(m_id, m_name, m_bday);
END;
/
Your procedure has some syntax issue which is fixed in following code:
CREATE OR REPLACE PROCEDURE ADD_EMP (
V_ID IN INT,
V_NAME IN VARCHAR2,
V_BDAY IN DATE
) IS
BEGIN
INSERT INTO EMPLOYEES (
ID,
FIRST_NAME,
BIRTH
)
SELECT V_ID,
V_NAME,
V_BDAY
FROM DUAL -- FROM clause was missing
WHERE NOT EXISTS (
SELECT ID
FROM EMPLOYEES
WHERE ID = V_ID
);
END;
/
Also, Your calling PL/SQL block has some issues which are corrected in the following code:
DECLARE
M_ID INT := 3;
M_NAME VARCHAR2(10) := 'John'; -- varchar2 must be declared with size
M_BDAY DATE := DATE '1990-12-16'; -- added date literal to convert string to date
BEGIN
ADD_CUST(M_ID, M_NAME, M_BDAY);
END;
/
In Oracle SELECT does not work without a FROM clause (other DBMS products are different). So you need to provide a table; you can use DUAL, which is a dummy table provided by Oracle which is guaranteed to return one row.
INSERT INTO Employees(Id,First_name,Birth)
SELECT v_id, v_name, v_bday
from dual
WHERE NOT EXISTS (
SELECT Id FROM Employees WHERE Id = v_id
);
Your INSERT statement would work with a slight change
CREATE OR REPLACE PROCEDURE add_emp(v_id Employees.Id%type,
v_name Employees.First_name%type,
v_bday Employees.Birth%type) IS
BEGIN
INSERT INTO Employees
(Id, First_name, Birth)
SELECT v_id, v_name, v_bday
FROM dual
WHERE NOT EXISTS (SELECT * FROM Employees WHERE Id = v_id);
END;
/
You can replace INSERT statement with MERGE as an alternative DML such as
MERGE INTO Employees e1
USING
(SELECT v_id AS id, v_name AS First_name, v_bday AS birth
FROM dual) e2
ON ( e1.id = e2.id )
WHEN MATCHED THEN UPDATE SET e1.First_name = e2.First_name,
e1.Birth = e2.Birth -- If you need to change for the matching case
WHEN NOT MATCHED THEN INSERT( e1.id, e1.First_name, e1.birth )
VALUES( e2.id, e2.First_name, e2.birth );

Copy data for another table using Collections Types index-by table (associated with a table)

I don't know how to create a function with collections type. I am familiar with SQL, but I do not know that particular function. Here is what I tried:
Create or Replace Function COPY_EMPLOYEES_WITH_RT(
Begin
insert into jjj_employees ( select * from employees)
I want to create a function COPY EMPLOYEES_WITH_RT and copy data from the table EMPLOYEES to jjj_EMPLOYEES using Collections Types index-by table (associated with a table).
CREATE OR REPLACE FUNCTION COPY_EMPLOYEES_WITH_RT(O_ERROR_MESSAGE OUT VARCHAR)
--DECLARE type
TYPE EMP_RECORD IS TABLE OF employees%ROWTYPE;
l_emp EMP_RECORD;
-- define cursor
CURSOR c_employees IS
SELECT *
FROM employees;
--
BEGIN
--
open c_employees;
loop
fetch c_employees
bulk collect into l_emp limit 1000;
exit when l_emp.count = 0;
-- Process contents of collection here.
Insert into jrf_employees values l_emp;
END LOOP;
CLOSE c_employees;
EXCEPTION
--
WHEN OTHERS THEN
O_error_message := SQLERRM;
END;
/
it's like something like that, i just didn't know, what is wrong
If you are using object-relational tables and particularly want to use PL/SQL associative arrays (index-by table types) then you can create your tables as:
CREATE TYPE employee_t IS OBJECT(
id NUMBER(10,0),
first_name VARCHAR2(20),
last_name VARCHAR2(20)
);
CREATE TABLE employees OF employee_t (
id PRIMARY KEY
);
CREATE TABLE jjj_employees OF employee_t (
id PRIMARY KEY
);
With the sample data:
INSERT INTO employees
SELECT 1, 'One', 'Uno' FROM DUAL UNION ALL
SELECT 2, 'Two', 'Dos' FROM DUAL UNION ALL
SELECT 3, 'Three', 'Tres' FROM DUAL;
And the function as:
CREATE FUNCTION COPY_EMPLOYEES_WITH_RT
RETURN NUMBER
IS
TYPE employee_a IS TABLE OF employee_t INDEX BY PLS_INTEGER;
emps employee_a;
i PLS_INTEGER;
BEGIN
FOR r IN ( SELECT VALUE(e) AS employee FROM employees e )
LOOP
emps( r.employee.id ) := r.employee;
END LOOP;
i := emps.FIRST;
WHILE i IS NOT NULL LOOP
INSERT INTO jjj_employees VALUES ( emps(i) );
i := emps.NEXT(i);
END LOOP;
RETURN 1;
END;
/
Then you can run the function using:
BEGIN
DBMS_OUTPUT.PUT_LINE( COPY_EMPLOYEES_WITH_RT() );
END;
/
And the table is copied as:
SELECT * FROM jjj_employees;
Outputs:
ID | FIRST_NAME | LAST_NAME
-: | :--------- | :--------
1 | One | Uno
2 | Two | Dos
3 | Three | Tres
db<>fiddle here
Update
Since you appear to want to use a collection and not an associative array:
CREATE OR REPLACE FUNCTION COPY_EMPLOYEES_WITH_RT
RETURN VARCHAR2
IS
--DECLARE type
TYPE EMP_RECORD IS TABLE OF employees%ROWTYPE;
l_emp EMP_RECORD;
-- DECLARE cursor
CURSOR c_employees IS
SELECT *
FROM employees;
BEGIN
OPEN c_employees;
LOOP
FETCH c_employees
BULK COLLECT INTO l_emp LIMIT 1000;
EXIT WHEN l_emp.COUNT = 0;
-- Process contents of collection here.
FORALL i IN 1 .. l_emp.COUNT
INSERT INTO jjj_employees VALUES l_emp(i);
END LOOP;
CLOSE c_employees;
RETURN NULL;
EXCEPTION
WHEN OTHERS THEN
RETURN SQLERRM;
END;
/
db<>fiddle

Select object inside an object in PL/SQL

I have the following code:
SET SERVEROUTPUT ON
CREATE OR REPLACE TYPE work_t AS OBJECT(
company VARCHAR2(50),
salary NUMBER(5)
);
/
CREATE OR REPLACE TYPE person_t AS OBJECT(
personnum NUMBER(5),
personname VARCHAR2(20),
personwork work_t
);
/
CREATE TABLE people OF person_t(
PRIMARY KEY (personnum)
);
INSERT INTO people VALUES(12, 'George', work_t('Google',75500));
If I want to print personname:
DECLARE
per1 person_t;
BEGIN
SELECT VALUE(e) INTO per1 FROM people e WHERE e.personnum = 12;
dbms_output.put_line(per1.personname);
END;
/
Now I want to print the company (and update it in different blocks) but I don't know how to do it.
Thanks.
Just add
dbms_output.put_line(per1.personwork.company);
in
DECLARE
per1 person_t;
BEGIN
SELECT VALUE(e) INTO per1 FROM people e WHERE e.personnum = 12;
dbms_output.put_line(per1.personname);
dbms_output.put_line(per1.personwork.company);
END;
/

How to select into varray in Oracle

I'm trying to save a set of ids in array:
create or replace type vrray_4 as varray(4) of varchar2(10);
create or replace procedure get_acl_owner_exchange (id in varchar2 ) is
path_count int;
acl_type out vrray_4;
begin
select owner_id into acl_type from kmc_acl_perm
where permission_name = 'read.'
or permission_name = 'write.'
or permission_name = 'delete.';
end get_acl_owner_exchange;
I'm getting the error:
LINE/COL ERROR
-------- --------------------------------------------------------------------------------
3/17 PLS-00103: Encountered the symbol "VRRAY_4" when expecting one of the following:
:= . ( # % ; not null range default character
The symbol ":=" was substituted for "VRRAY_4" to continue.
You have the OUT parameter in the wrong place and you need to use BULK COLLECT INTO rather than just INTO:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE KMC_ACL_PERM(
owner_id VARCHAR2(10),
permission_name VARCHAR2(10)
)
/
CREATE OR REPLACE TYPE vrray_4 AS VARRAY(4) OF VARCHAR2(10)
/
create or replace PROCEDURE GET_ACL_OWNER_EXCHANGE (
id IN VARCHAR2,
acl_type OUT VRRAY_4
) IS
BEGIN
SELECT owner_id
bulk collect into acl_type
from KMC_ACL_PERM
where permission_name IN ( 'read.', 'write.', 'delete.' );
END GET_ACL_OWNER_EXCHANGE;
/
Does this work for you:
CREATE OR REPLACE TYPE vrray_4 AS VARRAY(4) OF VARCHAR2(10);
/
CREATE TABLE kmc_acl_perm (
owner_id VARCHAR2(10),
permission_name VARCHAR2(10)
);
CREATE OR REPLACE PROCEDURE get_acl_owner_exchange (id IN VARCHAR2) IS
path_count NUMBER;
acl_type vrray_4;
BEGIN
SELECT owner_id BULK COLLECT INTO acl_type
FROM kmc_acl_perm
WHERE permission_name IN ('read.', 'write.', 'delete.');
END get_acl_owner_exchange;
/

Getting difference between two dates from 2 REF'd columns

I'm new to SQL and I'm trying to get the date between 2 values between two REF'd column values.
I currently have the tables set up like below:
CREATE OR REPLACE TYPE Person_Type AS OBJECT
(PersonId NUMBER,
DateBorn DATE)
CREATE OR REPLACE TYPE Movie_Type AS OBJECT
(MovieId NUMBER,
ReleaseDate DATE)
CREATE OR REPLACE TYPE Role_Type AS OBJECT
(PersonId REF Person_Type,
MovieId REF Movie_Type,
Name VARCHAR2(40),
MAP MEMBER FUNCTION Actor_Age RETURN NUMBER)
The data is inserted like this:
INSERT INTO Person_Table
VALUES (10000, '11-NOV-1974')
INSERT INTO Movie_Table
VALUES(1000000, '19-DEC-1997')
INSERT INTO Role_Table
VALUES((SELECT REF(a) FROM Person_Table a WHERE a.PersonId = 10000),
(SELECT REF(b) FROM Movie_Table b WHERE b.MovieId = 1000000),
'Some Person')
I'm trying to get the method Actor_Age to return the difference between the day someone is born and the date the movie got released. I've tried the below but it won't compile..
CREATE OR REPLACE TYPE BODY Role_Type
AS
MAP MEMBER FUNCTION Actor_Age
RETURN NUMBER
IS
BEGIN
RETURN (MONTHS_BETWEEN((SELECT g.PersonId.DateBorn FROM Role_Table g),
(SELECT f.MovieId.Releasedate FROM Role_Table f)/12));
END;
END;
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE OR REPLACE TYPE Person_Type AS OBJECT
(PersonId NUMBER,
DateBorn DATE)
/
CREATE OR REPLACE TYPE Movie_Type AS OBJECT
(MovieId NUMBER,
ReleaseDate DATE)
/
CREATE OR REPLACE TYPE Role_Type AS OBJECT
(PersonId REF Person_Type,
MovieId REF Movie_Type,
Name VARCHAR2(40),
MAP MEMBER FUNCTION Actor_Age RETURN NUMBER)
/
CREATE OR REPLACE TYPE BODY Role_Type
AS
MAP MEMBER FUNCTION Actor_Age
RETURN NUMBER
IS
p_age NUMBER;
BEGIN
SELECT MONTHS_BETWEEN(
DEREF( MovieID ).ReleaseDate,
DEREF( PersonID ).DateBorn
) / 12
INTO p_age
FROM DUAL;
RETURN p_age;
END;
END;
/
CREATE TABLE Person_Table OF Person_Type
/
CREATE TABLE Movie_Table OF Movie_Type
/
CREATE TABLE Role_Table OF Role_Type
/
INSERT INTO Person_Table VALUES ( 10000, DATE '1970-01-01' )
/
INSERT INTO Movie_Table VALUES ( 1000000, DATE '2000-01-01' )
/
INSERT INTO Role_Table VALUES(
(SELECT REF(a) FROM Person_Table a WHERE a.PersonId = 10000),
(SELECT REF(b) FROM Movie_Table b WHERE b.MovieId = 1000000),
'Some Person'
)
/
Query 1:
SELECT Name,
r.Actor_Age()
FROM Role_table r
Results:
| NAME | R.ACTOR_AGE() |
|-------------|---------------|
| Some Person | 30 |
In pl sql you have to use construction select into
CREATE OR REPLACE TYPE BODY Role_Type
AS
MAP MEMBER FUNCTION Actor_Age
RETURN NUMBER
IS
v_date_born date ;
v_realse_date date ;
BEGIN
SELECT deref(self.PersonId).DateBorn into v_date_born FROM dual;
SELECT deref(self.MovieId).Releasedate into v_realse_date FROM dual;
RETURN (MONTHS_BETWEEN(v_date_born,v_realse_date)/12);
END;
END;