Oracle Hierarchical Queries - oracle

I am struggling with a query with the below requirement:
Table A
ID Name Key
1 A1 Key1
2 A2 Key2
3 A3 Key3
Table B
ID A_ID NAME CONTAINER_A_ID
1 1 B1 NULL
2 1 B2 NULL
3 1 B3 2
4 2 B4 NULL
5 2 B5 NULL
6 3 B6 NULL
7 3 B7 NULL
The Key column in table A is unique
The A_ID column in table B is a foreign key of table A
The CONTAINER_A_ID column in table B means the row in table B can be a
container, it contains other data rows indicated by the CONTAINER_A_ID value.
Below is the example:
the input parameter is table A key column value, let's say A.Key = 'key1', and the result based on the above sample data will be:
A.ID A.NAME A.KEY B.ID B.A_ID B.NAME B.CONTAINER_A_ID
1 A1 KEY1 1 1 B1 NULL
1 A1 KEY1 2 1 B2 NULL
1 A1 KEY1 3 1 B3 2
2 A2 KEY2 4 2 B4 NULL
2 A2 KEY2 5 2 B5 NULL
if the input parameter is A.Key = 'key2', then the result will be:
A.ID A.NAME A.KEY B.ID B.A_ID B.NAME B.CONTAINER_A_ID
2 A2 KEY2 4 2 B4 NULL
2 A2 KEY2 5 2 B5 NULL
Thanks

This is on Oracle 11g.
If you are specifically looking for CONNECT BY I am not aware of that yet.
drop table t1; drop table t2;
create table t1 (id int primary key, name char(5), key char(5));
create table t2 (id int primary key, a_id int, name char(5) , container int);
insert into t1 values (1, 'A1', 'K1');
insert into t1 values (2, 'A2', 'K2');
insert into t1 values (3, 'A3', 'K3');
insert into t2 values (1, 1, 'B1', null);
insert into t2 values (2, 1, 'B2', null);
insert into t2 values (3, 1, 'B3', 2);
insert into t2 values (4, 2, 'B4', null);
insert into t2 values (5, 2, 'B5', null);
insert into t2 values (6, 3, 'B6', null);
insert into t2 values (7, 3, 'B7', null);
with t(id, name, key, bid, aid, bname, con) as (
select a.id, a.name, a.key, b.id, b.a_id, b.name, b.container
from t1 a
inner join
t2 b
on a.id = b.a_id
where a.key = 'K1'
union all
select a.id, a.name, a.key, b.id, b.a_id, b.name, b.container
from t t
inner join
t1 a
on a.id = t.con
inner join
t2 b
on a.id = b.a_id
) select * from t;
EDIT: Reponse to Jorge's comment
insert into t2 values (4, 2, 'B4', 3);

This is for Hierarchical Query
with TableA as
(
select 1 id, 'A1' Name, 'Key1' key from dual union all
select 2, 'A2', 'Key2' from dual union all
select 3, 'A3', 'Key3' from dual
)
, tableb as
(
select 1 id, 1 a_id, 'B1' name , null CONTAINER_A_ID from dual union all
select 2 , 1 , 'B2' , null from dual union all
select 3 , 1 , 'B3' , 2 from dual union all
select 4 , 2 , 'B4' , null from dual union all
select 5 , 2 , 'B5' , null from dual union all
select 6 , 3 , 'B6' , null from dual union all
select 7 , 3 , 'B7' , null from dual
)
select
a.id, a.name, a.key, b.id, b.a_id, b.name, b.container_a_id
from
tableb b
left join
tablea a
on
a.id = b.a_id
start with
A.Key = 'Key1'
connect by
prior b.container_a_id = b.a_id;
If you need order then add order by a.id, b.id,a.name,...; to the end.

CTEs are fine to use in 11g Oracle. I just saw Jorge is in before me. It is easier to see how the recursion works if you just use tableB in the CTE and then join to the CTE to get all the fields, like this
with
recurse (a_id, b_id, parent_id)
as
(select a_id, id, container_a_id as parent_id
from tableB
WHERE A_ID = 1 -- Put your parameter here
union all
select b.a_id, b.id, b.container_a_id
from recurse r, tableB b
where b.a_id = r.parent_id
)
select r.a_id, a.name, a.key, b.id, b.a_id, b.name, b.container_a_id
from recurse r, tableA a, tableB b
where r.a_id = a.id and r.b_id = b.id
;
This gets the same results, but although you have to use a_id and not a_key for the condition, it is a little bit easier to understand the recursion.
So, leaving this here in case it helps someone to understand a bit about CTEs.

