View to return the root node given any children (in Oracle) - view

We manufacture equipment, and we give every unit we produce a serial number (s/n). We have a parent/child table which we use to establish the relationship between the top-level assemblies and its children. I am providing a sample layout of such table below.
CREATE TABLE PAR_CHI
(PARENT_SN VARCHAR2(50 BYTE) NOT NULL,
CHILD_SN VARCHAR2(50 BYTE) NOT NULL,
PRIMARY KEY (PARENT_SN, CHILD_SN));
I have some data that can be loaded in this table, found below.
insert into PAR_CHI values (' ', '135887957');
insert into PAR_CHI values ('135887957', '135562597');
insert into PAR_CHI values ('135562597', '135424162');
insert into PAR_CHI values ('135424162', '135422839');
insert into PAR_CHI values ('135887957', '135623876');
insert into PAR_CHI values ('135623876', '135519894');
insert into PAR_CHI values ('135519894', '135517981');
insert into PAR_CHI values ('135887957', '136526805');
I have no experience with hierarchical queries, so I have done some online research about it and how it works. So far I have learned that a query like the one below can be used to find the 'tree' (or hierarchy) for a specific top-level assembly to return its full genealogy.
select PARENT_SN, CHILD_SN, level, connect_by_root(CHILD_SN) as root, sys_connect_by_path(CHILD_SN, '/') as path, connect_by_isleaf
from PAR_CHI
start with CHILD_SN = '135887957'
connect by PARENT_SN = prior CHILD_SN;
I have also learned that a query like the one below could be used to traverse the hierarchy in opposite direction (given a component s/n, to find its parent and all the way to the top-level assembly).
select PARENT_SN, CHILD_SN, level, connect_by_root(CHILD_SN) as root, sys_connect_by_path(CHILD_SN, '/') as path, connect_by_isleaf
from PAR_CHI
start with CHILD_SN = '135517981'
connect by CHILD_SN = prior PARENT_SN;
There is a way to get the 'real' parent for a specific serial number if 'connect_by_isleaf' is used, as shown below.
select PARENT_SN, CHILD_SN, level, connect_by_root(CHILD_SN) as root, sys_connect_by_path(CHILD_SN, '/') as path, connect_by_isleaf
from PAR_CHI
where connect_by_isleaf = 1
start with CHILD_SN = '135517981'
connect by CHILD_SN = prior PARENT_SN;
I have a specific need to create a view or a table (preferably a view), but this object, whatever it is, cannot call another object to build a dataset (let me explain: for instance, in SSRS Reports we can call a store procedure that would build a dataset given specific search criteria, and then the report would display the records in the dataset). This object must be ready for the user to perform a straight 'select statement' on it, and get the desired results.
The object should return one row every time, showing the top-level assembly s/n (root) given any s/n the user is searching for.
Let me provide some examples of expected results given search data.
If the user enters s/n '135887957', the dataset would return one row showing the values below:
Root S/N Searched S/N
135887957
This record corresponds to the top-level assembly itself, and the 'Root s/n' column is blank (not null).
If the user enters s/n '135562597', the dataset would return one row showing the values below.
Root S/N Searched S/N 135887957 135562597
This record shows that the entered s/n '135562597' has root s/n '135887957'. Since this is a level 2 component, the result returns the top-level assembly.
If the user enters s/n '135519894', the dataset would return one row showing the values below.
Root S/N Searched S/N 135887957 135519894
This record shows that the entered s/n '135519894' has root s/n '135887957' (strictly speaking, the 'parent' of this level 3 component is '135623876', but we need to get the top-level assembly, not the 'real' parent).
If the user enters s/n '135422839', the dataset should return one row showing the values below.
Root S/N Searched S/N 135887957 135422839
This record shows that the entered s/n '135422839' has root s/n '135887957' (strictly speaking, the 'parent' of this level 4 component is '135424162', but we need to get the top-level assembly, not the 'real' parent).
As you can see, we do not really need any other data like the level, or the 'real' parent' or the path, but if we can add it, it would be helpful; just the s/n that corresponds to the top-level assembly. One big difference between the code that I have seen in other posts and my specific need is that we do not have a 'starting point'; all the queries out there use a 'start with' statement, but since we need to put this in a view, we do not really have that starting record to start searching from.
Could anyone advice how we can build this object given the needs we have?
Thanks in advance.

