Oracle Alpha-Numeric column sorting - sql

I am working on to Sort the Revision column of an Oracle DB table in the asc order as per below.
At first the numeric revisions to be sorted (1,2,3,…).
Thereafter Alpha-Numeric to be sorted as following: A, B, B1, C, C1, C2,…,Y, Y2, Y3, Z, AA, AB,..,DA, …ZZ, etc.
Row_Number() in the SELECT statement to be filled with 1,2,3… for each document# (ABC, XYZ) after revision sorting out.
See the uploaded image for the required table.
I tried with SUBSTR , Order by, etc but failed to sort out as per above requirement.
Can someone help me on this ? Thanks!

As I understand your question, you want to put last revisions that contain only two characters and no digits.
You can use a conditional sort:
select
t.*,
row_number() over(
partition by doc#
order by
case when regexp_like(revision, '^\w\d?$') then 0 else 1 end,
revision
) rn
from t
order by doc#, rn
The regular expression describes a string starting with an alphanumeric character, optionally followed by a digit: these revisions should come first.
Demo on DB Fiddle:
with t as (
select 'ABC' doc#, '1' revision from dual
union all select 'ABC', '2' from dual
union all select 'ABC', '3' from dual
union all select 'ABC', 'A' from dual
union all select 'ABC', 'B' from dual
union all select 'ABC', 'B1' from dual
union all select 'ABC', 'C' from dual
union all select 'ABC', 'C1' from dual
union all select 'ABC', 'D' from dual
union all select 'ABC', 'AA' from dual
union all select 'ABC', 'AB' from dual
union all select 'ABC', 'BA' from dual
union all select 'ABC', 'DA' from dual
)
select
t.*,
row_number() over(
partition by doc#
order by
case when regexp_like(revision, '^\w\d?$') then 0 else 1 end,
revision
) rn
from t
order by doc#, rn
DOC# | REVISION | RN
:--- | :------- | -:
ABC | 1 | 1
ABC | 2 | 2
ABC | 3 | 3
ABC | A | 4
ABC | B | 5
ABC | B1 | 6
ABC | C | 7
ABC | C1 | 8
ABC | D | 9
ABC | AA | 10
ABC | AB | 11
ABC | BA | 12
ABC | DA | 13