Related

split data in a single column into multiple columns in oracle

my_table :
Name
Value
item_1
AB
item_2
2
item_3
B1
item_1
CD
item_1
EF
item_2
3
item_3
B2
item_4
ZZ
required output:
item_1
item_2
item_3
item_4
AB
2
B1
ZZ
CD
3
B2
NULL
EF
NULL
NULL
NULL
SQL query :
with item_1 as (select value from my_table where name = 'item_1'),
item_2 as (select value from my_table where name = 'item_2'),
item_3 as (select value from my_table where name = 'item_3'),
item_4 as (select value from my_table where name = 'item_4')
select item_1.value, item_2.value,item_3.value, item_4.value from item_1 cross join item_2 cross join item_3 cross join item_4;
If I am using pivot along with MAX aggregate function, the query will display only max values of the corresponding items instead of displaying all the values.
Is there any way to split a single column into multiple columns(using where condition as mentioned in the above query) without cross join.
Use ROW_NUMBER and then PIVOT:
SELECT item_1,
item_2,
item_3,
item_4
FROM (
SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY name ORDER BY ROWNUM) AS rn
FROM table_name t
)
PIVOT (
MAX(value) FOR name IN (
'item_1' AS item_1,
'item_2' AS item_2,
'item_3' AS item_3,
'item_4' AS item_4
)
)
Which, for the sample data:
CREATE TABLE table_name (Name, Value) AS
SELECT 'item_1', 'AB' FROM DUAL UNION ALL
SELECT 'item_2', '2' FROM DUAL UNION ALL
SELECT 'item_3', 'B1' FROM DUAL UNION ALL
SELECT 'item_1', 'CD' FROM DUAL UNION ALL
SELECT 'item_1', 'EF' FROM DUAL UNION ALL
SELECT 'item_2', '3' FROM DUAL UNION ALL
SELECT 'item_3', 'B2' FROM DUAL UNION ALL
SELECT 'item_4', 'ZZ' FROM DUAL;
Outputs:
ITEM_1
ITEM_2
ITEM_3
ITEM_4
AB
2
B1
ZZ
CD
3
B2
null
EF
null
null
null
db<>fiddle here
How about this?
DF column is calculated by row_number analytic function which partitions by each name (and sorts by value). It is ignored from the final column list, but its role is crucial in the GROUP BY clause.
SQL> with test (name, value) as
2 (select 'item_1', 'AB' from dual union all
3 select 'item_2', '2' from dual union all
4 select 'item_3', 'B1' from dual union all
5 select 'item_1', 'CD' from dual union all
6 select 'item_1', 'EF' from dual union all
7 select 'item_2', '3' from dual union all
8 select 'item_3', 'B2' from dual union all
9 select 'item_4', 'ZZ' from dual
10 ),
11 temp as
12 (select name, value,
13 row_number() over (partition by name order by value) df
14 from test
15 )
16 select
17 max(case when name = 'item_1' then value end) item_1,
18 max(case when name = 'item_2' then value end) item_2,
19 max(case when name = 'item_3' then value end) item_3,
20 max(case when name = 'item_4' then value end) item_4
21 from temp
22 group by df;
ITEM_1 ITEM_2 ITEM_3 ITEM_4
------ ------ ------ ------
AB 2 B1 ZZ
CD 3 B2
EF
SQL>

Query to join Oracle tables

