Can Hierarchical queries be used in a condition? - sql

I am going to build a Hierarchial Tree in Oracle Forms using the Hiearchical queries. As far as I know, the HQ works only for single table, can they be used in a condition where we have Master Details scenario.
Or can it be based on a view?
For example; I want to display an hierarchical with 3 of options from various tables. For example a Customer Order can have have various Items. First List of All CO, on clicking a specific CO, All the Items therein, An Item may have Sub Parts, and so on. Spanning multiple connected tables

If you have the test data:
CREATE TABLE customer_orders ( id, name ) AS
SELECT 1, 'Cust. Ord. XYZ' FrOM DUAL UNION ALL
SELECT 2, 'Cust. Ord. ABC' FrOM DUAL UNION ALL
SELECT 3, 'Cust. Ord. MNO' FrOM DUAL;
CREATE TABLE customer_order_items ( id, order_id, name ) AS
SELECT 1, 3, 'MNO_NNN' FROM DUAL UNION ALL
SELECT 2, 3, 'MNO_OOO' FROM DUAL UNION ALL
SELECT 3, 3, 'MNO_MMM' FROM DUAL UNION ALL
SELECT 4, 2, 'ABC_AAA' FROM DUAL;
CREATE TABLE customer_order_item_parts ( id, item_id, name ) AS
SELECT 1, 1, 'Part_2_N' FROM DUAL UNION ALL
SELECT 2, 1, 'Part_1_N' FROM DUAL UNION ALL
SELECT 3, 2, 'Part_1_O' FROM DUAL UNION ALL
SELECT 4, 2, 'Part_3_O' FROM DUAL UNION ALL
SELECT 5, 4, 'Part_5_A' FROM DUAL UNION ALL
SELECT 6, 4, 'Part_3_A' FROM DUAL UNION ALL
SELECT 7, 4, 'Part_2_A' FROM DUAL UNION ALL
SELECT 8, 4, 'Part_4_A' FROM DUAL;
Then you can use UNION ALL (and JOINs as required) to concatenate the data and once it is all compiled then you can apply a hierarchical query:
SELECT *
FROM (
SELECT id AS order_id,
NULL AS item_id,
NULL AS part_id,
name
FROM customer_orders
UNION ALL
SELECT order_id,
id,
NULL,
name
FROM customer_order_items
UNION ALL
SELECT i.order_id,
p.item_id,
p.id,
p.name
FROM customer_order_items i
INNER JOIN
customer_order_item_parts p
ON ( i.id = p.item_id )
)
START WITH item_id IS NULL
CONNECT BY NOCYCLE
PRIOR order_id = order_id
AND ( ( PRIOR item_id IS NULL
AND part_id IS NULL )
OR ( PRIOR part_id IS NULL
AND part_id IS NOT NULL
AND PRIOR item_id = item_id )
)
ORDER SIBLINGS BY name
Which outputs:
ORDER_ID | ITEM_ID | PART_ID | NAME
-------: | ------: | ------: | :-------------
2 | null | null | Cust. Ord. ABC
2 | 4 | null | ABC_AAA
2 | 4 | 7 | Part_2_A
2 | 4 | 6 | Part_3_A
2 | 4 | 8 | Part_4_A
2 | 4 | 5 | Part_5_A
3 | null | null | Cust. Ord. MNO
3 | 3 | null | MNO_MMM
3 | 1 | null | MNO_NNN
3 | 1 | 2 | Part_1_N
3 | 1 | 1 | Part_2_N
3 | 2 | null | MNO_OOO
3 | 2 | 3 | Part_1_O
3 | 2 | 4 | Part_3_O
1 | null | null | Cust. Ord. XYZ
db<>fiddle here

Related

Sorting the result based on 2 columns Oracle

