Oracle table hierarchy level and grouping - sql

I have a oracle database where foreign key is enabled and I have tables A,B,C,D,E,F,AB,ABC,EF
Attaching the picture for hierarchy
'B' has parent 'D',
'AB' has parent 'A' as well as 'B',
'ABC' has parent 'AB' and 'C',
'EF' has parent 'E' & 'F',
Now {A,B,C,D,AB, ABC} has no link with {E,F,EF}. I have 2 requirements
I need to group them as group 1 & 2
I need to assign a hierarchy level for each group maintaining referential integrity as follows
How can I do it using sql/pl/sql in oracle database?

Assuming that you always end up at a single leaf node for each group then you can use a hierarchical query twice: from leaf-to-root to determine the groups; and then from root-to-leaf to determine the depths.
WITH groups ( child, parent, grp ) AS (
SELECT child,
parent,
DENSE_RANK() OVER ( ORDER BY CONNECT_BY_ROOT( child ) )
FROM table_name t
START WITH child NOT IN ( SELECT parent FROM table_name )
CONNECT BY PRIOR parent = child
),
depths ( child, parent, grp, depth ) AS (
SELECT child, parent, grp, LEVEL
FROM groups
START WITH parent NOT IN ( select child FROM table_name )
CONNECT BY PRIOR child = parent
)
SELECT table_name,
grp,
MAX( depth + depth_modifier ) AS depth
FROM depths
UNPIVOT ( table_name FOR depth_modifier IN ( child AS 1, parent AS 0 ) )
GROUP BY grp, table_name
ORDER BY grp, depth, table_name
Which, for your sample data:
CREATE TABLE table_name ( child, parent ) AS
SELECT 'B', 'D' FROM DUAL UNION ALL
SELECT 'AB', 'A' FROM DUAL UNION ALL
SELECT 'AB', 'B' FROM DUAL UNION ALL
SELECT 'ABC', 'AB' FROM DUAL UNION ALL
SELECT 'ABC', 'C' FROM DUAL UNION ALL
SELECT 'EF', 'E' FROM DUAL UNION ALL
SELECT 'EF', 'F' FROM DUAL;
Outputs:
TABLE_NAME | GRP | DEPTH
:--------- | --: | ----:
A | 1 | 1
C | 1 | 1
D | 1 | 1
B | 1 | 2
AB | 1 | 3
ABC | 1 | 4
E | 2 | 1
F | 2 | 1
EF | 2 | 2
If you don't always have a single leaf node for each group then you'll need a MUCH more complicated solution (probably written in PL/SQL).
db<>fiddle here

Related

Oracle SQL: How to get the nth node between the root and any node in a tree (Optimizing)