I have a 'MASTER' table as shown below:
Entity Cat1 Cat2
A Mary;Steve Jacob
B Alex;John Sally;Andrew
Another table 'PERSON' has associations of person's name (this could be InfoID as well) with emails.
Name Email InfoID
Mary maryD#gmail.com mryD
Steve steveR#gmail.com stvR
Jacob jacobB#gmail.com jacbb
Sally sallyD#gmail.com sallD
Alex AlexT#gmail.com alexT
John JohnP#gmail.com johP
Andrew AndrewV#gmail.com andV
I want to join the person table with master such as:
Entity Cat1 EmailCat1 Cat2 EmailCat2
A Mary;Steve maryD#gmail.com;steveR#gmail.com Jacob jacobB#gmail.com
B Alex;John AlexT#gmail.com;JohnP#gmail.com Sally;Andrew sallyD#gmail.com;AndrewV#gmail.com
any insights on how to go about it?
Honestly, your master table design needs to be normalized. But, in the meantime you could try this query below :
with
needed_rows_for_cat1_tab (lvl) as (
select level from dual
connect by level <= (select max(regexp_count(Cat1, ';')) from Your_bad_master_tab) + 1
)
, needed_rows_for_cat2_tab (lvl) as (
select level from dual
connect by level <= (select max(regexp_count(Cat2, ';')) from Your_bad_master_tab) + 1
)
, split_cat1_val_tab as (
select Entity, Cat1
, substr(Cat1||';'
, lag(pos, 1, 0)over(partition by Entity order by lvl) + 1
, pos - lag(pos, 1, 0)over(partition by Entity order by lvl) - 1
) val
, lvl
, pos
, 1 cat
from (
select Entity, Cat1, instr(Cat1||';', ';', 1, r1.lvl)pos, r1.lvl
from Your_bad_master_tab c1
join needed_rows_for_cat1_tab r1 on r1.lvl <= regexp_count(Cat1, ';') + 1
)
)
, split_cat2_val_tab as (
select Entity, Cat2
, substr(Cat2||';'
, lag(pos, 1, 0)over(partition by Entity order by lvl) + 1
, pos - lag(pos, 1, 0)over(partition by Entity order by lvl) - 1
) val
, lvl
, pos
, 2 cat
from (
select Entity, Cat2, instr(Cat2||';', ';', 1, r2.lvl)pos, r2.lvl
from Your_bad_master_tab c1
join needed_rows_for_cat2_tab r2 on r2.lvl <= regexp_count(Cat2, ';') + 1
)
)
select ENTITY
, max(decode(cat, 1, CAT1, null)) CAT1
, listagg(decode(cat, 1, EMAIL, null), ';')within group (order by lvl) EmailCat1
, max(decode(cat, 2, CAT1, null)) CAT2
, listagg(decode(cat, 2, EMAIL, null), ';')within group (order by lvl) EmailCat2
from (
select c.*, p.Email
from split_cat1_val_tab c join Your_person_tab p on (c.val = p.name)
union all
select c.*, p.Email
from split_cat2_val_tab c join Your_person_tab p on (c.val = p.name)
)
group by ENTITY
;
Here are your sample data
--drop table Your_bad_master_tab purge;
create table Your_bad_master_tab (Entity, Cat1, Cat2) as
select 'A', 'Mary;Steve', 'Jacob' from dual union all
select 'B', 'Alex;John', 'Sally;Andrew' from dual
;
--drop table Your_person_tab purge;
create table Your_person_tab (Name, Email, InfoID) as
select 'Mary', 'maryD#gmail.com' ,'mryD' from dual union all
select 'Steve', 'steveR#gmail.com' ,'stvR' from dual union all
select 'Jacob', 'jacobB#gmail.com' ,'jacbb' from dual union all
select 'Sally', 'sallyD#gmail.com' ,'sallD' from dual union all
select 'Alex', 'AlexT#gmail.com' ,'alexT' from dual union all
select 'John', 'JohnP#gmail.com' ,'johP' from dual union all
select 'Andrew', 'AndrewV#gmail.com' ,'andV' from dual
;
DB<>fiddle

Joining 2 tables for value comparision

I have 2 tables which contain data as follows:
Table1-
Col1. Col2
A1. B1
A1. B2
Table2-
Col1. Col2
A1. C1
A1. C2
Now when I am joining these 2 tables to compare col2, sometimes I get the output like A1-B1-C1;A1-B2-C2 and some time I get it as A1-B1-C2;A1-B2-C1.
But I don't want it to come in the second form ever.can anyone suggest any query which will help me achieve this.
Thanks in advance
You can do it with ROW_NUMBER() window function:
select t1.col1, t1.col2, t2.col2
from (
select t.*, row_number() over (partition by col1 order by col2) rn
from table1 t
) t1 inner join (
select t.*, row_number() over (partition by col1 order by col2) rn
from table2 t
) t2 on t2.col1 = t1.col1 and t2.rn = t1.rn
See the demo.
Results:
> COL1 | COL2 | COL2
> :--- | :--- | :---
> A1 | B1 | C1
> A1 | B2 | C2
The most obvious join is on col1 column which returns 4 rows:
SQL> with
2 t1 (col1, col2) as
3 (select 'A1', 'B1' from dual union all
4 select 'A1', 'B2' from dual
5 ),
6 t2 (col1, col2) as
7 (select 'A1', 'C1' from dual union all
8 select 'A1', 'C2' from dual
9 )
10 select a.col1, a.col2 a_col2, b.col2 b_col2
11 from t1 a join t2 b on a.col1 = b.col1
12 order by a.col1, a.col2, b.col2;
COL1 A_COL2 B_COL2
------- ------- -------
A1 B1 C1
A1 B1 C2
A1 B2 C1
A1 B2 C2
SQL>
These are all combinations you named, so - how do you "sometimes" get only two of them, and "sometimes" another two? What's wrong with another two results (as you said you don't want them to show at all)?
Yet another option is to use the RANK windows function as follows:
SELECT T1C1, T1C2, T2C2 FROM
(SELECT T1.COL1 T1C1, T1.COL2 T1C2, T2.COL1 T2C1, T2.COL2 T2C2,
RANK() OVER (PARTITION BY T1.COL1 ORDER BY T1.COL2) AS T1RN,
RANK() OVER (PARTITION BY T1.COL1 ORDER BY T2.COL2) AS T2RN
FROM TABLE1 T1 JOIN TABLE2 T2 ON T1.COL1 = T2.COL1)
WHERE T1RN = T2RN;