In the example, the root element should not have any parent, if you remove the blank and recreate the table as this then your existing solution should work.
CREATE TABLE PAR_CHI
(PARENT_SN VARCHAR2(50 BYTE) ,
CHILD_SN VARCHAR2(50 BYTE) NOT NULL
);
create or replace view test_hierarchy as
select PARENT_SN, CHILD_SN as ROOT, level level_in,
connect_by_root(CHILD_SN) as Searched_val,
sys_connect_by_path(CHILD_SN, '/') as path
from PAR_CHI
where connect_by_isleaf = 1
--start with CHILD_SN = '135422839'
connect by CHILD_SN = prior PARENT_SN
;
you can query this view and pass the serial number to search and it will give you all the required fields

Related

oracle database data synchronized from one to multi relative tables

I hava a user table , which is quite simple
create table user (
user_id int primary key,
user_name varchar2(20)
)
And I build a couples of relative tables assocaite with user table and each table has a user_id , user_name.
So here comes a question, I happend misinput a data with wrong name, but then I just linked to this wrong record with all relative tables. If I want correct the user table and same time synchronized user_name in all relative tables.How I do in simple way? Plus I didn't set any constraint with these tables.
Edit:
So let me put that more clearly. I can query all user from user table, and then I just create a select in the jsp page. And this selector got two field user_id, user_name. This is how we call it a selector. First I recorded a man with '01','tam' maybe, and I just recorded another row in salary with 'tam','$1300'. This was all wrong cause name was 'tom'. It's easily to change user or salary , but in our system, there are over 40 tables linked to user. I know it's a bad idea but it is designed that way
by our dba and it already worked a long time.
We'll start by making the problem explicit. The data model violates Third Normal Form: instead of relying on user_id to reference user_name every table dependent on the user table has the attribute. The consequence of this is that correcting a mistake in user_name means propagating that change to every table.
Further more it seems that this application lacks a mechanism for correcting errors, or rather propagating the correction to all the impacted tables. So, what to do?
Dynamic SQL and the data dictionary to the rescue:
declare
l_id user.user_id%type := 1234;
l_old_name user.user_name%type := 'Tam';
l_new_name user.user_name%type := 'Tom';
begin
for rec in ( select table_name from user_tab_cols where column_name = 'USER_ID'
intersect
select table_name from user_tab_cols where column_name = 'USER_NAME'
)
loop
execute immediate 'update '|| rec.table_name ||
' set user_name = :1 where user_id = :2 and user_name = :3'
using l_new_name, l_id, l_old_name;
commit;
end loop;
end;
/
No guarantees about performance, because it depends on the data and indexing for each table.
"it already worked a long time"
Which makes me wonder how many data inconsistencies are contained in your system that you don't know about? Maybe your DBA needs to brush up on their data modelling skills.

DB Design: Table Inheritance and Hierarchy