I'm trying to find the Nth node from the root starting at a given node. Return the given node if it is less than or equal to the Nth node from the root. My query works, but it's slow and I'm not happy with it. Any ideas on how to refactor this to improve performance?
test_data
---------
unique_id | lookup_acct_code
'1' 'leaf-1'
'2' 'stem-2'
'3' 'branch-1'
'4' 'trunk-2'
'5' 'root-1'
linked_accounts
---------------
acct_code | parent_code
'leaf-1' 'stem-1'
'stem-1' 'twig-1'
'twig-1' 'stick-1'
'stick-1' 'branch-1'
'branch-1' 'trunk-1'
'trunk-1' 'root-1'
'root-1' NULL
'leaf-2' 'stem-2'
'stem-2' 'twig-2'
'twig-2' 'stick-2'
'stick-2' 'branch-2'
'branch-2' 'trunk-2'
'trunk-2' 'root-2'
'root-2' NULL
SELECT unique_id,
(SELECT acct_code
FROM (SELECT acct_code, ROW_NUMBER() OVER (ORDER BY level desc) rn
FROM linked_accounts
CONNECT BY acct_code = PRIOR parent_code
START WITH acct_code = lookup_acct_code)
WHERE rn <= 3
ORDER BY rn DESC
FETCH FIRST 1 ROWS ONLY)
FROM test_data;
/* Correct output
1 branch-1
2 branch-2
3 branch-1
4 trunk-2
5 root-1
*/
You can generate value up to the nth node from the root first (using a recursive sub-query factoring clause instead of a hierarchical query) and then join rather than using a correlated sub-query:
WITH from_roots ( acct_code, depth, nth_from_root ) AS (
SELECT acct_code,
1,
acct_code
FROM linked_accounts
WHERE parent_code IS NULL
UNION ALL
SELECT l.acct_code,
depth + 1,
CASE
WHEN depth >= 3
THEN nth_from_root
ELSE l.acct_code
END
FROM linked_accounts l
INNER JOIN from_roots f
ON ( f.acct_code = l.parent_code )
)
SELECT unique_id, nth_from_root
FROM from_roots f
INNER JOIN test_data d
ON ( d.lookup_acct_code = f.acct_code )
or, you could adapt your query to use MAX(LEVEL) OVER () - LEVEL + 1 to directly calculate the depth:
SELECT unique_id,
( SELECT acct_code
FROM (
SELECT acct_code, MAX(LEVEL) OVER () - LEVEL + 1 AS depth
FROM linked_accounts
CONNECT BY acct_code = PRIOR parent_code
START WITH acct_code = lookup_acct_code
)
WHERE depth <= 3
ORDER BY depth DESC
FETCH FIRST 1 ROW ONLY
) AS nth_node
FROM test_data;
Which, for your sample data:
CREATE TABLE test_data ( unique_id, lookup_acct_code ) AS
SELECT '1', 'leaf-1' FROM DUAL UNION ALL
SELECT '2', 'stem-2' FROM DUAL UNION ALL
SELECT '3', 'branch-1' FROM DUAL UNION ALL
SELECT '4', 'trunk-2' FROM DUAL UNION ALL
SELECT '5', 'root-1' FROM DUAL;
CREATE TABLE linked_accounts ( acct_code, parent_code ) AS
SELECT 'leaf-1', 'stem-1' FROM DUAL UNION ALL
SELECT 'stem-1', 'twig-1' FROM DUAL UNION ALL
SELECT 'twig-1', 'stick-1' FROM DUAL UNION ALL
SELECT 'stick-1', 'branch-1' FROM DUAL UNION ALL
SELECT 'branch-1', 'trunk-1' FROM DUAL UNION ALL
SELECT 'trunk-1', 'root-1' FROM DUAL UNION ALL
SELECT 'root-1', NULL FROM DUAL UNION ALL
SELECT 'leaf-2', 'stem-2' FROM DUAL UNION ALL
SELECT 'stem-2', 'twig-2' FROM DUAL UNION ALL
SELECT 'twig-2', 'stick-2' FROM DUAL UNION ALL
SELECT 'stick-2', 'branch-2' FROM DUAL UNION ALL
SELECT 'branch-2', 'trunk-2' FROM DUAL UNION ALL
SELECT 'trunk-2', 'root-2' FROM DUAL UNION ALL
SELECT 'root-2', NULL FROM DUAL;
Both output:
UNIQUE_ID | NTH_FROM_ROOT
:-------- | :------------
1 | branch-1
2 | branch-2
3 | branch-1
4 | trunk-2
5 | root-1
You would need to profile all the solutions on a larger data-set to see which solution is more performant.
db<>fiddle here
You can try to avoid scalar subquery by using CONNECT_BY_ROOT function to get the unique_id you've started from. And do the same filtering based on maximum level found after the tree was build. Try this:
with hier as (
select
/*Find unique ID of subtree*/
connect_by_root(f.unique_id) as unique_id,
connect_by_root(la.acct_code) as start_node,
la.acct_code,
/*Find max depth of subtree*/
max(level) over(partition by connect_by_root(f.unique_id)) as max_lvl,
/*Find current reversed depth of node*/
level as lvl
from linked_accounts la
left join test_data f
on la.acct_code = f.lookup_acct_code
start with f.unique_id is not null
connect by prior la.parent_code = la.acct_code
)
select
unique_id,
case max_lvl
when lvl
/*Select our start node for short subtree*/
then start_node
/*And node al level 3 otherwise*/
else acct_code
end as node_name
from hier
/*Select the node on level 3 from the root*/
where max_lvl - lvl = 2
/*Or the last node in subtree for the depth < 3*/
or (max_lvl <= 2 and lvl = max_lvl)
order by 1 asc
UNIQUE_ID | NODE_NAME
:-------- | :--------
1 | branch-1
2 | branch-2
3 | branch-1
4 | trunk-2
5 | root-1
db<>fiddle here

determine no of layers of parent child relationship

