oracle-databaseoracle12couter-apply

Oracle OUTER APPLY with TABLE constructor behaves like CROSS APPLY. Bug?


Consider the following setup:

CREATE TYPE list_t AS VARRAY(10) OF NUMBER(10);
/
CREATE TABLE x (
  id NUMBER(10),
  list list_t
);
INSERT INTO x VALUES (0, list_t());
INSERT INTO x VALUES (1, list_t(1));
INSERT INTO x VALUES (2, list_t(1, 2));

My intuition tells me that the following queries should produce the same result:

-- 1: Using 12c syntax with table constructor
SELECT x.id, x.list, y.column_value
FROM x
OUTER APPLY TABLE (x.list) y;

-- 2: Using 12c syntax with an explicit (+) operator
SELECT x.id, x.list, y.column_value
FROM x
OUTER APPLY TABLE (x.list)(+) y;

-- 3: Using 12c syntax with derived table
SELECT x.id, x.list, y.column_value
FROM x
OUTER APPLY (SELECT * FROM TABLE (x.list)) y;

-- 4: Using 11g syntax with old outer join (+)
SELECT x.id, x.list, y.column_value
FROM x, TABLE(x.list)(+) y;

-- 5: Using 12c standard syntax with LATERAL (works only on derived tables in this case)
SELECT x.id, x.list, y.column_value
FROM x
LEFT JOIN LATERAL (SELECT * FROM TABLE (x.list)) y ON 1 = 1;

However, they don't return the same result. #1's result seems wrong:

Result 1

(behaves like CROSS APPLY, seems wrong)

ID  LIST                COLUMN_VALUE
------------------------------------
1   TEST.LIST_T(1)      1
2   TEST.LIST_T(1, 2)   1
2   TEST.LIST_T(1, 2)   2

Result 2, 3, 4 and 5

ID  LIST                COLUMN_VALUE
------------------------------------
0   TEST.LIST_T()
1   TEST.LIST_T(1)      1
2   TEST.LIST_T(1, 2)   1
2   TEST.LIST_T(1, 2)   2

Alternative using a stored function

To illustrate my confusion, I can add a stored function that just returns the input list:

CREATE OR REPLACE FUNCTION f (list list_t) RETURN list_t IS
BEGIN
  RETURN list;
END f;
/

And then use that in the original query:

-- 1: Using 12c syntax with table constructor
SELECT x.id, x.list, y.column_value
FROM x
OUTER APPLY TABLE (f (x.list)) y;

I'm now getting the expected result with 4 rows:

ID  LIST                COLUMN_VALUE
------------------------------------
0   TEST.LIST_T()
1   TEST.LIST_T(1)      1
2   TEST.LIST_T(1, 2)   1
2   TEST.LIST_T(1, 2)   2

Question

Is this a bug in Oracle 12.2.0.1.0, or is this how OUTER APPLY and the TABLE constructor are supposed to work?


Solution

  • It's a bug of the Parser, at the expansion stage. OUTER APPLY (as some other features that were added in 12.1) is apparently implemented by conversion to syntax the optimizer knew in 11g. The first query is (incorrectly) converted to INNER join, while the third is (correctly) converted to OUTER join.

    -- select x.id,x.list,y.column_value from x outer apply table (list) y
    select "A1"."ID_0"           "ID",
           "A1"."LIST_1"         "LIST",
           "A1"."COLUMN_VALUE_2" "COLUMN_VALUE"
    from   (select "A3"               ."ID" "ID_0",
                   "A3"."LIST"         "LIST_1",
                   "A2"."COLUMN_VALUE" "COLUMN_VALUE_2"
            from   "DEMO"."X" "A3",
                   (select value(a4) "COLUMN_VALUE"
                    from   table("A3"."LIST") "A4") "A2"
            where  1 = 1) "A1"
    
    -- select x.id,x.list,y.column_value from x outer apply (select * from table (list)) y
    select "A1"."ID_0"           "ID",
           "A1"."LIST_1"         "LIST",
           "A1"."COLUMN_VALUE_2" "COLUMN_VALUE"
    from   (select "A3"                 ."ID" "ID_0",
                   "A3"."LIST"           "LIST_1",
                   "A2"."COLUMN_VALUE_0" "COLUMN_VALUE_2"
            from   "DEMO"."X" "A3",
                   lateral((select "A4"."COLUMN_VALUE_0" "COLUMN_VALUE_0"
                           from   lateral((select "A5"."COLUMN_VALUE" "COLUMN_VALUE_0"
                                          from   (select value(a6) "COLUMN_VALUE"
                                                  from   table("A3"."LIST") "A6") "A5")) "A4"
                           where  1 = 1))(+) "A2") "A1"