Recursive SQL select query in Oracle 11 - sql

I have 4 different tables, bommodule, bomitem and mapbomitemmodule, mapbomparentsubmodule.
bommodule-table:
CREATE TABLE "BOMMODULE"
(
"MODULEID" NUMBER(10,0) NOT NULL ENABLE,
"ISROOTMODULE" NUMBER(1,0) NOT NULL ENABLE,
"MODULENAME" VARCHAR2(255 CHAR),
.....
)
bomitem-table:
CREATE TABLE "BOMITEM"
(
"ITEMID" NUMBER(10,0) NOT NULL ENABLE,
....
)
mapbomitemmodule-table: (This table maps the items to one or more modules).
CREATE TABLE "SSIS2"."MAPBOMITEMMODULE"
(
"ITEMID" NUMBER(10,0) NOT NULL ENABLE,
"MODULEID" NUMBER(10,0) NOT NULL ENABLE,
CONSTRAINT "MAP_ITEM_MODULE_FK" FOREIGN KEY ("ITEMID") REFERENCES "BOMITEM" ("ITEMID") ENABLE,
CONSTRAINT "MAP_MODULE_ITEM_FK" FOREIGN KEY ("MODULEID") REFERENCES "BOMMODULE" ("MODULEID") ENABLE
)
mapbomparentsubmodule-table: (This table maps the module to submodules)
CREATE TABLE "MAPBOMPARENTSUBMODULE"
(
"PARENTMODULEID" NUMBER(10,0) NOT NULL ENABLE,
"SUBMODULEID" NUMBER(10,0) NOT NULL ENABLE,
CONSTRAINT "PARENTMODULE_SUBMODULE_FK" FOREIGN KEY ("SUBMODULEID") REFERENCES "BOMMODULE" ("MODULEID") ENABLE,
CONSTRAINT "SUBMODULE_PARENTMODULE_FK" FOREIGN KEY ("PARENTMODULEID") REFERENCES "BOMMODULE" ("MODULEID") ENABLE
)
So imagine a structure something like this.
root module
submodule 1
submodule 2
submodule 3
submodule 4
submodule 5
item 5
item 6
item 7
item 2
item 3
item 4
item 1
I need to find out all items that belong to a specific moduleId. All items on all submodule levels should be listed.
How can I do this? I am using Oracle 11 as database.
Thanks a lot for your help, much appreciate it!

Thanks for the interesting question
1 step. Prepare table for canonical oracle hierarhy.
select PARENTMODULEID, SUBMODULEID, 1 HTYPE
from MAPBOMPARENTSUBMODULE
union all
select null, MODULEID, 1 --link root to null parent
from BOMMODULE B
where ISROOTMODULE = 1
union all
select MODULEID, ITEMID, 2
from MAPBOMITEMMODULE
2 step. Expand the hierarchy by using connect by
select PARENTMODULEID
,SUBMODULEID
,HTYPE
,level
,lpad('|', level*3, '|')
from
(
select PARENTMODULEID, SUBMODULEID, 1 HTYPE
from MAPBOMPARENTSUBMODULE
union all
select null, MODULEID, 1
from BOMMODULE B
where ISROOTMODULE = 1
union all
select MODULEID, ITEMID, 2
from MAPBOMITEMMODULE
) ALL_HIER
connect by prior SUBMODULEID = PARENTMODULEID
and prior HTYPE = 1 --parent is always module
start with ALL_HIER.PARENTMODULEID is null
order siblings by HTYPE
3 step. Last. Join with value tables.
select PARENTMODULEID
,SUBMODULEID
,HTYPE
,ALL_VAL.VAL
,level
,rpad('|', level * 3, ' ') || ALL_VAL.VAL
from
(
select PARENTMODULEID, SUBMODULEID, 1 HTYPE
from MAPBOMPARENTSUBMODULE
union all
select null, MODULEID, 1
from BOMMODULE B
where ISROOTMODULE = 1
union all
select MODULEID, ITEMID, 2
from MAPBOMITEMMODULE
) ALL_HIER
,(
select MODULEID VAL_ID, MODULENAME VAL, 1 VTYPE
from BOMMODULE
union all
select ITEMID, 'item '||ITEMID, 2
from BOMITEM
) ALL_VAL
where ALL_VAL.VAL_ID = ALL_HIER.SUBMODULEID
and ALL_VAL.VTYPE = ALL_HIER.HTYPE
connect by prior SUBMODULEID = PARENTMODULEID
and prior HTYPE = 1
start with ALL_HIER.PARENTMODULEID is null
order siblings by HTYPE