I have two columns Date and Number with null values in date. Can I sort with date and number col together checking if date is null then sort with number.
dt num
3/20/2022 1
3/16/2022 3
3/17/2022 4
3/18/2022 5
NULL 6
NULL 7
3/19/2022 8
*Expected Output*
dt num
3/16/2022 3
3/17/2022 4
3/18/2022 5
NULL 6
NULL 7
3/19/2022 8
3/20/2022 1
We need to sort by the date if there is one and if there is not we search the previous row where the date is not null.
This does mean that we are running a sub-query per line so it will be slow for large queries.
create table d(
dt date,
num int);
insert into d (dt, num)
select to_date('2022-03-20','YYYY-MM-DD'),1 from dual union all
select to_date('2022-03-16','YYYY-MM-DD'),3 from dual union all
select to_date ('2022-03-17','YYYY-MM-DD'), 4 from dual union all
select to_date('2022-03-17','YYYY-MM-DD'),5 from dual union all
select to_date('2022-03-18','YYYY-MM-DD'),6 from dual union all
select to_date('2022-03-16','YYYY-MM-DD'),10 from dual union all
select to_date('2022-03-19','YYYY-MM-DD'),9 from dual;
insert into d ( num)
select 7 from dual union all
select 8 from dual ;
select
dt,
num,
( select dt
from d
where num <= d1.num and dt is not null
order by num desc
fetch next 1 rows only
) as dt_plus
from d d1
order by dt_plus,num;
DT | NUM | DT_PLUS
:-------- | --: | :--------
16-MAR-22 | 3 | 16-MAR-22
16-MAR-22 | 10 | 16-MAR-22
17-MAR-22 | 4 | 17-MAR-22
17-MAR-22 | 5 | 17-MAR-22
18-MAR-22 | 6 | 18-MAR-22
null | 7 | 18-MAR-22
null | 8 | 18-MAR-22
19-MAR-22 | 9 | 19-MAR-22
20-MAR-22 | 1 | 20-MAR-22
db<>fiddle here

Filtering a table via another table's values

I have 2 tables:
Value
+----+-------+
| id | name |
+----+-------+
| 1 | Peter |
| 2 | Jane |
| 3 | Joe |
+----+-------+
Filter
+----+---------+------+
| id | valueid | type |
+----+---------+------+
| 1 | 1 | A |
| 2 | 1 | B |
| 3 | 1 | C |
| 4 | 1 | D |
| 5 | 2 | A |
| 6 | 2 | C |
| 7 | 2 | E |
| 8 | 3 | A |
| 9 | 3 | D |
+----+---------+------+
I need to retrieve the values from the Value table where the related Filter table does not contain the type 'B' or 'C'
So in this quick example this would be only Joe.
Please note this is a DB2 DB and i have limited permissions to run selects only.
Or also a NOT IN (<*fullselect*) predicate:
Only that my result is 'Joe', not 'Jane' - and the data constellation would point to that ...
WITH
-- your input, sans reserved words
val(id,nam) AS (
SELECT 1,'Peter' FROM sysibm.sysdummy1
UNION ALL SELECT 2,'Jane' FROM sysibm.sysdummy1
UNION ALL SELECT 3,'Joe' FROM sysibm.sysdummy1
)
,
filtr(id,valueid,typ) AS (
SELECT 1,1,'A' FROM sysibm.sysdummy1
UNION ALL SELECT 2,1,'B' FROM sysibm.sysdummy1
UNION ALL SELECT 3,1,'C' FROM sysibm.sysdummy1
UNION ALL SELECT 4,1,'D' FROM sysibm.sysdummy1
UNION ALL SELECT 5,2,'A' FROM sysibm.sysdummy1
UNION ALL SELECT 6,2,'C' FROM sysibm.sysdummy1
UNION ALL SELECT 7,2,'E' FROM sysibm.sysdummy1
UNION ALL SELECT 8,3,'A' FROM sysibm.sysdummy1
UNION ALL SELECT 9,3,'D' FROM sysibm.sysdummy1
)
-- real query starts here
SELECT
*
FROM val
WHERE id NOT IN (
SELECT valueid FROM filtr WHERE typ IN ('B','C')
)
;
-- out id | nam
-- out ----+-------
-- out 3 | Joe
Or also, a failing left join:
SELECT
val.*
FROM val
LEFT JOIN (
SELECT valueid FROM filtr WHERE typ IN ('B','C')
) filtr
ON filtr.valueid = val.id
WHERE valueid IS NULL
You can use EXISTS, as in:
select *
from value v
where not exists (
select null from filter f
where f.valueid = v.id and f.type in ('B', 'C')
);
Result:
ID NAME
--- -----
3 Joe
See running example at db<>fiddle.

Oracle: How can I pivot an EAV table with a dynamic cardinality for certain keys?