There is well known old method: rpad(col, max-length, '0')
For example rpad(col, max(length(col)) over(), '0'

Related

Compare date time to another table date_time

select uid , max(price) keep(dense_rank last order by usage_date ASC) amount
from HISTORY group by uid" ;
compare HISTORY to ADJUSTMENT
If the latest usage_date (each id ) is before (sen_date)
I would like to add the price of ADJUSTMENT
How can I do this ?
*I want to add the price of ADJUSTMENT to max(price) if usage_date is older than ADJUSTMENT
in this case uid= 5 is should be added 200
ADJUSTMENT
uid,price,sen_date
1,100,2020-10-23
2,200,2020-10-23
2,200,2020-09-22
2,200,2020-08-23
2,200,2020-10-22
3,300,2020-10-20
5,200,2020-10-20
HISTORY
uid,price,usage_date
1,1000,2020-10-23
1,1000,2020-10-19
1,1000,2020-10-03
2,1000,2020-10-23
3,1000,2020-10-23
3,1000,2020-10-23
3,1000,2020-10-23
4,1000,2020-10-20
4,1000,2020-10-23
4,1000,2020-10-19
4,1000,2020-10-23
4,1000,2020-10-23
5,1000,2020-10-02
5,1000,2020-10-03
5,1000,2020-10-04
6,1000,2020-10-23
7,1000,2020-10-23
Take your query and LEFT OUTER JOIN it to a similar query for the ADJUSTMENT table correlating on uid with a later date and then use COALESCE to combine the dates:
SELECT h."UID",
h.usage_date,
a.sen_date,
h.price AS base_price,
COALESCE( a.price, 0 ) AS price_adjustment,
h.price + COALESCE( a.price, 0 ) AS price
FROM (
SELECT "UID",
MAX( usage_date ) AS usage_date,
MAX(price) KEEP ( DENSE_RANK LAST ORDER BY usage_date ASC) AS price
FROM HISTORY
GROUP BY "UID"
) h
LEFT OUTER JOIN
(
SELECT "UID",
MAX( sen_date ) AS sen_date,
MAX(price) KEEP ( DENSE_RANK LAST ORDER BY sen_date ASC) AS price
FROM ADJUSTMENT
GROUP BY "UID"
) a
ON ( h."UID" = a."UID" AND h.usage_date < a.sen_date )
ORDER BY "UID"
Which, for the sample data:
CREATE TABLE adjustment ( "UID",price,sen_date ) AS
SELECT 1,100,DATE '2020-10-23' FROM DUAL UNION ALL
SELECT 2,200,DATE '2020-10-23' FROM DUAL UNION ALL
SELECT 2,200,DATE '2020-09-22' FROM DUAL UNION ALL
SELECT 2,200,DATE '2020-08-23' FROM DUAL UNION ALL
SELECT 2,200,DATE '2020-10-22' FROM DUAL UNION ALL
SELECT 3,300,DATE '2020-10-20' FROM DUAL UNION ALL
SELECT 5,200,DATE '2020-10-20' FROM DUAL;
CREATE TABLE HISTORY ( "UID",price,usage_date) AS
SELECT 1,1000,DATE '2020-10-23' FROM DUAL UNION ALL
SELECT 1,1000,DATE '2020-10-19' FROM DUAL UNION ALL
SELECT 1,1000,DATE '2020-10-03' FROM DUAL UNION ALL
SELECT 2,1000,DATE '2020-10-23' FROM DUAL UNION ALL
SELECT 3,1000,DATE '2020-10-23' FROM DUAL UNION ALL
SELECT 3,1000,DATE '2020-10-23' FROM DUAL UNION ALL
SELECT 3,1000,DATE '2020-10-23' FROM DUAL UNION ALL
SELECT 4,1000,DATE '2020-10-20' FROM DUAL UNION ALL
SELECT 4,1000,DATE '2020-10-23' FROM DUAL UNION ALL
SELECT 4,1000,DATE '2020-10-19' FROM DUAL UNION ALL
SELECT 4,1000,DATE '2020-10-23' FROM DUAL UNION ALL
SELECT 4,1000,DATE '2020-10-23' FROM DUAL UNION ALL
SELECT 5,1000,DATE '2020-10-02' FROM DUAL UNION ALL
SELECT 5,1000,DATE '2020-10-03' FROM DUAL UNION ALL
SELECT 5,1000,DATE '2020-10-04' FROM DUAL UNION ALL
SELECT 6,1000,DATE '2020-10-23' FROM DUAL UNION ALL
SELECT 7,1000,DATE '2020-10-23' FROM DUAL;
Outputs:
UID | USAGE_DATE | SEN_DATE | BASE_PRICE | PRICE_ADJUSTMENT | PRICE
--: | :--------- | :-------- | ---------: | ---------------: | ----:
1 | 23-OCT-20 | null | 1000 | 0 | 1000
2 | 23-OCT-20 | null | 1000 | 0 | 1000
3 | 23-OCT-20 | null | 1000 | 0 | 1000
4 | 23-OCT-20 | null | 1000 | 0 | 1000
5 | 04-OCT-20 | 20-OCT-20 | 1000 | 200 | 1200
6 | 23-OCT-20 | null | 1000 | 0 | 1000
7 | 23-OCT-20 | null | 1000 | 0 | 1000
db<>fiddle here
Do you just mean this (shown here)?
SELECT a.* FROM adjustment a
WHERE a.sen_date > (SELECT max(usage_date) FROM history WHERE uid = a.uid)

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

How do I turn rows with duplicate values to columns?

I try to pivot my table but keeping additional rows (in my example eeeeee ) Is there a way in Oracle SQL to do this?
select * from (
select
mat_table.material, attribute_table.attribute, attribute_table.value
from
mat_table mat_table
inner join
attribute_table on mat_table.rel= attribute_table.rel
where
material = 'Material_A'
)
material |attribute| value
_____________________________________
Material_A |aaaaaa |
Material_A |bbbbbb | hello
Material_A |cccccc | val_1
Material_A |dddddd | 2
Material_A |eeeeee | 15
Material_A |eeeeee | 16
Material_A |eeeeee | 24
when I use pivot under the where clause
pivot (
max(attribute) as max_value for attribute IN ( 'aaaaaa',
'bbbbbb',
'cccccc',
'dddddd',
'eeeeee'
))
I am getting closer to what I want but for eeeee I get only one value
material |aaaaaa | bbbbbb | cccccc | dddddd | eeeeee |
__________________________________________________________
Material_A | | hello | val_1 | 2 | 24 |
but what I want is something like
material |aaaaaa | bbbbbb | cccccc | dddddd | eeeeee_1 | eeeeee_2 | eeeeee_3 |
__________________________________________________________________________________
Material_A | | hello | val_1 | 2 | 15 16 | 24
If there are always 3 values for eeeeee then you can do it as following
SQL> with mat_table (material, attribute, value) as
2 (
3 select 'Material_A', 'aaaaaa', null from dual
4 union all select 'Material_A', 'bbbbbb', 'hello' from dual
5 union all select 'Material_A', 'cccccc', 'val_1' from dual
6 union all select 'Material_A', 'dddddd', '2' from dual
7 union all select 'Material_A', 'eeeeee', '15' from dual
8 union all select 'Material_A', 'eeeeee', '16' from dual
9 union all select 'Material_A', 'eeeeee', '24' from dual
10 )
11 select *
12 from (select t.*,
13 row_number() over(partition by attribute order by value) rn
14 from mat_table t)
15 pivot (max(value) for (attribute, rn) in
16 (
17 ('aaaaaa', 1), ('bbbbbb', 1), ('cccccc', 1), ('dddddd', 1),
18 ('eeeeee', 1), ('eeeeee', 2), ('eeeeee', 3)
19 ));
MATERIAL 'aaaa 'bbbb 'cccc 'dddd 'eeee 'eeee 'eeee
---------- ----- ----- ----- ----- ----- ----- -----
Material_A hello val_1 2 15 16 24
If, however, you expect Oracle to dynamically create columns for any number of values for eeeeee then that is not possible.
Please read detailed explanation here Oracle Dynamic Pivoting
You can generate XML for any combinations of attribute and value but if you want to display result using SQL then eventually all the columns must be specified (alternative approach is parsing XML on client side).
SQL> with mat_table (material, attribute, value) as
2 (
3 select 'Material_A', 'aaaaaa', null from dual
4 union all select 'Material_A', 'bbbbbb', 'hello' from dual
5 union all select 'Material_A', 'cccccc', 'val_1' from dual
6 union all select 'Material_A', 'dddddd', '2' from dual
7 union all select 'Material_A', 'eeeeee', '15' from dual
8 union all select 'Material_A', 'eeeeee', '16' from dual
9 union all select 'Material_A', 'eeeeee', '24' from dual
10 )
11 select material, x.*
12 from mat_table
13 pivot xml (count(*) as dummy for (attribute, value) in (any, any))
14 -- parsing output
15 , xmltable('/PivotSet' passing attribute_value_xml
16 columns
17 aaaaaa varchar2(10) path '/PivotSet/item[column="aaaaaa"]/column[2]',
18 bbbbbb varchar2(10) path '/PivotSet/item[column="bbbbbb"]/column[2]',
19 cccccc varchar2(10) path '/PivotSet/item[column="cccccc"]/column[2]',
20 dddddd varchar2(10) path '/PivotSet/item[column="dddddd"]/column[2]',
21 eeeeee_1 varchar2(10) path '/PivotSet/item[column="eeeeee"][1]/column[2]',
22 eeeeee_2 varchar2(10) path '/PivotSet/item[column="eeeeee"][2]/column[2]',
23 eeeeee_3 varchar2(10) path '/PivotSet/item[column="eeeeee"][3]/column[2]') x;
MATERIAL AAAAAA BBBBBB CCCCCC DDDDDD EEEEEE_1 EEEEEE_2 EEEEEE_3
---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
Material_A hello val_1 2 15 16 24
In this case there is no guarantee that EEEEEE_1/EEEEEE_2/EEEEEE_3 will be 15/16/24 in exactly this order.
EDIT: TS commented that it did not work for multiple materials. So I expanded the answer to account for that.
You could simply concat a row_number (partioned by material, attribute) to the attribute in your first query. You can add ordening by value if you like. To account for multiple materials the row_number to also partitioned by material. This means that the same attributes for different materials will get the same name and will end up in the same column.
replace attribute_table.attribute with
concat(attribute_table.attribute,'_', row_number() over (partition by attribute_table.material, attribute_table.attribute order by attribute_table.attribute, attribute_table.value))
Complete code and result:
with mat_table as
(
select 'Material_A' as material, 'aaaaaa' as attribute, null as value
union all select 'Material_A', 'bbbbbb', 'hello'
union all select 'Material_A', 'cccccc', 'val_1'
union all select 'Material_A', 'dddddd', '2'
union all select 'Material_A', 'eeeeee', '15'
union all select 'Material_A', 'eeeeee', '16'
union all select 'Material_A', 'eeeeee', '24'
union all select 'Material_B' , 'aaaaaa', 'lol'
union all select 'Material_B', 'bbbbbb', 'hi'
union all select 'Material_B', 'cccccc', 'max_val'
union all select 'Material_B', 'dddddd', '4'
union all select 'Material_B', 'eeeeee', '67'
union all select 'Material_B', 'eeeeee', '99'
union all select 'Material_B', 'eeeeee', null
)
select *
from (
select t.material,
t.value ,
concat(t.attribute,'_', row_number() over (partition by t.material , t.attribute order by t.attribute, t.value)) as numbered_attribute
from mat_table t) as d
pivot (
max(d.value)
for numbered_attribute IN ( [aaaaaa_1],
[bbbbbb_1],
[cccccc_1],
[dddddd_1],
[eeeeee_1],
[eeeeee_2],
[eeeeee_3]
)) as total
order by total.material
Note: I used SQL-Server. Maybe you'll have to change some syntax like [eeeeee_3] => 'eeeeee_3'
Base table with numbered attributes:
Final result after pivot:

Oracle - count records if number of token less than 2

I have records like...
ID | KEY
-------|---------
1 | 123_456_abc
1 | 123_xyz
1 | 456_abc
2 | 123_abc
2 | 122_73_zcc
3 | 123_wer
4 | 345_23_fhd
4 | 3453_abc
5 | ad1fr2h3_abcasd
5 | ers2g45bb_abc2rtd
5 | asf23g_abc1_sf45
I want count(ID) where count(tokanize(numeric(KEY),'_')) < 2
As count(ID) will be 6
You can try something like this
SELECT COUNT(ID) FROM xyz WHERE key NOT LIKE '%_%_%';
This should filter all elements which have less than two underscores.
Try this :
select Count(1) from
(with abc(id,key) as (select '1','123_456_abc' from dual
Union all
select '1','123_xyz' from dual
UNion all
select '1','456_abc' from dual
Union all
select '2','123_abc' from dual
UNion all
select '2','123_73_zcc' from dual
Union all
select '3','123_wer' from dual
UNion all
select '1','345_23_fhd' from dual
UNion all
select '1','345_abc' from dual
)
select key, length(regexp_replace(key,'[^_]*','')) cntr
from abc )
where cntr = 1
eliminate all records which has more than 1 underscores
then eliminate the ones which do not start with a number
then sum it up
select sum(cnt) from (
select key, cnt, id from (
select key, length(regexp_replace(key,'[^_]*','')) cnt, id from table_name
) where cnt < 2
) where regexp_like(key,'[1-9]+(.)*')

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.