Start by investigating the use of a hierarchical query, using the CONNECT BY clause. It is designed for exactly this type of model.
The principle for multiple tables is the same as that for a single table:
i. You identify "starting rows".
ii. You define the logic by which you identify the next level of rows based on the "current" set.
In some cases it can help to define the hierarchical query in an inline view, particularly if a single table holds all the data required for identifying both the starting rows and the connection between levels.
These queries are always tricky, and as with many SQL issues it often helps to start off simple and build up the complexity.

Related

Using multiple tables for an Oracle hierarchy query where each table represents a level

I'm researching ways to create a hierarchical SQL query for my Oracle database where Table1 and Table2 have a many-to-one relationship, and Table2 and Table3 have a many-to-one relationship and where Table1 is the parent of Table2, and Table2 is the parent of Table3.
I'm trying to create a query where Table1 represents LEVEL 1, Table2 represents LEVEL2, and Table3 represents LEVEL 3 in Oracle's LEVEL pseudocolumn.
A simplified table structure is as follows, where TABLE1, TABLE2, and TABLE3 each have columns that are unique to them and their own unique IDs and descriptions.
For example, imagine Table1 represent a State, Table2 represents a City, and Table3 represents a Zip code - these all have their own unique properties, and a State has many Cities, and a City has many Zip Codes.
TABLE1(id (PK), t1name, description)
TABLE2(id (PK), t2name, table1ID (FK), description, var1, var2)
TABLE3(id (PK), t3name, table2ID (FK), description, var3, var4)
I've tried using a query like this but the LEVEL pseudocolumn is always '1' and the CONNECT_BY_ISLEAF pseudocolumn is also '1':
WITH alltabs as
(Select 'T1' as src, table1.ID, NULL AS parent1Id, NULL as parent2Id, table1.name as name
from table1
union
Select 'T2' as src, table2.ID, table2.table1Id, NULL as parent2Id, table2.name as name
from table2
union
Select 3 as src, table3.ID, NULL AS parent1Id, table3.table2id, table3.name as name
from table3)
Select LEVEL, src, parent1Id, parent2Id, name, CONNECT_BY_ISLEAF
from alltabs
connect by (id = parent1Id and (src = 'T1' or src = 'T2'))
or (id = parent2Id AND (src = 'T2' or src = 'T3'))
The result I'm looking for is one where the LEVEL pseudocolumn is '1' for Table1, '2' for Table2, and '3' for Table3.
Any guidance would be appreciated - I'm also open to changing the table structure. Thank you!
Just use a single table with a self-referential foreign key:
CREATE TABLE table1 (
id NUMBER
CONSTRAINT table1__id__pk PRIMARY KEY,
name VARCHAR2(50),
parent CONSTRAINT table1__parent__fk REFERENCES table1 (id)
);
Then you can use:
SELECT t.*,
LEVEL,
CONNECT_BY_ISLEAF,
SYS_CONNECT_BY_PATH(name, ' > ') AS ancestors
FROM table1 t
START WITH parent IS NULL
CONNECT BY PRIOR id = parent
ORDER SIBLINGS BY name;
Which, for the sample data:
INSERT INTO table1 (id, name, parent)
SELECT 1, 'Alice', NULL FROM DUAL UNION ALL
SELECT 2, 'Beryl', 1 FROM DUAL UNION ALL
SELECT 3, 'Carol', 1 FROM DUAL UNION ALL
SELECT 4, 'Debra', 2 FROM DUAL UNION ALL
SELECT 5, 'Emily', 3 FROM DUAL UNION ALL
SELECT 6, 'Fiona', 4 FROM DUAL;
Outputs:
ID
NAME
PARENT
LEVEL
CONNECT_BY_ISLEAF
ANCESTORS
1
Alice
null
1
0
 > Alice