I have the following Entity–attribute–value (EAV) table in Oracle:
| ID | Key | Value |
|----|-------------|--------------|
| 1 | phone_num_1 | 111-111-1111 |
| 1 | phone_num_2 | 222-222-2222 |
| 1 | contact_1 | friend |
| 1 | contact_2 | family |
| 1 | first_name | mike |
| 1 | last_name | smith |
| 2 | phone_num_1 | 333-333-3333 |
| 2 | phone_num_2 | 444-444-4444 |
| 2 | contact_1 | family |
| 2 | contact_2 | friend |
| 2 | first_name | john |
| 2 | last_name | adams |
| 3 | phone_num_1 | 555-555-5555 |
| 3 | phone_num_2 | 666-666-6666 |
| 3 | phone_num_3 | 777-777-7777 |
| 3 | contact_1 | work |
| 3 | contact_2 | family |
| 3 | contact_3 | friend |
| 3 | first_name | mona |
| 3 | last_name | lisa |
Notice that some keys are indexed and therefore have an association with other indexed keys. For example, phone_num_1 is to be associated with contact_1.
Note: There is no hard limit to the number of indexes. There can be 10, 20, or even 50 phone_num_*, but it's guaranteed that for each phone_num_N, there is a corresponding contact_N
This is my desired result:
| ID | Phone_Num | Contact | First_Name | Last_Name |
|----|--------------|---------|------------|-----------|
| 1 | 111-111-1111 | friend | mike | smith |
| 1 | 222-222-2222 | family | mike | smith |
| 2 | 333-333-3333 | family | john | adams |
| 2 | 444-444-4444 | friend | john | adams |
| 3 | 555-555-5555 | work | mona | lisa |
| 3 | 666-666-6666 | family | mona | lisa |
| 3 | 777-777-7777 | friend | mona | lisa |
What have I tried/looked at:
I have looked into the pivot function of Oracle; however, I don't believe that can solve my problem since I don't have a fixed number of attributes that I want to pivot on.
I've looked at these posts:
SQL Query to return multiple key value pairs from a single table in one row
Pivot rows to columns without aggregate
Question:
Is what I'm tying to accomplish at all possible purely with SQL? If so, how can it be done? If not, please explain why.
Any help is much appreciated and here's the with table to help you get started:
with
table_1 ( id, key, value ) as (
select 1,'phone_num_1','111-111-1111' from dual union all
select 1,'phone_num_2','222-222-2222' from dual union all
select 1,'contact_1','friend' from dual union all
select 1,'contact_2','family' from dual union all
select 1,'first_name','mike' from dual union all
select 1,'last_name','smith' from dual union all
select 2,'phone_num_1','333-333-3333' from dual union all
select 2,'phone_num_2','444-444-4444' from dual union all
select 2,'contact_1','family' from dual union all
select 2,'contact_2','friend' from dual union all
select 2,'first_name','john' from dual union all
select 2,'last_name','adams' from dual union all
select 3,'phone_num_1','555-555-5555' from dual union all
select 3,'phone_num_2','666-666-6666' from dual union all
select 3,'phone_num_3','777-777-7777' from dual union all
select 3,'contact_1','work' from dual union all
select 3,'contact_2','family' from dual union all
select 3,'contact_3','friend' from dual union all
select 3,'first_name','mona' from dual union all
select 3,'last_name','lisa' from dual
)
select * from table_1;
This is not a dynamic pivot as you have a fixed set of keys - you just need to separate the enumeration of the keys from the keys themselves first.
You need to:
Separate the phone_num and contact key prefixes from the enumerated item; then
Pivot the common keys that have no enumeration so that they are associated with each enumerated key; and finally,
Pivot a second time to get the enumerated keys in a row together.
Oracle Setup:
CREATE TABLE table_1 ( id, key, value ) as
select 1,'phone_num_1','111-111-1111' from dual union all
select 1,'phone_num_2','222-222-2222' from dual union all
select 1,'contact_1','friend' from dual union all
select 1,'contact_2','family' from dual union all
select 1,'first_name','mike' from dual union all
select 1,'last_name','smith' from dual union all
select 2,'phone_num_1','333-333-3333' from dual union all
select 2,'phone_num_2','444-444-4444' from dual union all
select 2,'contact_1','family' from dual union all
select 2,'contact_2','friend' from dual union all
select 2,'first_name','john' from dual union all
select 2,'last_name','adams' from dual union all
select 3,'phone_num_1','555-555-5555' from dual union all
select 3,'phone_num_2','666-666-6666' from dual union all
select 3,'phone_num_3','777-777-7777' from dual union all
select 3,'contact_1','work' from dual union all
select 3,'contact_2','family' from dual union all
select 3,'contact_3','friend' from dual union all
select 3,'first_name','mona' from dual union all
select 3,'last_name','lisa' from dual
Query:
SELECT *
FROM (
SELECT id,
CASE
WHEN key LIKE 'phone_num_%' THEN 'phone_num'
WHEN key LIKE 'contact_%' THEN 'contact'
ELSE key
END AS key,
CASE
WHEN key LIKE 'phone_num_%'
OR key LIKE 'contact_%'
THEN TO_NUMBER( SUBSTR( key, INSTR( key, '_', -1 ) + 1 ) )
ELSE NULL
END AS item,
value,
MAX( CASE key WHEN 'first_name' THEN value END )
OVER ( PARTITION BY id ) AS first_name,
MAX( CASE key WHEN 'last_name' THEN value END )
OVER ( PARTITION BY id ) AS last_name
FROM table_1
)
PIVOT( MAX( value ) FOR key IN ( 'contact' AS contact, 'phone_num' AS phone_num ) )
WHERE item IS NOT NULL
ORDER BY id, item
Output:
ID | ITEM | FIRST_NAME | LAST_NAME | CONTACT | PHONE_NUM
-: | ---: | :--------- | :-------- | :------ | :-----------
1 | 1 | mike | smith | friend | 111-111-1111
1 | 2 | mike | smith | family | 222-222-2222
2 | 1 | john | adams | family | 333-333-3333
2 | 2 | john | adams | friend | 444-444-4444
3 | 1 | mona | lisa | work | 555-555-5555
3 | 2 | mona | lisa | family | 666-666-6666
3 | 3 | mona | lisa | friend | 777-777-7777
db<>fiddle here
If you can refactor the table then a simple improvement would be to add an extra column to hold the enumeration of the keys and use NULL when it is a value common to every enumeration:
CREATE TABLE table_1 ( id, key, line, value ) as
select 1, 'phone_num', 1, '111-111-1111' from dual union all
select 1, 'phone_num', 2, '222-222-2222' from dual union all
select 1, 'contact', 1, 'friend' from dual union all
select 1, 'contact', 2, 'family' from dual union all
select 1, 'first_name', NULL, 'mike' from dual union all
select 1, 'last_name', NULL, 'smith' from dual
Then your set of keys is always fixed and you do not need to extract the enumeration value from the key.
This is ugly, but I think does what you need
select t1.* , t2.value, t3.n, t3.f
from table_1 t1
inner join table_1 t2 on t1.id = t2.id and REPLACE(t1.key, 'phone_num_', '') = REPLACE(t2.key, 'contact_', '')
inner join (
select ID, min(case when Key = 'first_name' then Value end) as n, min(case when Key = 'last_name' then Value end) as f
from table_1
group by ID
) t3 on t1.id = t3.id
where
t1.Key not in('first_name','last_name')
SELECT id,
phone,
contact,
first_value(last) IGNORE NULLS over (partition BY id order by id DESC range BETWEEN CURRENT row AND unbounded following ) last_name,
first_value(FIRST) IGNORE NULLS over (partition BY id order by id DESC range BETWEEN CURRENT row AND unbounded following ) first_name
FROM
(SELECT id,
value,
row_number() over ( partition BY id,SUBSTR(KEY,1 ,instr(KEY,'',1)-1) order by KEY) rn,
SUBSTR(KEY,1 ,instr(KEY,'',1) -1) KEY
FROM table_1
) pivot ( MAX(value) FOR KEY IN ( 'phone' AS phone,'last' AS last,'first' AS FIRST,'contact' AS contact))
ORDER BY id;