Get multiple data into one colomn Oracle Query

I have 3 tables: table_A, table_B and table_C. Table_A has a Primary key and is referred by a foreign key from table_B. Table_C has a primary key referred by a foreign key from table_B. The design is like this:
Table_A:
ID_A
TextData
Table_B:
ID_B
ID_A
ID_C
Table C:
ID_C
TextData
I want to join between 3 tables like this:
select A.ID_A, A.TextData as DataA,
(
select C.TextData
from Table_B B, Table_C C
where B.ID_C = C.ID_C and B.ID_A = C.ID_A
) as Data_C
from Table_A;
I know that it should be an error if I try to compile it with error like: return more than one elements.
But my client want me to join all data from table C into one row, all i know using concate to every data. But i don't know how to do it. I never try to create function or package on oracle. Can you help me how to fix my query?
The result should be like:
ID_A | DataA | Data_C
1 texta text1, text2, text8
2 textb text2, text3, text9
3 textc text1, text8, text9
You can try with this:
SETUP
create table table_A(ID_A number, TextData varchar2(100))
/
create table Table_B( ID_B number, ID_A number, ID_C number)
/
create table Table_C(ID_C number, TextData varchar2(100))
/
insert into table_A values (1, 'texta');
insert into table_A values (2, 'textb');
insert into table_A values (3, 'textc');
--
insert into table_C values (1, 'text1');
insert into table_C values (2, 'text2');
insert into table_C values (3, 'text3');
insert into table_C values (8, 'text8');
insert into table_C values (9, 'text9');
--
insert into table_b values (11, 1, 1);
insert into table_b values (12, 1, 2);
insert into table_b values (18, 1, 8);
insert into table_b values (22, 2, 2);
insert into table_b values (23, 2, 3);
insert into table_b values (29, 2, 9);
insert into table_b values (31, 3, 1);
insert into table_b values (38, 3, 8);
insert into table_b values (39, 3, 8);
QUERY:
select id_a, a.textData as DataA, listagg(c.textData, ', ') within group (order by c.textData) as Data_c
from table_A a
inner join table_B b
using(id_A)
inner join table_c c
using(id_c)
group by id_a, a.textData
XMLAGG or similar is what you need. Something like (not tested, but should give you a hint):
select A.ID_A, A.TextData as DataA,
(
select XMLELEMENT("Thedata",XMLAGG("textdata",C.TextData)) as td
from Table_B B, Table_C C
where B.ID_C = C.ID_C and B.ID_A = C.ID_A
) as Data_C
from Table_A;
Use REPLACE/TRANSLATE/REGEXP_REPLACE etc to strip out the XML tags if required.
Loads of example about e.g. http://www.dba-oracle.com/t_converting_rows_columns.htm .
May be you should write your aggregate. Funcrion to concat your strings user defined aggregates
Or use undocumented wm_concat. Thus function deprecated in 12c.
If you're on Oracle 11g or higher, you can use LISTAGG:
with table_a as (select 1 id_a, 'texta' textdata from dual union all
select 2 id_a, 'textb' textdata from dual union all
select 3 id_a, 'textc' textdata from dual),
table_b as (select 1 id_b, 1 id_a, 1 id_c from dual union all
select 2 id_b, 1 id_a, 2 id_c from dual union all
select 3 id_b, 1 id_a, 4 id_c from dual union all
select 4 id_b, 2 id_a, 2 id_c from dual union all
select 5 id_b, 2 id_a, 3 id_c from dual union all
select 6 id_b, 2 id_a, 5 id_c from dual union all
select 7 id_b, 3 id_a, 1 id_c from dual union all
select 8 id_b, 3 id_a, 4 id_c from dual union all
select 9 id_b, 3 id_a, 5 id_c from dual),
table_c as (select 1 id_c, 'text1' textdata from dual union all
select 2 id_c, 'text2' textdata from dual union all
select 3 id_c, 'text3' textdata from dual union all
select 4 id_c, 'text8' textdata from dual union all
select 5 id_c, 'text9' textdata from dual)
-- end of mimicking your tables; see sql below.
select a.id_a,
a.textdata,
listagg(c.textdata, ', ') within group (order by c.id_c) data_c
from table_a a
inner join table_b b on (a.id_a = b.id_a)
inner join table_c c on (b.id_c = c.id_c)
group by a.id_a, a.textdata;
ID_A TEXTDATA DATA_C
---------- -------- --------------------
1 texta text1, text2, text8
2 textb text2, text3, text9
3 textc text1, text8, text9