2
Beryl
1
2
0
 > Alice > Beryl
4
Debra
2
3
0
 > Alice > Beryl > Debra
6
Fiona
4
4
1
 > Alice > Beryl > Debra > Fiona
3
Carol
1
2
0
 > Alice > Carol
5
Emily
3
3
1
 > Alice > Carol > Emily
fiddle
Update
Imagine Table1 represent a State, Table2 represents a City, and Table3 represents a Zip code - these all have their own unique properties, and a State has many Cities, and a City has many Zip Codes.
If you have three distinct sets of data (states, cities and zip codes) each with properties that are unique to that set of data then use three different three tables.
CREATE TABLE state (
id NUMBER PRIMARY KEY,
name VARCHAR2(50),
government VARCHAR2(50),
flag BLOB
);
CREATE TABLE city (
id NUMBER PRIMARY KEY,
state_id REFERENCES state (id),
mayor VARCHAR2(20),
rat_population NUMBER(4,0)
);
CREATE TABLE zip_code (
id NUMBER PRIMARY KEY,
state_id REFERENCES state (id),
nearest_city_id REFERENCES city (id),
refuse_collection_day VARCHAR2(9)
);
If you want to find information about the tables then just use a JOIN on the referential constraints when you want to combine two-or-more data sets. You do not want to use a hierarchical query for this type of data.

Oracle Count and Group By Optimized Report Query

Oracle 19c
We have a requirement where we want to provide some report data from Event table, Event and Item table has schema like this
CREATE TABLE EVENT
(
"ID" NUMBER(19,0) NOT NULL ENABLE,
"CREATED" TIMESTAMP (6) NOT NULL,
"CUSTOMER_ID" VARCHAR2(255 CHAR) NOT NULL,
"CONF_ID" VARCHAR2(255 CHAR),
"STATE" VARCHAR2(255 CHAR) NOT NULL,
"ITEM_ID" VARCHAR2(255 CHAR) NOT NULL
...
)
CREATE TABLE ITEM
(
"ID" NUMBER(19,0) NOT NULL ENABLE,
"NAME" VARCHAR2(255 CHAR) NOT NULL
....
primary key (ID)
)
alter table EVENT
add constraint EVENT_FK_ITEM_BID
foreign key (ITEM_ID)
references ITEM;
where events are created with different states as per real time occurrance. Events are bound to Item Table with Item_id.
What we want to achieve select count of Event States (only consider the latest state per item_id), grouped by CUSTOMER_ID and CONF_ID.
Event table could have more than 2 million rows.
result should look like
CUSTOMER_ID CONF_ID ACIVATED DEACTIVATED SUSPENDED
---------- ------- -------- ------------- ---------
1 2 50000 20000 5000
1 1 70000 30000 2000
2 1 80000 10000 10000
2 2 50000 20000 5000
Could you please guide us building an efficient query?
You can find the latest rows using the RANK analytic function (or the ROW_NUMBER analytic function if there will only ever be one latest row for each customer_id, conf_id, item_id tuple) and then PIVOT:
SELECT *
FROM (
SELECT customer_id,
conf_id,
state
FROM (
SELECT customer_id,
conf_id,
state,
RANK() OVER (PARTITION BY customer_id, conf_id, item_id ORDER BY created DESC) AS rnk
FROM event
WHERE state IN ('ACTIVATED', 'SUSPENDED', 'DEACTIVATED')
)
WHERE rnk = 1
)
PIVOT (
COUNT(*)
FOR state IN (
'ACTIVATED' AS activated,
'SUSPENDED' AS suspended,
'DEACTIVATED' AS deactivated
)
);
fiddle

Recursive query based on array column