Create view from multiple tables, combine values from multiple rows into one row

I have 3 tables as below:
Area table:
UserID | Area
---------------
1 | 10001
2 | 10002
3 | 10003
Info table:
UserID | Info
-----------------
1 | U1_Info1
1 | U1_Info2
1 | U1_Info3
2 | U2_Info1
3 | U3_Info1
Company table:
UserID | Company
-----------------
1 | ComA
2 | ComB
3 | ComC
After that, I want group by UserID. My expected result as below:
UserID | Area | Info1 | Info2 | Info3 | Company
----------------------------------------------------------
1 | 10001 | U1_Info1 | U1_Info2 | U1_Info3 | ComA
2 | 10002 | U2_Info1 | | | ComB
3 | 10003 | U3_Info1 | | | ComC
User 3 doesn't have Info2 and Info3 so I set them = ' '.
Can I make a View like that?
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE Area ( UserID, Area ) AS
SELECT 1, 10001 FROM DUAL
UNION ALL SELECT 2, 10002 FROM DUAL
UNION ALL SELECT 3, 10003 FROM DUAL;
CREATE TABLE Info ( UserID, Info ) AS
SELECT 1, 'U1_Info1' FROM DUAL
UNION ALL SELECT 1, 'U1_Info2' FROM DUAL
UNION ALL SELECT 1, 'U1_Info3' FROM DUAL
UNION ALL SELECT 2, 'U2_Info1' FROM DUAL
UNION ALL SELECT 3, 'U3_Info1' FROM DUAL;
CREATE TABLE Company (UserID, Company ) AS
SELECT 1, 'ComA' FROM DUAL
UNION ALL SELECT 2, 'ComB' FROM DUAL
UNION ALL SELECT 3, 'ComC' FROM DUAL;
CREATE VIEW TEST AS
SELECT A.UserID,
MAX( A.Area ) AS Area,
MAX( CASE WHEN I.Info LIKE '%_Info1' THEN I.Info END ) AS Info1,
MAX( CASE WHEN I.Info LIKE '%_Info2' THEN I.Info END ) AS Info2,
MAX( CASE WHEN I.Info LIKE '%_Info3' THEN I.Info END ) AS Info3,
MAX( C.Company ) AS Company
FROM Area A
INNER JOIN
Company C
ON ( A.UserID = C.UserID )
LEFT OUTER JOIN
Info I
ON ( A.UserID = I.UserID )
GROUP BY
A.UserID
Query 1:
SELECT * FROM test
Results:
| USERID | AREA | INFO1 | INFO2 | INFO3 | COMPANY |
|--------|-------|----------|----------|----------|---------|
| 1 | 10001 | U1_Info1 | U1_Info2 | U1_Info3 | ComA |
| 2 | 10002 | U2_Info1 | (null) | (null) | ComB |
| 3 | 10003 | U3_Info1 | (null) | (null) | ComC |

