I'm currently struggling with a PL/SQL problem, and Ididn't find a clear answer yet. The problem is, I have an object of type T, which can contain a list of objects of same type T.
Let's say I have a type TPerson. TPerson is defined by a name, and a list of children. So I have a grandfather, his two sons, and the first one has 2 daughters.
In PL/SQL, after some researches, I did this :
CREATE OR REPLACE TYPE TPerson IS OBJECT
(
Name VARCHAR2(30),
Children REF TPersonList,
constructor function TPerson(name VARCHAR2) return self as result
);
CREATE OR REPLACE TYPE body TPerson as
constructor function TPerson(aname VARCHAR2) return self as result is
begin
Name := aname;
Children := TPersonList();
return;
end;
end;
CREATE OR REPLACE TYPE TPersonList IS TABLE OF REF TPerson';
Everything runs with no exception. But the TPerson type does not compile correctly, and I have this compilation error:
pls-00532 target of ref must be a complete or incomplete object type
It's the first time I use this REF thing. I'm not even sure I'm using it correctly. In my opinion, it's not a really good way to do that (the 'Children' thing), but I don't have the choice. So if someone could explain me a proper way to achieve that, that would help me a lot...
I'm not sure if an object type can have a member of collection of the same type as one have to be able to define the collection before the object type and object type before the collection. To break the circular dependency one needs to introduce a level of indirection in the following fashion (warning non-tested pseudocode follows):
-- supertype
create type object_t is object;
create type object_list_t is table of ref object_t;
-- subtype
create type person_t under object_t (
name varchar2(20)
,children object_list_t
);
And then in the implementation of person_t you use treat to narrow down from supertype to subtype.
Another idea is to have the relations of the objects in a specific relation table. See the example below.
First create a simple person-type:
create type person_t is object (
name varchar2(20)
);
/
show errors
create table persons of person_t;
insert into persons values(person_t('Grandfather'));
insert into persons values(person_t('Father'));
insert into persons values(person_t('Mother'));
insert into persons values(person_t('Son 1'));
insert into persons values(person_t('Daughter 1'));
insert into persons values(person_t('Son 2'));
Second create a table for parent-child relations:
create table x (
parent ref person_t
,child ref person_t
);
-- build a family tree
insert into x values(
(select ref(p) from persons p where name = 'Grandfather')
,(select ref(p) from persons p where name = 'Father')
);
insert into x values(
(select ref(p) from persons p where name = 'Father')
,(select ref(p) from persons p where name = 'Son 1')
);
insert into x values(
(select ref(p) from persons p where name = 'Father')
,(select ref(p) from persons p where name = 'Son 2')
);
insert into x values(
(select ref(p) from persons p where name = 'Father')
,(select ref(p) from persons p where name = 'Daughter 1')
);
insert into x values(
(select ref(p) from persons p where name = 'Mother')
,(select ref(p) from persons p where name = 'Son 1')
);
insert into x values(
(select ref(p) from persons p where name = 'Mother')
,(select ref(p) from persons p where name = 'Son 2')
);
insert into x values(
(select ref(p) from persons p where name = 'Mother')
,(select ref(p) from persons p where name = 'Daughter 1')
);
Now you can traverse the family tree with a good old SQL.
column parent format a30
column child format a30
select deref(child) as child from x where deref(parent).name = 'Father';
CHILD(NAME)
------------------------------
PERSON_T('Son 1')
PERSON_T('Son 2')
PERSON_T('Daughter 1')
select deref(parent) as parent, deref(child) as child
from x
start with deref(parent).name = 'Grandfather'
connect by prior child = parent
;
PARENT(NAME) CHILD(NAME)
------------------------------ ------------------------------
PERSON_T('Grandfather') PERSON_T('Father')
PERSON_T('Father') PERSON_T('Son 1')
PERSON_T('Father') PERSON_T('Son 2')
PERSON_T('Father') PERSON_T('Daughter 1')
The ref and deref functions are explained in Database Object-Relational Developer's Guide. That's a document you should be familiar with if you're working with Oracle object types.
Related
TABLE A : name empid activeind
TABLE B : name empid activeind
update table A based on table B comparing empid from both the table using Oracle stored procedure
Here's one option.
create or replace procedure p_upd is
begin
merge into a
using b
on (b.empid = a.empid)
when matched then update set a.name = b.name,
a.activeind = b.activeind;
end;
/
[EDIT] Based on your comment: you used wrong syntax. UPDATE should be
UPDATE a
SET (a.name, a.activeind) =
(SELECT b.name, b.activeind
FROM b
WHERE b.empid = a.empid)
WHERE EXISTS
(SELECT NULL
FROM b
WHERE b.empid = a.empid);
Why exists? Without it, you'd set name and activeid values to null for all a.empid rows that don't exist in b table.
I have these three object
create or replace
type type_client
( num int ,
username varchar(30),
balance int,
ta table_achat,
ref_admin ref type_admin,
member function get_prix_achat_total return int );
create or replace
type table_achat as table of achat ;
create or replace
type achat as object ( num_item int , qte int
);
create table table_client OF type_client ;
suppose in an entry of table_client .. we have a nested table like this :
(num_item,qte) : (1 , 5),(2 , 3)
what I want is the nested table be like this (for example):
(num_item,qte) : (1 , 5),(2 , 3)(3 , 44)
What I mean is, how to add a new line to an already created nested table while keeping existing entries? ..
We can use the MULTISET UNION operator to create a new set from two sets. In your case one of those sets is your existing set and the second set is the set of new entries.
Here is a demo based on a simplified version of your set-up:
declare
nt table_achat;
begin
nt := table_achat(achat(1 , 5),achat(2 , 3));
dbms_output.put_line(nt.count());
nt := nt multiset union table_achat(achat(3 , 44));
dbms_output.put_line(nt.count());
end;
/
Given a table T42 with a column COL_NT which is a nested table of your table_achat type you could insert a new entry in the nested table like this:
insert into the
(select col_nt from t42 where id = 1)
values (achat(3,44));
irrelevant from the question, which i couldn't and didn't try to understand, you can not combine insert + select + values statements as you did before. Perhaps you may prefer among below ones :
insert into the -- if table has one string column
(select ta from table_client where username=user);
OR
insert into the -- if table has two numeric columns
values (3,44);
I Created a nested table
I Want to bulk ref cursor (C_get_cards) into the nested table created.
I Want to use nested table as one of the column in table STAT_QUERIES.
How do I combine all the three steps in a single procedure? Please correct me if my approach is wrong.
--Nested Table---
CREATE OR REPLACE TYPE T_card_details AS TABLE OF VARCHAR2(20) -- define type
/
CREATE TYPE Query AS OBJECT ( -- create object
CARD_QUERY T_card_details) -- declare nested table as attribute
/
CREATE TABLE STAT_QUERIES (
OBJECTTYPE VARCHAR2(50),
CATEGORY VARCHAR2(100),
CARD_QUERY T_card_details)
NESTED TABLE CARD_QUERY STORE AS CARD_QUERY_TAB;
select * FROM STAT_QUERIES
-- The cursor bulk collect into T_CARD details
CREATE OR REPLACE PROCEDURE NRMSP_INVENTORYSTATS IS
CURSOR C_get_cards
IS
SELECT
nt.name nt_name
,nd.name nd_name
,ct.name ct_name
,ps.name ps_name
,count(c.cardid)
FROM cardtype ct, card c, node n, nodetype nt, nodedef nd, provisionstatus ps
WHERE ct.name in ('SRA AMP', 'XLA AMP', 'SAM', 'ESAM')
AND ct.cardtypeid = c.card2cardtype
AND c.card2node = n.nodeid
AND n.node2nodetype = nt.nodetypeid
AND n.node2nodedef = nd.nodedefid
AND c.card2provisionstatus = ps.provisionstatusid
GROUP by nt.name, nd.name, ct.name, ps.name
;
TYPE cards_TAB_TYPE IS TABLE OF C_get_cards%ROWTYPE INDEX BY BINARY_INTEGER ;
T_card_details cards_TAB_TYPE ;
BEGIN
OPEN C_get_cards ;
FETCH C_get_cards
BULK COLLECT INTO T_card_details
LIMIT 250
;
END;
/
---INSERT INTO STAT_QUERIES---
INSERT INTO STAT_QUERIES
VALUES('CARD', 'Cards from Other Projects 2014', 'CARD_QUERY');
I have a custom type:
CREATE OR REPLACE TYPE my_type IS OBJECT
(
field_one number,
field_two varchar2(10),
);
and a nested table of this type:
CREATE TYPE my_type_nt AS TABLE OF my_type;
and another custom type that contains this nested table:
CREATE OR REPLACE TYPE parent IS OBJECT
(
field_one number,
field_two my_type_nt,
);
I have to query a table for parent objects and then depending on the PK for that record, query another table for the all my_type objects for that parent.
so something like:
-- i know following code is wrong
select * into parent1
from table1
where table1.column1 = something;
and then:
for every record in parent1
populate it's my_type_nt
for every record in my_type_nt
do something
end loop
end loop
My questions are:
1. Is my approach wrong? Should I be joining the two tables instead?
2. I will have to populate the parent types anyway (this stored proc feeds into another stored proc which has the parent type as an input. What is an efficient way to select data into the parent type?
We can populate a nested table with a sub-query using CAST() and MULTISET()
select parent(p.id,
cast(multiset(select c.c_id
, c.c_name
from c
where c.p_id = p.id)
as my_type_nt)
)
into local_par
from p
where p.id = param_id;
Whether this is the best approach for you depends on what actual processing you have obfuscated in your pseudo-code: populate it's my_type_nt ... do something
"What is an efficient way to select data into the parent type?"
If you want to process multiple parents you should create a nested table type for it too:
CREATE TYPE parent_nt AS TABLE OF parent_t;
/
Then you can populate it with the BULK COLLECT syntax:
select parent(p.id,
cast(multiset(select c.c_id
, c.c_name
from c
where c.p_id = p.id)
as my_type_nt)
)
bulk collect into local_par_nt
from p
where p.id <= param_id;
Then loop through the collection to process each parent
for idx in 1 .. local_par_nt.count()
loop
do_something;
I'm having problems deleting specific record from the table (ORACLE DB).
I have a table with a nested table inside of it.
Table structure looks like this: where ML - nested table
Name, City, ML(Brand, Model, ID, Year, Price)
What I need to do is delete specific record with ID of 'L201'.
What I have tried so far:
SELECT B.ID FROM TABLE Dock A, Table(A.ML) B;
This is working giving me all the ID's.
Output:
ID
____
B201
S196
L201
This is not working when trying to delete the record:
DELETE FROM Dock
(SELECT B.ID FROM Dock A, Table(A.ML) B) C
WHERE C.ID = 'L201';
Getting error:
Line 2: SQL command not properly ended;
DELETE FROM TABLE
(SELECT D.ML FROM Dock D) E
WHERE E.ID = 'L201';
Throws an error:
single-row subquery returns more than one row
Maybe this one:
DELETE FROM
(SELECT A.Name, A.City, d.Brand, d.Model, d.ID, d.Year, d.Price
FROM Dock A, TABLE(ML) d)
WHERE ID = 'L201';
Update:
Another trial before we gonna make it more advanced:
DELETE FROM Dock
WHERE ROWID =ANY (SELECT a.ROWID FROM Dock a, TABLE(ML) b WHERE b.ID = 'L201');
Update 2:
If you prefer it more object-oriented, this one should work as well. At least I did not get any error.
CREATE OR REPLACE TYPE ML_TYPE AS OBJECT (
brand VARCHAR2(100),
ID VARCHAR2(20),
MODEL VARCHAR2(20),
YEAR NUMBER,
Price NUMBER,
MAP MEMBER FUNCTION getID RETURN VARCHAR2,
CONSTRUCTOR FUNCTION ML_TYPE(ID IN VARCHAR2) RETURN SELF AS RESULT);
CREATE OR REPLACE TYPE BODY ML_TYPE IS
CONSTRUCTOR FUNCTION ML_TYPE(ID IN VARCHAR2) RETURN SELF AS RESULT IS
-- Constructor to create dummy ML-Object which contains just an ID,
-- used for comparison
BEGIN
SELF.ID := ID;
RETURN;
END ML_TYPE;
MAP MEMBER FUNCTION getID RETURN VARCHAR2 IS
BEGIN
RETURN SELF.ID;
END getID;
END;
/
CREATE OR REPLACE TYPE ML_TABLE_TYPE IS TABLE OF ML_TYPE;
CREATE TABLE Dock (Name VARCHAR2(20), City VARCHAR2(20), ML ML_TABLE_TYPE)
NESTED TABLE ML STORE AS ML_NT;
insert into Dock values ('A', 'NY', ML_TABLE_TYPE(
ML_TYPE('brand1','L301','Model 2',2013, 1000),
ML_TYPE('brand2','L101','Model 3',2013, 1000)));
insert into Dock values ('B', 'NY', ML_TABLE_TYPE(
ML_TYPE('brand3','K301','Model 4',2014, 3000),
ML_TYPE('brand4','K101','Model 5',2014, 3000)));
insert into Dock values ('A', 'NY', ML_TABLE_TYPE(
ML_TYPE('brand5','K301','Model 8',2012, 2000),
ML_TYPE('brand6','L201','Model 9',2012, 2000)));
DELETE FROM Dock WHERE ML_TYPE('L201') MEMBER OF ML;
After reading some literature I think I found the correct way to do this.
By defining a mapping-function for your object-type you can compare two nested tables directly.
Example:
-- creating the custom object type
create or replace type ml_type as object (
brand varchar2(100),
id varchar2(20),
map member function sort_key return varchar2
);
-- creating the object type body and defining the map-function
create or replace type body ml_type as
map member function sort_key return varchar2 is
begin
return self.brand || '|' || self.id;
end;
end;
/
-- creating the nested table of custom type
create or replace type ml_tab as table of ml_type;
-- deleting from your table by comparing the nested-table elements
delete from dock where ml = (select ml from dock a, table(a.ml) b where b.id = 'L201');
In this example the map-functions is just returning the concatenated version of brand and id, but you can define it to what you want/need.