Below is how data is structured in my table:
Notes:
P - indicates the parent
c - indicates the parent
A parent can have a parent ( having a child)
but a child can NOT have a child or either parent. Determine the layer/generations.
Column1,Column2
1-p,2-p:
2-p,3-p:
2-p,4-c:
3-p,5-c
how to read the above data:
1 has a child named 2;
2 has child 3 & 4;
3 has child 5.
So based on the above logic '1' has 3 layers.
How to write a query to determine no of layers in Oracle?
You can use a hierarchical query:
SELECT column1,
MAX( depth ) AS max_depth
FROM (
SELECT CONNECT_BY_ROOT( column1 ) AS column1,
LEVEL AS depth
FROM table_name
WHERE CONNECT_BY_ISLEAF = 1
CONNECT BY
PRIOR Column2 = Column1
)
GROUP BY
column1;
Which, for the sample data:
CREATE TABLE table_name ( Column1, Column2 ) AS
SELECT '1-p','2-p' FROM DUAL UNION ALL
SELECT '2-p','3-p' FROM DUAL UNION ALL
SELECT '2-p','4-c' FROM DUAL UNION ALL
SELECT '3-p','5-c' FROM DUAL;
Outputs:
COLUMN1 | MAX_DEPTH
:------ | --------:
1-p | 3
3-p | 1
2-p | 2
db<>fiddle here

Select rows when a value appears multiple times

I have a table like this one:
+------+------+
| ID | Cust |
+------+------+
| 1 | A |
| 1 | A |
| 1 | B |
| 1 | B |
| 2 | A |
| 2 | A |
| 2 | A |
| 2 | B |
| 3 | A |
| 3 | B |
| 3 | B |
+------+------+
I would like to get the IDs that have at least two times A and two times B. So in my example, the query should return only the ID 1,
Thanks!
In MySQL:
SELECT id
FROM test
GROUP BY id
HAVING GROUP_CONCAT(cust ORDER BY cust SEPARATOR '') LIKE '%aa%bb%'
In Oracle
WITH cte AS ( SELECT id, LISTAGG(cust, '') WITHIN GROUP (ORDER BY cust) custs
FROM test
GROUP BY id )
SELECT id
FROM cte
WHERE custs LIKE '%aa%bb%'
I would just use two levels of aggregation:
select id
from (select id, cust, count(*) as cnt
from t
where cust in ('A', 'B')
group by id, cust
) ic
group by id
having count(*) = 2 and -- both customers are in the result set
min(cnt) >= 2 -- and there are at least two instances
This is one option; lines #1 - 13 represent sample data. Query you might be interested in begins at line #14.
SQL> with test (id, cust) as
2 (select 1, 'a' from dual union all
3 select 1, 'a' from dual union all
4 select 1, 'b' from dual union all
5 select 1, 'b' from dual union all
6 select 2, 'a' from dual union all
7 select 2, 'a' from dual union all
8 select 2, 'a' from dual union all
9 select 2, 'b' from dual union all
10 select 3, 'a' from dual union all
11 select 3, 'b' from dual union all
12 select 3, 'b' from dual
13 )
14 select id
15 from (select
16 id,
17 sum(case when cust = 'a' then 1 else 0 end) suma,
18 sum(case when cust = 'b' then 1 else 0 end) sumb
19 from test
20 group by id
21 )
22 where suma = 2
23 and sumb = 2;
ID
----------
1
SQL>
You can use group by and having for the relevant Cust ('A' , 'B')
And query twice (I chose to use with to avoid multiple selects and to cache it)
with more_than_2 as
(
select Id, Cust, count(*) c
from tab
where Cust in ('A', 'B')
group by Id, Cust
having count(*) >= 2
)
select *
from tab
where exists ( select 1 from more_than_2 where more_than_2.Id = tab.Id and more_than_2.Cust = 'A')
and exists ( select 1 from more_than_2 where more_than_2.Id = tab.Id and more_than_2.Cust = 'B')
What you want is a perfect candidate for match_recognize. Here you go:
select id_ as id from t
match_recognize
(
order by id, cust
measures id as id_
pattern (A {2, } B {2, })
define A as cust = 'A',
B as cust = 'B'
)
Output:
Regards,
Ranagal

Connect by prior get parents and children

