Skip to Main Content

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, babloo.

Asked: May 29, 2008 - 11:57 am UTC

Last updated: February 26, 2013 - 8:03 am UTC

Version: 9.2.0

Viewed 10K+ times! This question is

You Asked

Hi Tom

select * from test where dc_i(+)=552

create table t ( t1 number);

insert into t values(1);

insert into t values(2);

commit;

select * from t where t1(+)=3

why last query gives no data

and Tom said...

because an outer join preserves all of the rows in "some table" joined to "some other table" - all of the rows in "some table" will appear in the result set and if they have a match in "some other table", that'll appear too

but there isn't "some table" in this one, there is no table to return all rows from.

ops$tkyte%ORA10GR2> create table t ( x int );

Table created.

ops$tkyte%ORA10GR2> select *
  2    from t, (select 3 x from dual) d
  3   where t.x(+) = d.x
  4  /

         X          X
---------- ----------
                    3


that query has "some table"

Rating

  (5 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

babloo, May 30, 2008 - 11:08 am UTC


just the way it is ...

nameless, May 30, 2008 - 11:45 am UTC

Technically, the outer join operator (+) goes on the side of the "some other table" ... so it is kind of hard to say if T here is the "some table" or the "some other table" or both.

These two are equivalent:

select * from t where col (+) = <constant>
select * from t where col = <constant>

There isn't in fact a join here and so the outer join operator should not even be allowed.
But it is ... likely because of the obscure definition of join condition.
Tom Kyte
May 30, 2008 - 3:13 pm UTC

has to be allowed, it is not "illegal" to outer join to a constant



ops$tkyte%ORA10GR2> create table t ( x int, y varchar2(1) );

Table created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> select *
  2    from t, dual d
  3   where d.dummy = t.y(+)
  4     and 123 = t.x(+)
  5  /

         X Y D
---------- - -
             X

ops$tkyte%ORA10GR2> select *
  2    from t, dual d
  3   where d.dummy = t.y(+)
  4     and 123 = t.x
  5  /

no rows selected

use

babloo, May 30, 2008 - 6:16 pm UTC

Hi Tom ,
you say , this feature is usefull.
I am not able to find any real life situation where you would need to outer join to a constant .
I am sure you can help me appreciate this feature
Tom Kyte
May 31, 2008 - 6:58 am UTC

show me all employees, and if their department is located in DALLAS, show me the department name.

ops$tkyte%ORA10GR2> select empno, ename, dname
  2    from scott.emp e, scott.dept d
  3   where e.deptno = d.deptno(+)
  4     and 'DALLAS' = d.loc(+)
  5  /

     EMPNO ENAME      DNAME
---------- ---------- --------------
      7902 FORD       RESEARCH
      7876 ADAMS      RESEARCH
      7566 JONES      RESEARCH
      7369 SMITH      RESEARCH
      7788 SCOTT      RESEARCH
      7934 MILLER
      7839 KING
      7782 CLARK
      7900 JAMES
      7844 TURNER
      7698 BLAKE
      7654 MARTIN
      7521 WARD
      7499 ALLEN

14 rows selected.


puzzeld i am

Reene, September 04, 2008 - 1:05 pm UTC

Hi Tom

I have this query ,

SELECT /*+ index(itm ITEM_IDX) */ msib.organization_id organization_id,
msib.segment1 segment1,
msib.inventory_item_id inventory_item_id,
msib.description,
itm.itm_prim_source_fk,
itm.itm_id_type
FROM apps.mtl_system_items_b msib, apps.cst_item_costs cic, prepro.gems_item_tb itm
WHERE msib.organization_id = cic.organization_id (+)
AND msib.inventory_item_id = cic.inventory_item_id (+)
AND msib.segment1 = itm.itm_item_num_pk
AND itm.itm_prim_source_fk <> '730110' -- bind varaible
AND itm.itm_start_dt_pk = TO_DATE(('01-Jan-' || TO_CHAR( SYSDATE, 'YYYY' )),'DD-Mon-YYYY')
AND msib.inventory_asset_flag = 'Y'
AND msib.costing_enabled_flag = 'Y'
AND msib.segment1 NOT LIKE 'NL%'
AND msib.segment1 NOT LIKE '%PRERETURN%'
AND msib.eng_item_flag = 'N'
AND cic.cost_type_id (+) = 2081
AND msib.organization_id = 1630
and nvl(cic.item_cost,0)=0

can you please tell me what it is trying to do.

if i try to re-write this query as below

it gives me completly different results - why ?

SELECT /*+ index(itm ITEM_IDX) */ msib.organization_id organization_id,
msib.segment1 segment1,
msib.inventory_item_id inventory_item_id,
msib.description,
itm.itm_prim_source_fk,
itm.itm_id_type
FROM apps.mtl_system_items_b msib, (select * from apps.cst_item_costs
where cost_type_id(+) = 2081 and
nvl(cost_type_id,0) =0 ) cic
, prepro.gems_item_tb itm
WHERE msib.organization_id = cic.organization_id (+)
AND msib.inventory_item_id = cic.inventory_item_id (+)
AND msib.segment1 = itm.itm_item_num_pk
AND itm.itm_prim_source_fk <> '730110' -- bind varaible
AND itm.itm_start_dt_pk = TO_DATE(('01-Jan-' || TO_CHAR( SYSDATE, 'YYYY' )),'DD-Mon-YYYY')
AND msib.inventory_asset_flag = 'Y'
AND msib.costing_enabled_flag = 'Y'
AND msib.segment1 NOT LIKE 'NL%'
AND msib.segment1 NOT LIKE '%PRERETURN%'
AND msib.eng_item_flag = 'N'
--AND cic.cost_type_id (+) = 2081
AND msib.organization_id = 1630
--and nvl(cic.item_cost,0)=0

I want to understand the difference between 2.
I am not able to understnad this query in first place.

can you please explain me .
what is the impact of

cost_type_id(+) =2081 in this query.

Thanks
Tom Kyte
September 04, 2008 - 1:56 pm UTC

... can you please tell me what it is trying to do. ...

wow.


it joins these tables:

  FROM apps.mtl_system_items_b msib, 
       apps.cst_item_costs cic, 
       prepro.gems_item_tb itm



together using these join conditions:
WHERE msib.organization_id = cic.organization_id (+)
  AND msib.inventory_item_id = cic.inventory_item_id (+)
  AND msib.segment1 = itm.itm_item_num_pk



and then applies these additional filters to the resulting join:

  AND cic.cost_type_id (+) = 2081
  AND itm.itm_prim_source_fk <> '730110'    -- bind varaible
  AND itm.itm_start_dt_pk = TO_DATE(('01-Jan-' || TO_CHAR( SYSDATE, 'YYYY' )),'DD-Mon-YYYY')
  AND msib.inventory_asset_flag = 'Y'
  AND msib.costing_enabled_flag = 'Y'
  AND msib.segment1 NOT LIKE 'NL%'
  AND msib.segment1 NOT LIKE '%PRERETURN%'
  AND msib.eng_item_flag = 'N'
  AND msib.organization_id = 1630
  and nvl(cic.item_cost,0)=0



It does a whacky thing here:

TO_DATE(('01-Jan-' || TO_CHAR( SYSDATE, 'YYYY' )),'DD-Mon-YYYY')

you could have just used trunc(sysdate,'y'), shorter and more meaningful...


This construct just says "keep the rows where this is 2081 - and if there are NO rows for 2081 - make one up and set it to null"

  AND cic.cost_type_id (+) = 2081



This bit in the second one shows a misunderstanding of outer joins

(select * from
apps.cst_item_costs
where cost_type_id(+) = 2081
and nvl(cost_type_id,0) =0 )

that (+) in that context does *nothing*


ops$tkyte%ORA10GR2> create table t ( dummy char(1), x int );

Table created.

ops$tkyte%ORA10GR2> insert into t values ('y', 0);

1 row created.

ops$tkyte%ORA10GR2> commit;

Commit complete.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> select * from t where x(+)=2081;

no rows selected

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> select * from t, dual where dual.dummy=t.dummy(+) and t.x(+)=2081;

D          X D
- ---------- -
             X


outer joins work only when joining a table to another table - if you have a single table query, by definition you cannot "outer join" to anything.


But that shows what the x(+)=2081 is doing - it is an outer join - outer joins say "if a row exists that matches this join criteria - join to it, however if NO rows exist in the table we are outer joining to - make up a row and fill it all with nulls"

strange behavior with outer join and nvl() in where clause

Matt, February 25, 2013 - 5:49 pm UTC

Hi Tom

I came across some behavior I did not expect w.r.t. outer joins and nvl and inequalities in the where clause.

(full code to recreate is below)

I was taught that when you have an outer join you must ALWAYS have a (+) on the joins and filter conditions in the where clause.  But, why does a query like:

select a.id, a.value 
  from mbtemp a, mbtemp_limits b 
 where a.id = b.id(+) 
    -- say 0.5 is some "default" limit
   and a.value < nvl(b.max_value(+),0.5)  
 order by a.id;

return rows in mbtemp that have values higher than 0.5 when no corresponding rows in mbtemp_limits exist???

See below:

SQL> select banner from v$version;

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 Linux: Version 11.2.0.3.0 - Production
NLSRTL Version 11.2.0.3.0 - Production

SQL> 
SQL> create table mbtemp (id number(1), value number(5,5));

Table created.

SQL> 
SQL> create table mbtemp_limits (id number(1), max_value number(5,5));

Table created.

SQL> 
SQL> insert into mbtemp values(1, 0.25);

1 row created.

SQL> insert into mbtemp values(2, 0.75);

1 row created.

SQL> insert into mbtemp values(3, 0.9999);

1 row created.

SQL> insert into mbtemp values(4, 0.0001);

1 row created.

SQL> 
SQL> insert into mbtemp_limits values(1, 0.3);

1 row created.

SQL> 
SQL> commit;

Commit complete.

SQL> 
SQL> -- why does the following return values > 0.5 when corresponding rows do not exist?????
SQL> 
SQL> select a.id, a.value
  2    from mbtemp a, mbtemp_limits b
  3   where a.id = b.id(+)
  4     and a.value < nvl(b.max_value(+),0.5)  -- say 0.5 is some "default" limit
  5   order by a.id;

        ID      VALUE
---------- ----------
         1        .25
         2        .75
         3      .9999
         4      .0001

SQL> 
SQL> -- whereas this return rows with the behavior I would have expected.
SQL> --  If no corresponding row exists the the "default" limit is "0.5"
SQL> 
SQL> select a.id, a.value
  2    from mbtemp a, mbtemp_limits b
  3   where a.id = b.id(+)
  4     and a.value < nvl(b.max_value,0.5)  -- here 0.5 is the real "default" limit
  5   order by a.id;

        ID      VALUE
---------- ----------
         1        .25
         4      .0001

SQL> 

Tom Kyte
February 26, 2013 - 8:03 am UTC

In this case, you didn't want to use the outer join syntax at all.

You wanted to say:

I want every row in A joined to B by a.id = b.id and if there is no match in B for a row in A - output A anyway with NULL's for B.*

Then, further filter this result by applying the predicate "a.value < nvl(b.max_value,0.5)" - that'll be applied after any outer joining takes place.


The way you wrote it has A being outer joined to f(B) with an inequality - if we do the processing ( f(x) - the nvl ) and outer join to the result... you get what you get.

See, if we add a row, you can see this:

ops$tkyte%ORA11GR2> insert into mbtemp_limits( id, max_value ) values ( 4, null );

1 row created.

ops$tkyte%ORA11GR2> 
ops$tkyte%ORA11GR2> select a.id, a.value, b.*
  2    from mbtemp a, mbtemp_limits b
  3   where a.id = b.id(+)
  4     and a.value < nvl(b.max_value(+),0.5)
  5  /

        ID      VALUE         ID  MAX_VALUE
---------- ---------- ---------- ----------
         1        .25          1         .3
         4      .0001          4
         3      .9999
         2        .75


see how the row for ID=4 was not made up in B, the join "succeeded". A.id was joined to B.id and a.value was in fact less than nvl(b.max_value,0.5)

the (+) is associated with a column but applies to the expression. You can see this with other expressions as well:

ops$tkyte%ORA11GR2> select a.id, a.value, b.*
  2    from mbtemp a, mbtemp_limits b
  3   where a.id = b.id(+)
  4     and a.value < decode(b.max_value(+),0.25,-1,null,0.5)
  5  /

        ID      VALUE         ID  MAX_VALUE
---------- ---------- ---------- ----------
         4      .0001          4
         3      .9999
         1        .25
         2        .75



In that case, we can see the decode was done AND THEN the join. we turned 0.25 into -1 and null again into 0.5. Then we joined and the join "succeeded", we didn't need to make up a row for id=4 in B.

further, we have to be consistent - if you reference that column again in the expression - it has to use (+) there too:
ops$tkyte%ORA11GR2> select a.id, a.value, b.*
  2    from mbtemp a, mbtemp_limits b
  3   where a.id = b.id(+)
  4     and a.value < decode(b.max_value(+),0.25,-1,null,0.5,b.max_value)
  5  /
   and a.value < decode(b.max_value(+),0.25,-1,null,0.5,b.max_value)
               *
ERROR at line 4:
ORA-01417: a table may be outer joined to at most one other table


ops$tkyte%ORA11GR2> select a.id, a.value, b.*
  2    from mbtemp a, mbtemp_limits b
  3   where a.id = b.id(+)
  4     and a.value < decode(b.max_value(+),0.25,-1,null,0.5,b.max_value(+))
  5  /

        ID      VALUE         ID  MAX_VALUE
---------- ---------- ---------- ----------
         1        .25          1         .3
         4      .0001          4
         3      .9999
         2        .75




but in any case - the outer join on that part of the where clause was inappropriate if you type out your requirement in a sentence. You in fact wanted the results of A left join to B on ID to be filtered by the NVL() function.