How to do a Full Outer Join using old style Oracle syntax

I have two tables with the same columns, Item Code and Qty, for each Table:
TABLE A TABLE B
-------------- -------------
X 2 X 1
Y 1 S 2
Z 5 Z 5
The result that I am aiming to get is something like this:
Table C
---------------
X 2 1
Y 1 0
S 0 2
I only need the items where qty differs in both tables (including the nulls which should be shown as zeroes.
Note: I am using Oracle8 so I can't use the ANSI FULL OUTER JOIN.
Edit, Since the question is specific to Oracle 8 which does not use ANSI syntax, the following should work:
select col1,
nvl(a_col2, 0) as a_col2,
nvl(b_col2, 0) as b_col2
from
(
select a.col1, a.col2 as a_col2, b.col2 as b_col2
from TableA a, TableB b
where a.col1 = b.col1(+)
union
select b.col1, a.col2 as a_col2, b.col2 as b_col2
from TableA a, TableB b
where a.col1(+) = b.col1
)
where a_col2 <> b_col2
or (a_col2 is null or b_col2 is null)
See SQL Fiddle with Demo. This will return:
| COL1 | A_COL2 | B_COL2 |
--------------------------
| S | 0 | 2 |
| X | 2 | 1 |
| Y | 1 | 0 |
If you are using a version of Oracle that supports ANSI syntax then you can use the following FULL OUTER JOIN:
select
coalesce(a.col1, b.col1) col1,
coalesce(a.col2, 0) a_col2,
coalesce(b.col2, 0) b_col2
from tablea a
full outer join tableb b
on a.col1 = b.col1
where a.col2 <> b.col2
or (a.col2 is null or b.col2 is null);
See SQL Fiddle with Demo
Another writing of the query which should work in 8 and (probably earlier versions).
It uses neither FULL JOIN not the horrible (+) syntax for joins so it should work even when an upgrade deprecates it.
Assuming that there are no Nulls already on the tables, you won't need COALESCE() or NVL() either:
SELECT a.col1,
a.col2 AS a_col2,
b.col2 AS b_col2
FROM TableA a, TableB b
WHERE a.col1 = b.col1
AND ( a.col2 <> b.col2
OR a.col2 IS NULL
OR b.col2 IS NULL
)
UNION ALL
SELECT col1, col2, 0
FROM TableA a
WHERE NOT EXISTS
( SELECT *
FROM TableB b
WHERE a.col1 = b.col1
)
UNION ALL
SELECT col1, 0, col2
FROM TableB b
WHERE NOT EXISTS
( SELECT *
FROM TableA a
WHERE a.col1 = b.col1
) ;
Tests at SQL-Fiddle
select code, nvl(a.qty,0) a, nvl(b.qty,0) b
from tableA a full join tableB b using(code)
where decode(a.qty, b.qty, 0) is null
fiddle
Another option:
select
full_list.item_code,
nvl(table_a.qty,0) table_a_qty,
nvl(table_b.qty,0) table_b_qty
from
(select item_code from table_a
union
select item_code from table_b) full_list,
table_a,
table_b
where
full_list.item_code = table_a.item_code(+) and
full_list.item_code = table_b.item_code(+)
The following query should work just right:
SELECT * FROM (
SELECT nvl(a.c1, b.c2), nvl(a.col1, 0) qty1, nvl(b.col2, 0) qty2 FROM a FULL OUTER JOIN b ON a.c1 = b.c2
) where qty1 != qty2;
http://sqlfiddle.com/#!4/d37ff/5/0

Resources