The example used does not represent the domain I am actually solving for; it's just for example.
Background
I am defining table inheritance. Planning to use Table API packages & views to handle the necessary logic & presentation.
I also need to define multiple multi-level named hierarchies with disparate attributes.
Combining table inheritance (Location > State, City) & named hierarchies (State <- City), however, is where I've come across a design snag.
The Problem
I want to define a Unique Key enforcing Name of City is Unique within State, but the abstraction of the Name field is preventing that. I can handle it in PL/SQL if needed, but I would like to enforce it at the SQL level (ideally without an additional table) if at all possible.
Constraints
Other tables need to be able to refer to any level of the hierarchy generically (location).
Other tables also need to be able to refer to a specific level of the hierarchy.
Thoughts
I cannot do single table inheritance due to the complexity of the graph of relations.
I have thought about duplicating the Name field for ease, though I'd rather not violate Normal Forms.
Example
CREATE TABLE location
( id NUMBER ( 38) PRIMARY KEY
, name VARCHAR2(1000) NOT NULL
, type_id NUMBER ( 38) NOT NULL
--, other common/super attributes
);
CREATE TABLE state
( id NUMBER(38) PRIMARY KEY
REFERENCES location (id)
--, child attributes
);
CREATE TABLE city
( id NUMBER(38) PRIMARY KEY
REFERENCES location (id)
, state_id NUMBER(38) NOT NULL
REFERENCES state (id)
--, child attributes
);
I don't see why requiring city and state names to be unique (and therefore holding them in the relevant table) would violate any normal form. city name is not the same attribute as state name "New York" city != "New York" state for example.
Adding a "parent_location" to location table maybe works for you.
CREATE TABLE location
( id NUMBER ( 38) PRIMARY KEY
, name VARCHAR2(1000) NOT NULL
, type_id NUMBER ( 38) NOT NULL
, parent_location_id number( 38) NOT NULL
--, other common/super attributes
);
CREATE UNIQUE INDEX location_u2
ON location (parent_location_id, name);
CREATE TABLE city
( id NUMBER(38) PRIMARY KEY
REFERENCES location (id)
, --state_id gone away
--, child attributes
);
It also would prevent having two states with the same name.
The disadvantage is you'll need to use a dummy "0" location_id for the top node.
Best regards.

Modify a nested table in Oracle