I have a table with parent and child as columns, my parent column is an array because the child can have multiple parents. So could anyone suggest on how to build cte (Postgres 11.2) to get parent and ancestors of a child.This is my data. The cte should return the correct parent irrespective of the array position.
CREATE TABLE public.mytable (
id uuid NOT NULL,
parent_id uuid[],
name character varying(255) COLLATE pg_catalog."default",
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
CONSTRAINT mytable_pkey PRIMARY KEY (id)
);
The data is
id parentId
1 null
2 null
3 1
4 1
5 1
6 [3,4]
7 2
8 6
Expected output for the id=8:
parent
1
[3+4]
6
This works for your test case:
WITH RECURSIVE ancestry AS (
SELECT parent_id
FROM tbl
WHERE id = 8 -- your id here
UNION ALL
SELECT DISTINCT t.parent_id
FROM ancestry a
JOIN tbl t ON t.id = ANY (a.parent_id)
WHERE t.parent_id IS NOT NULL
)
TABLE ancestry;
db<>fiddle here
The query does not, however, merge elements of multiple distinct arrays on the same level. Your test case is not revealing in this respect. You probably have to do more. But you'll first have to define exactly what's allowed in the data and how to deal with duplicates in multiple ancestry arrays on the same level.

How to insert a column which sets unique id based on values in another column (SQL)?