I'm working on a query which use connect by prior.
I have written a query which retrieves all children of an entity. What I want is to retrieve both children and parents rows.
Here is my SQL :
Select *
From myTable tab
Connect By Prior tab.id= tab.child_id
Start With tab.id= 2;
How could I retrieve parents ?
Thanks.
Use UNION ALL to combine a query to get the children with another to get the ancestors.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE myTable ( id, child_id ) AS
SELECT 0, 1 FROM DUAL UNION ALL
SELECT 1, 2 FROM DUAL UNION ALL
SELECT 2, 3 FROM DUAL UNION ALL
SELECT 3, 4 FROM DUAL UNION ALL
SELECT 4, 5 FROM DUAL UNION ALL
SELECT 3, 6 FROM DUAL UNION ALL
SELECT 0, 7 FROM DUAL UNION ALL
SELECT 1, 8 FROM DUAL;
Query 1:
SELECT * -- Child Rows
FROM mytable
START WITH id = 2
CONNECT BY PRIOR child_id = id
UNION ALL
SELECT * -- Ancestor Rows
FROM mytable
START WITH child_id = 2
CONNECT BY PRIOR id = child_id
Results:
| ID | CHILD_ID |
|----|----------|
| 2 | 3 |
| 3 | 4 |
| 4 | 5 |
| 3 | 6 |
| 1 | 2 |
| 0 | 1 |
An alternative approach to a hierarchical query, if you're on 11gR2 or higher, is recursive subquery factoring:
with rcte (id, child_id, some_other_col) as (
select id, child_id, some_other_col -- whichever columns you're interested in
from myTable
where id = 2
union all
select t.id, t.child_id, t.some_other_col -- whichever columns you're interested in
from rcte r
join myTable t
on t.id = r.child_id -- match parents
or t.child_id = r.id -- match children
)
cycle id set is_cycle to 1 default 0
select id, child_id, some_other_col -- whichever columns you're interested in
from rcte
where is_cycle = 0;
The anchor member finds your initial target row. The recursive member then looks for parents or children of each row found so far.
The final query can get whichever columns you want, do aggregation, etc.
(Possibly worth testing both approaches with real data to see if there is a performance difference, of course).

Unexpected result of multiset mapping in Oracle SQL