I have the below Nested table created :
create or replace TYPE access_t AS OBJECT (
AccessID VARCHAR2(50),
Eligibility char(1)
);
/
create or replace TYPE Access_tab IS TABLE OF access_t;
/
create or replace TYPE add_t AS OBJECT (
city VARCHAR2(100),
state VARCHAR2(100),
zip VARCHAR2(10),
APOINTSARRAY Access_tab )
;
/
create or replace TYPE add_tab IS TABLE OF add_t;
/
CREATE TABLE RQST_STATUS
( RQST_ID NUMBER,
ADDRESS add_tab
)
NESTED TABLE ADDRESS STORE AS RQST_STATUS_ADDRESS
( NESTED TABLE APOINTSARRAY STORE AS RQST_STATUS_AP)
;
If i need to change ADDRESS type to new_add_tab with some additional columns instead of add_tab , Can i just use ALTER TABLE .. MODIFY .. command ?
I am getting ORA-00922 or ORA-22913 errors . I cannot change the type directly because it is used somewhere else too. Also, the table is already loaded with data.
Please suggest.
You can do that but you have to alter the TYPE not the TABLE.
Check the documentation ALTER TYPE Statement: alter_method_spec
Most important is the CASCADE key word.
Examples:
ALTER TYPE access_t ADD ATTRIBUTE NEW_Eligibility INTEGER CASCADE;
ALTER TYPE access_t DROP ATTRIBUTE Eligibility CASCADE;
ALTER TYPE access_t MODIFY ATTRIBUTE AccessID VARCHAR2(100) CASCADE;
Here is a step-by-step description of my suggestion. It might not be the most elegant, but I think that it would be best for you to have something you can fully understand (as opposed to an obscure trick).
Also, and since I don't really know what kind of changes you need to do for the internal table, I'm leaving the maximal flexibility for you to do any change you may wish to do.
Let's call your table T1 that contains a columns C_T which is your internal table.
The internal table contains columns C_1, C-2 and C_3, and you want the new structure for the record to be D_1, D_2, D_3, D_4 and D_5, where the mapping is:
C_1 -> D_5,
C_2 -> D_1,
C_3 -> D_2,
{new} -> D_3,
{new} -> D_4.
Create a tempo table TEMPO_T with a column SOURCE_ROWID (varchar2(64)) and the new columns D_1,..., D5.
Write a small anonymous block having a cursor that selects the ROWID of each row of table T1 and all the records within the internal table in column C_T (order by ROWID). The result would look like (this is just an example of course):
ROWID C_1 C_2 C_2
wwereeedffff 1 a ww
wwereeedffff 2 b xx
wwereeedffff 7 l yy
ertrtrrrtrrr 5 d PP
ertrtrrrtrrr 99 h mm
...
[Note: The use of ROWID is under the assumption that you don't have a column that can serve as a unique identifier for each row in table T1; if there is such column - one defined as UNIQUE INDEX - you can use that field instead]
Having this query ready, convert it into an INSERT into the temporary table TEMPO_T along with whatever values you need to store for columns D_3 and D_4.
Now, you have a backup of the original contents of column C_T and hence can delete the column.
Now, you can update the type that defines the structure of column C_T to its new form (i.e. D_1,...,D_5) and alter table T1 by adding a column whose type is the updated one.
Finally, you can insert the contents of column C_T with that stored in the temporary table (since you already have this, I assume that you know how to implement it - inserting a table within a cell column of the outer table).
That's it.
Needless to say, I would make a backup of your data before engaging into this.
Hope this description is detailed enough to enable you to complete the task at hand.

How to query tree table structure which has no subnodes

I have data organized as a tree in Oracle 11g.
CREATE TABLE FOLDER
(
NAME VARCHAR2(20),
ID NUMBER(10, 0),
PARENT_ID NUMBER(10, 0)
}
(I think this structure is obvious and I should explain it).
Trying to query only data that has no subfolders. Using this query (not sure its correct):
SELECT * FROM folder f WHERE NOT EXISTS (
SELECT *
FROM folder cf
WHERE f.id= cf.parent)
Is there possibility to use more optimal query for this purpose, for example using COUNT ?
You could use a hierarchical query:
select name, id, parent_id from (
select name, id, parent_id, connect_by_isleaf as isleaf
from folder
start with parent_id = parent_id -- from comments
connect by nocycle parent_id = prior id
)
where isleaf = 1;
The inner query gets the hierarchy plus a flag set using the connect_by_isleaf pseudocolumn:
The CONNECT_BY_ISLEAF pseudocolumn returns 1 if the current row is a leaf of the tree defined by the CONNECT BY condition. Otherwise it returns 0. This information indicates whether a given row can be further expanded to show more of the hierarchy.
You need the nocycle parameter because the fact you have the parent ID set to the same as the ID in the top-level row of the hierarchy means the prior condition is met by that row itself, causing a loop. The combination of the starting condition and the nocycle will let this work.
Essentially that gives you the whole of the original table plus an extra column with a 1 or a 0. You can run that inner query on its own to see what it produces.
You're only looking for the lead nodes, this which have no sub-folders, so the outer query then filters on the generated isleaf column, so you only see those which are leaves.

(Oracle) How to extract a specialized UDO from a object table based on a generalized UDO?

I apologize in advance for not knowing the terminology, but here it goes:
How do I extract a specialized UDO (UDO= user defined object & specialized= that 'extends' or is 'under' another UDO) that I inserted in a object table defined for a generalized UDO (generalized - higher in the same UDO hierarchy).
Details:
CREATE OR REPLACE TYPE GENERIC_UDO AS OBJECT (
atribute_1 TYPE,
...
atribute_n TYPE2, //TYPE2 is a nested table
) NOT FINAL NOT INSTANTIABLE;
CREATE OR REPLACE TYPE SPECIALIZED_UDO UNDER GENERIC_UDO (
atribute_1 TYPE,
...
atribute_q TYPE3, //TYPE3 is also a nested table
) FINAL INSTANTIABLE;
CREATE TABLE TBL_GENERIC_UDO OF GENERIC_UDO
( CONSTRAINT PK...)
... //other nested tables
NESTED TABLE atribute_n STORE AS atribute_n_nst;
So this works:
INSERT INTO TBL_GENERIC_UDO values (new TBL_GENERIC_UDO(...)); because TBL_GENERIC_UDO is 'under' GENERIC_UDO.
A 'select * from TBL_GENERIC_UDO' shows me as columns just the GENERIC_UDO atributes (as expected!)
So how do I extract or extract and convert, because I need to read back the inserted SPECIALIZED_UDO.
I payed a bit with:
VALUE(x): SELECT VALUE(tgo) FROM TBL_GENERIC_UDO tgo;
TREAT(x as Y): SELECT TREAT(VALUE(tgo) as SPECIALIZED_UDO) FROM TBL_GENERIC_UDO tgo;
and no good results. Actually in booth cases I get
SCHEMANAME.SPECIALIZED_UDO(values, values,..., null)
oracle.sql.STRUCT#54faa2
In the first case, when I did the insert, the corespondent value for atribute_q was set to null, in the second one it was a proper value, ex: SPECIALIZED_UDO('value1','value1',...)
I am asumming thats because TBL_GENERIC_UDO doesnt have a store clause for atribute_q from SPECIALIZED_UDO.
So anyone thoughts, explanations would be appreciated.
Solved it myself! Wasn't that hardcore as I expected. First I thought that Oracle will 'truncate' when inserting a specialized UDO into the table defined for a generic UDO, and in my case the specialized UDO had additionally another nested table as a atribute, but it doesn't.
Check code example, last line is the most important one:
CREATE OR REPLACE TYPE OBJ_TBL_TEXT IS TABLE OF VARCHAR2(100);
CREATE OR REPLACE TYPE OBJ_CONTAINER1 AS OBJECT (
ATRIBUTE NUMBER(6),
TBL_TEXT OBJ_TBL_TEXT
) NOT FINAL NOT INSTANTIABLE;
CREATE OR REPLACE TYPE OBJ_CONTAINER2 UNDER OBJ_CONTAINER1(
SECOND_TBL_TEXT OBJ_TBL_TEXT
) FINAL INSTANTIABLE;
CREATE TABLE TBL_CONTAINER1 OF OBJ_CONTAINER1
( CONSTRAINT PK_TBL_CONTAINER1 PRIMARY KEY(ATRIBUTE))
NESTED TABLE TBL_TEXT STORE AS TBL_TEXT_NST;
INSERT INTO TBL_CONTAINER1 VALUES (NEW OBJ_CONTAINER2(1,null,null));
INSERT INTO TBL_CONTAINER1 VALUES (NEW OBJ_CONTAINER2(2,null, OBJ_TBL_TEXT('VAL1','VAL2')));
INSERT INTO TBL_CONTAINER1 VALUES (NEW OBJ_CONTAINER2(3,OBJ_TBL_TEXT('VAL3','VAL4'), null));
INSERT INTO TBL_CONTAINER1 VALUES (NEW OBJ_CONTAINER2(4,OBJ_TBL_TEXT('VAL5','VAL6'), OBJ_TBL_TEXT('VAL7','VAL8')));
--------------------------------------------------------------------------------
SELECT * FROM TBL_CONTAINER1;
SELECT VALUE(tc) FROM TBL_CONTAINER1 tc;
SELECT TREAT(VALUE(tc) as OBJ_CONTAINER2).SECOND_TBL_TEXT FROM TBL_CONTAINER1 tc;
First select will display as columns just the atributes from the OBJ_CONTAINER1, like:
ATTRIBUTE| TBL_TEXT
--===============--
- 1 (null)
- 2 (null)
- 3 VARCHAR(VAL3,VAL4)
- 4 VARCHAR(VAL5,VAL6)
Second one looks weirder
VALUE(TC)
--=====--
DATAMODELER.OBJ_CONTAINER2(1,null,null)
oracle.sql.STRUCT#57ac8e
oracle.sql.STRUCT#1df6f
oracle.sql.STRUCT#f40801
VALUE - returns the object instance for a row of an object table as associated with the correlation_variable (table alias)
Third one lets me have access to nested attributes not mention in the nested clause in the creation of TBL_CONTAINER1.
TREAT(VALUE(tc) as OBJ_CONTAINER2).SECOND_TBL_TEXT
--==============================================--
(null)
VARCHAR(VAL1,VAL2)
(null)
VARCHAR(VAL7,VAL8)
It is exactly what I needed. Dont know yet how Oracle stores the additional attributes, and don't care. Though to share the solution...

Resources