Fetching one to many relationship from same table

I have to pull one to many relationship data from the same table.
The structure of the table is like
____________________________________
| CUSTOMER_ID | SUBSCRIPTION_NAME |
-------------------------------------
| 1 | ABC |
| 1 | TNT |
| 1 | AMC |
| 2 | ABC |
| 2 | USA |
| 3 | TNT |
| 3 | AMC |
-------------------------------------
I would like to get the output like
| CUSTOMER_ID | SUBSCRIPTION_NAME |
| 1 | ABC,TNT,AMC |
| 2 | ABC,USA |
| 3 | TNT,AMC |
SELECT customer_id,
LISTAGG(subscription_name, '; ') WITHIN GROUP (ORDER BY subscription_name) AS subscription_name
FROM subscription
GROUP BY customer_id
ORDER BY customer_id;
Order by is optional
Just few alternatives to LISTAGG:
SQL> with t (CUSTOMER_ID, SUBSCRIPTION_NAME)
2 as (
3 select 1, 'ABC' from dual union all
4 select 1, 'TNT' from dual union all
5 select 1, 'AMC' from dual union all
6 select 2, 'ABC' from dual union all
7 select 2, 'USA' from dual union all
8 select 3, 'TNT' from dual union all
9 select 3, 'AMC' from dual
10 )
11 SELECT CUSTOMER_ID,
12 RTRIM(
13 XMLAGG (XMLELEMENT(e, SUBSCRIPTION_NAME||',') ORDER BY SUBSCRIPTION_NAME).EXTRACT('//text()')
14 ,',') AS x
15 FROM t
16 GROUP BY CUSTOMER_ID
17 /
CUSTOMER_ID X
----------- ----------------------------------------
1 ABC,AMC,TNT
2 ABC,USA
3 AMC,TNT
SQL> with t (CUSTOMER_ID, SUBSCRIPTION_NAME)
2 as (
3 select 1, 'ABC' from dual union all
4 select 1, 'TNT' from dual union all
5 select 1, 'AMC' from dual union all
6 select 2, 'ABC' from dual union all
7 select 2, 'USA' from dual union all
8 select 3, 'TNT' from dual union all
9 select 3, 'AMC' from dual
10 )
11 select customer_id,
12 max(substr(sys_connect_by_path( SUBSCRIPTION_NAME, ','),2)) x from
13 (
14 select customer_id, SUBSCRIPTION_NAME, row_number() over(partition by customer_id order by null) rn
15 from t
16 )
17 start with rn=1
18 connect by prior customer_id = customer_id and rn = prior rn+1
19 group by customer_id
20 /
CUSTOMER_ID X
----------- ----------------------------------------
1 ABC,TNT,AMC
2 ABC,USA
3 TNT,AMC