Please help me to confirm is that behavior explained below is a bug, or clearly explain why it's right.
There are a high probability that I misunderstood some concept, but now for me it looks like a bug.
All examples below simplified as much as possible to demonstrate core of the issue. Real situation is very complex, so only general answers and workarounds related to principle of query construction is acceptable.
You are welcome to ask clarifying questions in comments and i'll try to do my best to answer them.
Thank you for attention. :)
Question
Why in last Example (Example 5) collection instance in (select count(1) ... subquery from first row mapped to all rows of the table, while expected result is to map each collection instance to it's own row?
At the same time collections used in cardinality(...) expression chosen properly.
Same situation (not covered in examples) exists if constructed in this way collections used in from or where part of a query.
Test schema setup
(SQLFiddle)
create or replace type TabType0 as table of varchar2(100)
/
create table Table0( tab_str_field varchar2(100), tab_field TabType0)
nested table tab_field store as tab_field_table
/
insert into table0 (tab_str_field, tab_field) values (
'A',
cast(multiset(
select 'A' from dual union all
select 'B' from dual union all
select 'C' from dual
) as TabType0)
)
/
insert into table0 (tab_str_field, tab_field) values (
'B',
cast(multiset(
select 'B' from dual union all
select 'C' from dual
) as TabType0)
)
/
insert into table0 (tab_str_field, tab_field) values (
'C',
cast(multiset(
select 'A' from dual union all
select 'B' from dual union all
select 'C' from dual union all
select 'D' from dual
) as TabType0)
)
/
insert into table0 (tab_str_field, tab_field) values (
'D',
cast(multiset(
select 'A' from dual
) as TabType0)
)
/
select 'Initial table data' caption from dual
/
select * from table0
/
table data:
| TAB_STR_FIELD | TAB_FIELD |
-----------------------------
| A | A,B,C |
| B | B,C |
| C | A,B,C,D |
| D | A |
Examples
Example 1 (SQLFiddle) - work with nested table fields - OK
select 'Work with nested table - OK' caption from dual
/
select
tab_field tab_field,
-- cardinality
cardinality(tab_field) tab_cardinality,
-- select from table field of current row
(select count(1) from table(tab_field)) tab_count,
-- select from field of current row while joining
-- with another field of same row
( select column_value from table(tab_field)
where column_value = tab_str_field
) same_value
from table0
/
results:
| TAB_FIELD | TAB_CARDINALITY | TAB_COUNT | SAME_VALUE |
--------------------------------------------------------
| A,B,C | 3 | 3 | A |
| B,C | 2 | 2 | B |
| A,B,C,D | 4 | 4 | C |
| A | 1 | 1 | (null) |
Example 2 (SQLFiddle) - work with constructed source data alone - OK
select 'Work with constructed source data alone - OK' caption from dual
/
with table_data_from_set as (
select
'A' tab_str_field,
cast(multiset(
select 'A' from dual union all
select 'B' from dual union all
select 'C' from dual
) as TabType0) tab_field
from dual union all
select
'B' tab_str_field,
cast(multiset(
select 'B' from dual union all
select 'C' from dual
) as TabType0) tab_field
from dual union all
select
'C' tab_str_field,
cast(multiset(
select 'A' from dual union all
select 'B' from dual union all
select 'C' from dual union all
select 'D' from dual
) as TabType0) tab_field
from dual union all
select
'D' tab_str_field,
cast(multiset(
select 'A' from dual
) as TabType0) tab_field
from dual
)
select
tab_field tab_field,
-- cardinality
cardinality(tab_field) tab_cardinality,
-- select from table field of current row
(select count(1) from table(tab_field)) tab_count,
-- select from field of current row while joining
-- with another field of same row
( select column_value from table(tab_field)
where column_value = tab_str_field
) same_value
from table_data_from_set
/
results:
| TAB_FIELD | TAB_CARDINALITY | TAB_COUNT | SAME_VALUE |
--------------------------------------------------------
| A,B,C | 3 | 3 | A |
| B,C | 2 | 2 | B |
| A,B,C,D | 4 | 4 | C |
| A | 1 | 1 | (null) |
Example 3 (SQLFiddle) - join table with multisets constructed in WITH - OK
select 'Join table with multisets constructed in WITH - OK' caption from dual
/
with table_data_from_set as (
select
'A' tab_str_field,
cast(multiset(
select 'A' from dual union all
select 'B' from dual union all
select 'C' from dual
) as TabType0) tab_field
from dual union all
select
'B' tab_str_field,
cast(multiset(
select 'B' from dual union all
select 'C' from dual
) as TabType0) tab_field
from dual union all
select
'C' tab_str_field,
cast(multiset(
select 'A' from dual union all
select 'B' from dual union all
select 'C' from dual union all
select 'D' from dual
) as TabType0) tab_field
from dual union all
select
'D' tab_str_field,
cast(multiset(
select 'A' from dual
) as TabType0) tab_field
from dual
)
select
table0.tab_field table0_tab_field,
table_data_from_set.tab_field set_tab_field,
-- cardinality
cardinality(table0.tab_field) table0_tab_cardinality,
cardinality(table_data_from_set.tab_field) set_tab_cardinality,
-- select from table field of current row
(select count(1) from table(table_data_from_set.tab_field)) set_tab_count,
-- select from field of current row while joining
-- with another field of same row
( select column_value from table(table_data_from_set.tab_field)
where column_value = table0.tab_str_field
) same_value
from
table0,
table_data_from_set
where
table_data_from_set.tab_str_field = table0.tab_str_field
/
results:
| TABLE0_TAB_FIELD | SET_TAB_FIELD | TABLE0_TAB_CARDINALITY | SET_TAB_CARDINALITY | SET_TAB_COUNT | SAME_VALUE |
----------------------------------------------------------------------------------------------------------------
| A,B,C | A,B,C | 3 | 3 | 3 | A |
| B,C | B,C | 2 | 2 | 2 | B |
| A,B,C,D | A,B,C,D | 4 | 4 | 4 | C |
| A | A | 1 | 1 | 1 | (null) |
Example 4 (SQLFiddle) - join table with multisets constructed in WITH + subquery - OK
select 'Join table with multisets constructed in WITH and subquery - OK' caption from dual
/
with table_data_from_set as (
select
'A' tab_str_field,
cast(multiset(
select 'A' from dual union all
select 'B' from dual union all
select 'C' from dual
) as TabType0) tab_field
from dual union all
select
'B' tab_str_field,
cast(multiset(
select 'B' from dual union all
select 'C' from dual
) as TabType0) tab_field
from dual union all
select
'C' tab_str_field,
cast(multiset(
select 'A' from dual union all
select 'B' from dual union all
select 'C' from dual union all
select 'D' from dual
) as TabType0) tab_field
from dual union all
select
'D' tab_str_field,
cast(multiset(
select 'A' from dual
) as TabType0) tab_field
from dual
)
select
table0_tab_field table0_tab_field,
set_tab_field set_tab_field,
-- cardinality
cardinality(table0_tab_field) table0_tab_cardinality,
cardinality(set_tab_field) set_tab_cardinality,
-- select from table field of current row
(select count(1) from table(set_tab_field)) set_tab_count,
-- select from field of current row while joining
-- with another field of same row
( select column_value from table(set_tab_field)
where column_value = table0_tab_str_field
) same_value
from (
select
table0.tab_str_field table0_tab_str_field,
table0.tab_field table0_tab_field,
table_data_from_set.tab_str_field set_tab_str_field,
table_data_from_set.tab_field set_tab_field
from
table0,
table_data_from_set
where
table_data_from_set.tab_str_field = table0.tab_str_field
)
/
results:
| TABLE0_TAB_FIELD | SET_TAB_FIELD | TABLE0_TAB_CARDINALITY | SET_TAB_CARDINALITY | SET_TAB_COUNT | SAME_VALUE |
----------------------------------------------------------------------------------------------------------------
| A,B,C | A,B,C | 3 | 3 | 3 | A |
| B,C | B,C | 2 | 2 | 2 | B |
| A,B,C,D | A,B,C,D | 4 | 4 | 4 | C |
| A | A | 1 | 1 | 1 | (null) |
Example 5 (SQLFiddle) - join table with multisets constructed on the fly - FAILED
select 'Join table with multisets constructed on the fly - FAIL (set_tab_count wrong)' caption from dual
/
with string_set as (
select 'A' str_field from dual union all
select 'B' str_field from dual union all
select 'C' str_field from dual union all
select 'D' str_field from dual union all
select 'E' str_field from dual
)
select
table0_tab_field table0_tab_field,
set_tab_field set_tab_field,
-- cardinality
cardinality(table0_tab_field) table0_tab_cardinality,
cardinality(set_tab_field) set_tab_cardinality,
-- select from table field of current row
(select count(1) from table(set_tab_field)) set_tab_count,
-- select from field of current row while joining
-- with another field of same row
( select column_value from table(set_tab_field)
where column_value = table0_tab_str_field
) same_value
from (
select
table0.tab_str_field table0_tab_str_field,
table0.tab_field table0_tab_field,
(
cast(multiset(
select
string_set.str_field
from
string_set,
table(table0.tab_field) tab_table
where
string_set.str_field = tab_table.column_value
) as TabType0)
) set_tab_field
from
table0
)
/
result (all values in set_tab_count column are same - wrong! ) :
| TABLE0_TAB_FIELD | SET_TAB_FIELD | TABLE0_TAB_CARDINALITY | SET_TAB_CARDINALITY | SET_TAB_COUNT | SAME_VALUE |
----------------------------------------------------------------------------------------------------------------
| A,B,C | A,B,C | 3 | 3 | 3 | A |
| B,C | B,C | 2 | 2 | 3 | B |
| A,B,C,D | A,B,C,D | 4 | 4 | 3 | C |
| A | A | 1 | 1 | 3 | (null) |
Oracle version information
Instance 1
BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
PL/SQL Release 11.2.0.3.0 - Production
CORE 11.2.0.3.0 Production
TNS for IBM/AIX RISC System/6000: Version 11.2.0.3.0 - Production
NLSRTL Version 11.2.0.3.0 - Production
Instance 2
BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Express Edition Release 11.2.0.2.0 - Production
PL/SQL Release 11.2.0.2.0 - Production
CORE 11.2.0.2.0 Production
TNS for 32-bit Windows: Version 11.2.0.2.0 - Production
NLSRTL Version 11.2.0.2.0 - Production
SQLFiddle with all queries together.
It's a bug. Adding a /*+ NO_MERGE */ hint to the second inline view in the last example will generate the expected results. See this SQL Fiddle for an example. Regardless of the query, that hint should never change the results. There are a couple of other seemingly unrelated changes you can make that will generate the correct results, such as removing some of the columns, or adding an unused ROWNUM in the middle.
Oracle is re-writing your query to optimize it, but doing something wrong. You could probably get some more information by tracing the query, but I doubt you'll be able to truly fix the issue. Work around it for now and submit a service request to Oracle so they can create a bug and eventually fix it.