Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TABLE/CAST/MULTISET vs subquery in FROM clause

The following query doesn't work. It is expected to fail since temp.col references something that is unavailable in that context.

with temp as  (
       select 'A' col from dual
       union all
       select 'B' col from dual
     )
select *
from temp,
     (select level || temp.col from dual connect by level < 3);

The error message from Oracle is : ORA-00904: "TEMP"."COL": invalid identifier

But why is the next query working ? I see CAST/MULTISET as a way to go from a SQL table to a collection type and TABLE to go back to a SQL table. Why do we use such round-trip ? I guess to make the query work, but how ?

with temp as  (
       select 'A' col from dual
       union all
       select 'B' col from dual
     )
select *
from temp,
     table(
       cast(
         multiset(
           select level || temp.col from dual connect by level < 3
         ) as sys.odcivarchar2list
       )
     ) t;

The result is :

COL COLUMN_VALUE
--- ------------
A   1A          
A   2A          
B   1B          
B   2B          

Look how the second column is named COLUMN_VALUE. Looks like a generated name by one of the construct CAST/MULTISET or TABLE.

EDIT

With the accepted answer below, I checked the documentation and found that the TABLE mechanism is a table collection expression. The expression between rounded brackets is the collection expression. The documentations defines a mechanism called left correlation :

The collection_expression can reference columns of tables defined to its left in the FROM clause. This is called left correlation. Left correlation can occur only in table_collection_expression. Other subqueries cannot contains references to columns defined outside the subquery.

So this is like LATERAL in 12c.

like image 301
Ludovic Kuty Avatar asked Mar 20 '26 13:03

Ludovic Kuty


1 Answers

Oracle allows lateral inline views to reference other tables inside the inline view.

In old versions this feature was mostly used for optimizations, as discussed in the Oracle optimizer blog here. Explicit lateral joins were added in 12c. Your first query only needs a small change to work in 12c:

with temp as  (
       select 'A' col from dual
       union all
       select 'B' col from dual
     )
select *
from temp,
     lateral(select level || temp.col from dual connect by level < 3);

Apparently Oracle also silently uses lateral joins for collection unnesting. There are a few cases where SQL uses a logical cross join, but the tables are obviously closely related; such as XMLTable, JSON_table, and queries like your second example. In those cases it makes sense to execute the two tables together. I assume the lateral mechanism is used there, although neither the execution plan nor the 10053 optimizer trace uses the word "lateral". The documentation even has an example very similar to yours in the Collection Unnesting: Examples. However, this "feature" is still not well documented.


On a side note, in general you should avoid SQL features that increase the context. Features like lateral joins, common table expressions, and correlated subqueries can be useful, but they can also make SQL statements more difficult to understand. A regular inline view can be run and understood all by itself and has a very simple interface - its projected columns. That simplicity makes it easier to assemble small components into a large statement.

I suggest you re-write your query like below. Treat each inline view like you would a function or procedure - give them good names and comments. It will help you later when you assemble them into large, realistic statements.

select col, the_level||col
from
(
    --Good comment 1.
    select 'A' col from dual union all
    select 'B' col from dual
) good_name_1
cross join
(
    --Good comment 2.
    select level the_level
    from dual
    connect by level < 3
) good_name_2
like image 108
Jon Heller Avatar answered Mar 22 '26 05:03

Jon Heller



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!