I will create table where I will insert multiple values for different companies. Basically I have all values that are in the table below but I want to add a column IndicatorID which is linked to IndicatorName so that every indicator has a unique id. This will obviously not be a PrimaryKey.
I will insert the data with multiple selects:
CREATE TABLE abc
INSERT INTO abc
SELECT company_id, 'roe', roevalue, metricdate
FROM TABLE1
INSERT INTO abc
SELECT company_id, 'd/e', devalue, metricdate
FROM TABLE1
So, I don't know how to add the IndicatorID I mentioned above.
EDIT:
Here is how I populate my new table:
INSERT INTO table(IndicatorID, Indicator, Company, Value, Date)
SELECT [the ID that I need], 'NI_3y' as 'Indicator', t.Company, avg(t.ni) over (partition by t.Company order by t.reportdate rows between 2 preceding and current row) as 'ni_3y',
t.reportdate
FROM table t
LEFT JOIN IndicatorIDs i
ON i.Indicator = roe3 -- the part that is not working if I have separate indicatorID table
I am going to insert different indicators for the same companies. And I want indicatorID.
Your "indicator" is a proper entity in its own right. Create a table with all indicators:
create table indicators (
indicator_id int identity(1, 1) primary key,
indicator varchar(255)
);
Then, use the id only in this table. You can look up the value in the reference table.
Your inserts are then a little more complicated:
INSERT INTO indicators (indicator)
SELECT DISTINCT roevalue
FROM table1 t1
WHERE NOT EXISTS (SELECT 1 FROM indicators i2 WHERE i2.indicator = t1.roevalue);
Then:
INSERT INTO ABC (indicatorId, companyid, value, date)
SELECT i.indicatorId, t1.company, v.value, t1.metricdate
FROM table1 t1 CROSS APPLY
(VALUES ('roe', t1.roevalue), ('d/e', t1.devalue)
) v(indicator, value) JOIN
indicators i
ON i.indicator = v.indicator;
This process is called normalization and it is the typical way to store data in a database.
DDL and INSERT statement to create an indicators table with a unique constraint on indicator. Because the ind_id is intended to be a foreign key in the abc table it's created as a non-decomposable surrogate integer primary key using the IDENTITY property.
drop table if exists test_indicators;
go
create table test_indicators (
ind_id int identity(1, 1) primary key not null,
indicator varchar(20) unique not null);
go
insert into test_indicators(indicator) values
('NI'),
('ROE'),
('D/E');
The abc table depends on the ind_id column from indicators table as a foreign key reference. To populate the abc table company_id's are associated with ind_id's.
drop table if exists test_abc
go
create table test_abc(
a_id int identity(1, 1) primary key not null,
ind_id int not null references test_indicators(ind_id),
company_id int not null,
val varchar(20) null);
go
insert into test_abc(ind_id, company_id)
select ind_id, 102 from test_indicators where indicator='NI'
union all
select ind_id, 103 from test_indicators where indicator='ROE'
union all
select ind_id, 104 from test_indicators where indicator='D/E'
union all
select ind_id, 103 from test_indicators where indicator='NI'
union all
select ind_id, 105 from test_indicators where indicator='ROE'
union all
select ind_id, 102 from test_indicators where indicator='NI';
Query to get result
select i.ind_id, a.company_id, i.indicator, a.val
from test_abc a
join test_indicators i on a.ind_id=i.ind_id;
Output
ind_id company_id indicator val
1 102 NI NULL
2 103 ROE NULL
3 104 D/E NULL
1 103 NI NULL
2 105 ROE NULL
1 102 NI NULL
I was finally able to find the solution for my problem which seems to me very simple, although it took time and asking different people about it.
First I create my indicators table where I assign primary key for all indicators I have:
CREATE TABLE indicators (
indicator_id int identity(1, 1) primary key,
indicator varchar(255)
);
Then I populate easy without using any JOINs or CROSS APPLY. I don't know if this is optimal but it seems as the simplest choice:
INSERT INTO table(IndicatorID, Indicator, Company, Value, Date)
SELECT
(SELECT indicator_id from indicators i where i.indicator = 'NI_3y) as IndicatorID,
'NI_3y' as 'Indicator',
Company,
avg(ni) over (partition by Company order by reportdate rows between 2 preceding and current row) as ni_3y,
reportdate
FROM TABLE1

How to make a join in a hierarchical query

I have these two tables:
CREATE TABLE Category
(
curCategory VARCHAR2(50) PRIMARY KEY,
parentCategory VARCHAR(50),
CONSTRAINT check_cat CHECK (curCategory is not null),
CONSTRAINT fk_category1 FOREIGN KEY(parentCategory) REFERENCES Category(curCategory)
);
CREATE TABLE Article
(
name VARCHAR(50)
artCategory VARCHAR(50),
CONSTRAINT pk_article PRIMARY KEY(name),
CONSTRAINT fk_artCategory FOREIGN KEY(artCategory) REFERENCES Category(curCategory)
);
What I want is something like this:
Select
level, curCategory, parentCategory
from
Category
join
article on artCategory = curCategory
start with curCategory = 'Clothes'
connect by prior curCategory = parentCategory
order siblings by curCategory;
I want to print every article which is a clothes. So what I wanted to do is go through every child category including the category itself ('Clothes') and check if the article category matches the curCategory. But when I execute my query I get zero records.
Syntax looks like Oracle so - here's one option which shows what you might do.
Use VARCAHR**2** datatype, not VARCHAR. I shortened columns' length so that the output is easier to display. Also, there was a syntax error (a missing comma at the end of the article.name column declaration).
SQL> create table category (
2 curcategory varchar2(20) primary key,
3 parentcategory varchar2(20),
4 constraint check_cat check ( curcategory is not null ),
5 constraint fk_category1 foreign key ( parentcategory )
6 references category ( curcategory )
7 );
Table created.
SQL> create table article (
2 name varchar2(20),
3 artcategory varchar2(20),
4 constraint pk_article primary key ( name ),
5 constraint fk_artcategory foreign key ( artcategory )
6 references category ( curcategory )
7 );
Table created.
Some sample data:
SQL> select * from category;
CURCATEGORY PARENTCATEGORY
-------------------- --------------------
clothes
trousers clothes
shirts clothes
SQL> select * from article;
NAME ARTCATEGORY
-------------------- --------------------
t-shirt shirts
a-shirt shirts
jeans trousers
SQL>
According to that, the hierarchy should look like this:
clothes
shirts
t-shirt
a-shirt
trousers
jeans
OK, let's make it so.
Out of two tables, using the WITH factoring clause (i.e. a CTE, common table expression), I'm UNION-ing those two tables in order to create a single parent-child source. Then, writing a hierarchical query is a simple matter.
SQL> with source as
2 (select c.parentcategory parent, c.curcategory child
3 from category c
4 union
5 select a.artcategory parent, a.name child
6 from article a
7 )
8 select lpad(' ', 2 * level - 1) || child clothes
9 from source
10 start with parent is null
11 connect by prior child = parent
12 order siblings by child;
CLOTHES
------------------------------------------------------------
clothes
shirts
a-shirt
t-shirt
trousers
jeans
6 rows selected.
SQL>