Skip to Main Content

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Nag.

Asked: August 10, 2002 - 10:09 am UTC

Last updated: July 08, 2011 - 2:27 pm UTC

Version: 8.1.7

Viewed 10K+ times! This question is

You Asked

Tom

SQL> desc emp
Name Null? Type
----------------------------------------- -------- -------------------------
EMPNO NOT NULL NUMBER(4)
ENAME VARCHAR2(10)
JOB VARCHAR2(9)
MGR NUMBER(4)
HIREDATE DATE
SAL NUMBER(7,2)
COMM NUMBER(7,2)
DEPTNO NUMBER(2)

SQL> alter table emp add (dname varchar2(20));

Table altered.


SQL> ed
Wrote file afiedt.buf

1 select ename,emp.deptno,emp.dname
2 from emp,dept
3 where emp.deptno(+)=dept.deptno and .. outer join
4* emp.dname=dept.dname .. no outer join
SQL> /

no rows selected




SQL> update emp set dname='ACCOUNTING' ;

14 rows updated.

SQL> commit;

Commit complete.

SQL> select ename,emp.deptno,emp.dname
2 from emp,dept
3 where emp.deptno(+)=dept.deptno and .. outer join

4 emp.dname(+)=dept.dname .. outer join

5 /

ENAME DEPTNO DNAME
---------- ---------- --------------------
CLARK 10 ACCOUNTING
KING 10 ACCOUNTING
MILLER 10 ACCOUNTING

6 rows selected.



Tom , why is it necessary to give (+) on all the join conditions between the two tables if we want to outer join between two tables.

2.In 8i or 9i I heard that we can give outer join on both sides of the join. Can you give us an example. What purpose does it serve, i.e. what kind of scenario is it useful for...

Thanks


and Tom said...

It is the very definition of an outer join, that is why. It is the way they "work".

If you see a query like this:

from t1, t2
where t1.x = t2.x(+)
and t2.any_other_column <some condition>

you know for a fact that outer join is a waste, a performance drain, something to be removed from the query.

Ask yourself this -- how can a row in T2 (EMP in your example) be both MISSING (made up due to the outer join) AND have a column that is equal to something??? It cannot be.


The other one is called a FULL OUTER JOIN. If you FULL OUTER JOIN T1 to T2 -- it means that ALL rows in T1 and ALL rows in T2 will appear in the result set.

Here is a demo of each:

ops$tkyte@ORA920.US.ORACLE.COM> @demo010
=================================================================

natural join, just a BAD idea
notice the deptno in the projection has no qualifier, in fact, it cannot!
this is a bug waiting to happen, relying on the "names" to join


ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select ename, dname, deptno
2 from SCOTT.EMP natural join SCOTT.DEPT
3 /

ENAME DNAME DEPTNO
---------- -------------- ----------
SMITH RESEARCH 20
ALLEN SALES 30
WARD SALES 30
JONES RESEARCH 20
MARTIN SALES 30
BLAKE SALES 30
CLARK ACCOUNTING 10
SCOTT RESEARCH 20
KING ACCOUNTING 10
TURNER SALES 30
ADAMS RESEARCH 20
JAMES SALES 30
FORD RESEARCH 20
MILLER ACCOUNTING 10

14 rows selected.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

Inner join, the right idea. Same as a "natural" join but you
list the columns


ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select ename, dname, emp.deptno, dept.deptno
2 from SCOTT.EMP inner join SCOTT.DEPT
3 on emp.deptno = dept.deptno
4 /

ENAME DNAME DEPTNO DEPTNO
---------- -------------- ---------- ----------
SMITH RESEARCH 20 20
ALLEN SALES 30 30
WARD SALES 30 30
JONES RESEARCH 20 20
MARTIN SALES 30 30
BLAKE SALES 30 30
CLARK ACCOUNTING 10 10
SCOTT RESEARCH 20 20
KING ACCOUNTING 10 10
TURNER SALES 30 30
ADAMS RESEARCH 20 20
JAMES SALES 30 30
FORD RESEARCH 20 20
MILLER ACCOUNTING 10 10

14 rows selected.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

outer join syntax. the LEFT outer join uses EMP as the
driving table here. if there were a row in EMP that didn't
have a mate in DEPT, it would make one up


ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select ename, dname, emp.deptno, dept.deptno
2 from SCOTT.EMP LEFT outer join SCOTT.DEPT
3 on emp.deptno = dept.deptno
4 /

ENAME DNAME DEPTNO DEPTNO
---------- -------------- ---------- ----------
MILLER ACCOUNTING 10 10
KING ACCOUNTING 10 10
CLARK ACCOUNTING 10 10
FORD RESEARCH 20 20
ADAMS RESEARCH 20 20
SCOTT RESEARCH 20 20
JONES RESEARCH 20 20
SMITH RESEARCH 20 20
JAMES SALES 30 30
TURNER SALES 30 30
BLAKE SALES 30 30
MARTIN SALES 30 30
WARD SALES 30 30
ALLEN SALES 30 30

14 rows selected.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

outer join syntax. the RIGHT outer join uses DEPT as the
driving table here. if there were a row in DEPT that didn't
have a mate in EMP (and there is), it would make one up


ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> select ename, dname, emp.deptno, dept.deptno
2 from SCOTT.EMP RIGHT outer join SCOTT.DEPT
3 on emp.deptno = dept.deptno
4 /

ENAME DNAME DEPTNO DEPTNO
---------- -------------- ---------- ----------
SMITH RESEARCH 20 20
ALLEN SALES 30 30
WARD SALES 30 30
JONES RESEARCH 20 20
MARTIN SALES 30 30
BLAKE SALES 30 30
CLARK ACCOUNTING 10 10
SCOTT RESEARCH 20 20
KING ACCOUNTING 10 10
TURNER SALES 30 30
ADAMS RESEARCH 20 20
JAMES SALES 30 30
FORD RESEARCH 20 20
MILLER ACCOUNTING 10 10
OPERATIONS 40

15 rows selected.

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set echo off
Enter to continue

=================================================================

FULL outer join syntax. Every row in EMP and DEPT will appear at least once
Note that I am making up DEPTNO's in EMP so there are EMP's without DEPT's
as well as DEPT's without EMP's

As you can imagine, this is EXPENSIVE

ops$tkyte@ORA920.US.ORACLE.COM>
ops$tkyte@ORA920.US.ORACLE.COM> set autotrace on explain
ops$tkyte@ORA920.US.ORACLE.COM> select nvl(ename,'**MADE UP**') ename,
2 nvl(dname,'**MADE UP**') dname,
3 emp.deptno, dept.deptno
4 from (select ename, deptno+20 deptno
5 from SCOTT.EMP ) EMP FULL outer join SCOTT.DEPT
6 on emp.deptno = dept.deptno
7 /

ENAME DNAME DEPTNO DEPTNO
----------- -------------- ---------- ----------
MILLER SALES 30 30
KING SALES 30 30
CLARK SALES 30 30
FORD OPERATIONS 40 40
ADAMS OPERATIONS 40 40
SCOTT OPERATIONS 40 40
JONES OPERATIONS 40 40
SMITH OPERATIONS 40 40
JAMES **MADE UP** 50
TURNER **MADE UP** 50
BLAKE **MADE UP** 50
MARTIN **MADE UP** 50
WARD **MADE UP** 50
ALLEN **MADE UP** 50
**MADE UP** RESEARCH 20
**MADE UP** ACCOUNTING 10

16 rows selected.


Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=10 Card=163 Bytes=6846)
1 0 VIEW (Cost=10 Card=163 Bytes=6846)
2 1 UNION-ALL
3 2 HASH JOIN (OUTER) (Cost=5 Card=82 Bytes=3444)
4 3 TABLE ACCESS (FULL) OF 'EMP' (Cost=2 Card=82 Bytes=1640)
5 3 TABLE ACCESS (FULL) OF 'DEPT' (Cost=2 Card=82 Bytes=1804)
6 2 HASH JOIN (ANTI) (Cost=5 Card=81 Bytes=2835)
7 6 TABLE ACCESS (FULL) OF 'DEPT' (Cost=2 Card=82 Bytes=1804)
8 6 TABLE ACCESS (FULL) OF 'EMP' (Cost=2 Card=82 Bytes=1066)



ops$tkyte@ORA920.US.ORACLE.COM> set autotrace off
ops$tkyte@ORA920.US.ORACLE.COM>



Rating

  (139 ratings)

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

Comments

is this inline view useless?

A reader, August 11, 2002 - 9:30 am UTC

Hi Tom

As usual excellent explanation about outer joins!
I have a query which uses outer joins as follows


select view_services.destine_a,
view_services.destine_b,
tmp_unit_call.id_price,
dm_bsc.id_contract
from dm_bsc, -- 8000000 rows no index
tmp_pre_call, -- 12000000 rows no index
tmp_unit_call, -- 20 rows no index
(select a.destine_a,
a.destine_b
from (select distinct destine_a, destine_b from tmp_pre_call) a,
dm_services b, -- 40 rows no indes
dm_services c -- 40 rows no index
where a.destine_a = b.destine(+)
and a.destine_b = c.destine(+)
) view_services
where tmp_pre_call.msisdn = dm_bsc.de_msisdn(+)
and tmp_pre_call.id_cdr = tmp_unit_call.id_cdr(+)
and tmp_pre_call.destine_a = view_services.destine_a(+)
and tmp_pre_call.destine_b = view_services.destine_b(+)


As You can see there is an inline view called view_services, I think it is not needed and it can be converted to


select view_services.destine_a,
view_services.destine_b,
tmp_unit_call.id_price,
dm_bsc.id_contract
from dm_bsc, -- 8000000 rows no index
tmp_pre_call, -- 12000000 rows no index
tmp_unit_call, -- 20 rows no index
dm_services b, -- 40 rows no index
dm_services c -- 40 rows no index
where tmp_pre_call.msisdn = dm_bsc.de_msisdn(+)
and tmp_pre_call.id_cdr = tmp_unit_call.id_cdr(+)
and tmp_pre_call.destine_a = b.destine(+)
and tmp_pre_call.destine_b = c.destine(+)


Am I correct assumming that the outer join in the inline view makes this query convertable? (If it was not an outer join I would filter out more rows in tmp_pre_call)
The first query is slow because tmp_pre_call is full scanned twice, one in inline view and the other in the query that is why I am looking to converting it.

Cheers



Tom Kyte
August 11, 2002 - 9:57 am UTC

Just a quick glance here -- these queries are not the same -- there is a DISTINCT on tmp_pre_call in the inline view that is missing in the other query. That could affect the cardinality of the result set.

It really depends on the QUESTION being asked. Do this (this is how I write queries myself) -- phrase the QUESTION in english. Forget about any existing queries and develop a query that answers the question.

A reader, November 18, 2002 - 2:45 pm UTC

Hi tom,

 can you please explain.....

  1   select e1.*, e2.ename from emp e1,dept d ,emp e2
  2   where e1.deptno = d.deptno(+)
  3*  and e1.mgr = e2.empno(+)
  4  /

EMPNO ENAME    JOB  MGR HIREDATE     SAL   COMM DEPTNO ENAME
---- -------- ---- ---- ---------    ---    ---- ------ ----
7369 SMITH   CLERK   7902  17-DEC-80 800          20  FORD 
7499 ALLEN  SALESMAN 7698  20-FEB-81 1600  300    30  BLAKE
7521 WARD   SALESMAN 7698  22-FEB-81 1250  500    30  BLAKE
7566 JONES  MANAGER  7839  02-APR-81 2975         20  KING
7654 MARTIN SALESMAN 7698  28-SEP-81 1250 1400    30 BLAKE
7698 BLAKE  MANAGER  7839  01-MAY-81  850         30 KING
7782 CLARK  MANAGER  7839  09-JUN-81  450         10 KING
7788 SCOTT  ANALYST  7566  19-APR-87 3000         20 JONES
7839 KING  PRESIDENT       17-NOV-81 5000         10
7844 TURNER SALESMAN 7698 08-SEP-81  1500    0    30 BLAKE
7876 ADAMS  CLERK    7788 23-MAY-87  1100         20 SCOTT
7900 JAMES  CLERK    7698 03-DEC-81   950         30 BLAKE
7902 FORD   ANALYST  7566 03-DEC-81  3000         20 JONES
7934 MILLER CLERK    7782 23-JAN-82  1300         10 CLARK
10   kobi   Player        02-OCT-02  5000     0

15 rows selected.

SQL> select * from dept;

    DEPTNO DNAME          LOC
---------- -------------- -------------
        10 ACCOUNTING     NEW YORK
        20 RESEARCH       DALLAS
        30 SALES          CHICAGO
        40 OPERATIONS     BOSTON

How come one table emp from scott/tiger can be outer joined to two tables emp(self joined) and dept ?

Thanks, 

Tom Kyte
November 18, 2002 - 8:48 pm UTC

why not?

Sagi, November 19, 2002 - 6:18 am UTC

Hi Tom,

Thanx for the very good examples of JOINs in 9i.

I could not get what you ment by saying:

"If you see a query like this:

from t1, t2
where t1.x = t2.x(+)
and t2.any_other_column <some condition>

you know for a fact that outer join is a waste, a performance drain, something
to be removed from the query."

Could you kindly explain in detail....

Regards,
Sagi

Tom Kyte
November 19, 2002 - 7:35 am UTC

doesn't:

you know for a fact that outer join is a waste, a performance drain, something
to be removed from the query.

Ask yourself this -- how can a row in T2 (EMP in your example) be both MISSING
(made up due to the outer join) AND have a column that is equal to something???
It cannot be.


answer that? I don't know how else to say it. if you see:

where t1.x = t2.x(+)
and t2.y = 5


you KNOW that outer join is a waste cause if we outer joined from t1 to t2 and there was no matching row in t2 -- y would be NULL, NULL = 5 would cause the row to disappear. Hence if we outer join -- we'll discard it anyway. Hence, outer join is a waste of energy in that query.

can you please explain

A reader, November 19, 2002 - 8:56 am UTC

Hi Tom,

so if emp from scott/tiger is
1.) outer joined to dept all the rows will be returned from
emp even a match is not found in dept
2.) emp1.mgr is outer joined to emp2.empno and all the rows
will be returned from emp1 even a match is not found in
emp2.empno

so can I say that in both the cases the set {emp1} is
constant and outer joined query will try to find matches
and if found display it
( return all rows from emp1 and just get the mathces from all other outer joined tables . no processing on emp1 ) ?


Tom Kyte
November 19, 2002 - 5:14 pm UTC

1) correct
2) correct

not -- not really (not in general). if either of the join keys were NOT unique in the joined to tables - the rows in emp could be MULTIPLIED (eg: if dept had 2 rows for deptno = 20)

Otherwise, if the joined to keys are unique -- then "yes"

But we need the record returned!!

John, November 19, 2002 - 1:18 pm UTC

<q>
If you see a query like this:

from t1, t2
where t1.x = t2.x(+)
and t2.any_other_column <some condition>
</q>

wouldn't :

from t1, t2
where t1.x = t2.x(+)
and (t2.y = ?any? or t2.y IS NULL)

work if a record was needed? we have changed some of
our programs to the or t2.y is null) for this very reason.

Is there a better way? (cost wise)


Tom Kyte
November 19, 2002 - 5:27 pm UTC

that is DIFFERENT then isn't it?

what I'm saying is I see literally TONS of code that has:

from t1, t2
where t1.x = t2.x(+)
and t2.any_other_column = :value


All that is doing is precluding the optimizer from lots of paths it could otherwise take. It is a waste and tells me "I'm not really sure what I'm doing here -- guess I'll outer join cause someone said I'll lose rows if I don't"

If you need it when ( t2.y = <some value> or t2.y is NULL ) that is totally very 100% different.

PL/SQL

sriram, November 20, 2002 - 2:29 am UTC

Hi Tom

I am sorry for posting a slightly different question in this thread.

I want to write a PL/SQL procedure which when executed, will prompt for a number, print "less than 100" if the entered number is lesser than 100 OR print "equal/more than 100" if the entered number is equal/greater than 100.

Requirement is that, I cannot pass the same as arguments, rather, the code should primpt for the inputs when executed (exec).

Please post the code for the same. This is urgently needed.

Regards
Sriram

Tom Kyte
November 21, 2002 - 11:49 am UTC

won't be happening. PLSQL runs in the database -- typically on a different machine. PLSQL isn't appropriate for writing an interactive program.

Unless of course you are using Oracle Forms on the client -- then it is basically what Oracle Forms does.

So, if this is a database stored procedure -- no joy for you and no chance of getting there from here.

If this is Oracle forms running plsql -- thats basically what it does.

outer join to subset

Darko, November 20, 2002 - 5:31 am UTC

"what I'm saying is I see literally TONS of code that has:

from t1, t2
where t1.x = t2.x(+)
and t2.any_other_column = :value"


It can be valid (but with changed sintax) if you want to outer join
to the subset of the table.

Example is if DEPARTMENT table has one more column ACTIVE_IND (active
depratement Y/N).
Task is to get all employees and coresponding department names, but department
must be active, otherwise department name should be empty.

select a.emp_name, b.dep_name
from EMP a,
DEP b
WHERE a.dep_id = b.dep_id (+)
AND b.active_ind (+) = 'Y'

Tom Kyte
November 21, 2002 - 12:06 pm UTC

but that is not ANYTHING LIKE WHAT I HAVE TYPED IN

there is no outer join on the second predicate. That is exactly my point. When I see code that VERBAITIM looks like:

from t1, t2
where t1.x = t2.x(+)
and t2.any_other_column = :value

I know the person writing it did not understand for a moment what they were really doing.

Full Tbale scan for FULL OUTER JOIN

Balaji, November 21, 2002 - 8:35 am UTC

Hi Tom
As i could see , do you mean to say that a full outer join on EMP and Dept will always result in a full table scan on both the tables.
I think this would be the case because eventhough we give the condition say d.dept_id(+) = 100 and e.emp_id (+) = 101
the optimizer has to fetch all the records that match the condition dept_id = 100 and also if dept_id <> 100 and smilarly for emp_id.
Regards
Balaji

Tom Kyte
November 21, 2002 - 1:10 pm UTC

I hesitate to say "always".


scott@ORA920.US.ORACLE.COM> select *
2 from (select * from EMP where job = 'CLERK') emp
3 full outer join
4 (select * from DEPT where loc = 'BOSTON' ) dept
5 on (emp.deptno = dept.deptno)
6 /

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=4 Card=2 Bytes=234)
1 0 VIEW (Cost=4 Card=2 Bytes=234)
2 1 UNION-ALL
3 2 NESTED LOOPS (OUTER) (Cost=2 Card=1 Bytes=117)
4 3 TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=1 Card=1 Bytes=87)
5 4 INDEX (RANGE SCAN) OF 'JOB_IDX' (NON-UNIQUE) (Cost=1 Card=1)
6 3 TABLE ACCESS (BY INDEX ROWID) OF 'DEPT' (Cost=1 Card=1 Bytes=30)
7 6 INDEX (UNIQUE SCAN) OF 'PK_DEPT' (UNIQUE)
8 2 NESTED LOOPS (ANTI) (Cost=2 Card=1 Bytes=49)
9 8 TABLE ACCESS (BY INDEX ROWID) OF 'DEPT' (Cost=1 Card=1 Bytes=30)
10 9 INDEX (RANGE SCAN) OF 'LOC_IDX' (NON-UNIQUE) (Cost=1 Card=1)
11 8 TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=1 Card=1 Bytes=19)
12 11 INDEX (RANGE SCAN) OF 'JOB_IDX' (NON-UNIQUE) (Cost=1 Card=1)

not a full scan to be found in that one.

Outer Join

Anil, March 10, 2003 - 4:16 am UTC

DearTom,
I am having a table test like this

select * from test;

ACCT_NO Type AMOUNT
----- - ----------
6285 C 1000
6285 D 2000
6286 C 1000
6286 D 2000
6287 C 1000
6288 D 1000

C is Credit and D is Debit

I want a result set like this

Acct_No Credit Debit
6285 1000 2000
6286 1000 2000
6287 1000
6288 1000

I am using Oracle 8.1.7 so i cannot use FULL OUTER JOIN FEATURE OF 9i. If i use outer join i don't get all the 4 account numbers.

Can You please help in giving me a query.


Tom Kyte
March 10, 2003 - 7:47 am UTC

why would you even consider joining?


select acct_no,
sum( decode( type, 'C', amount, null ) ) credit,
sum( decode( type, 'D', amount, null ) ) debit
from test
group by acct_no;



what if pk is of two columns ?

A reader, March 10, 2003 - 4:17 pm UTC

table dept

deptcd number ,
dept_group_id number,
dept_nm varchar2(50) NULL,
dept_brname varchar2(30) NULL,
dept_notes varchar2(30)

alter table dept
add constraint pk_dept
primary key(deptcd,dept_group_id);


table emp

empid number primary key,
ename_name varchar2(50),
e_deptcd number,
entry_ts date,
deptcd number,
dept_group_id number,


alter table emp
add constraint fk_emp
foreign key(deptcd ,dept_group_id)
references dept(deptcd,dept_group_id);



**NOTE** : in emp table dept can be null **

Now,,

I need to develop a qurey ( I think an outer join)
so show " emp by dept cd and also those who has dept null ".

how can i do it ?

please advise !

Thanks,


Tom Kyte
March 10, 2003 - 6:18 pm UTC

your question

I need to develop a qurey ( I think an outer join)
so show " emp by dept cd and also those who has dept null ".

doesn't make sense to me -- do you mean:

for all departements, print out emps and if there are no emps, print out the dept information anyway.


If so, yes, it is just

select *
from emp, dept
where dept.deptcd = emp.deptcd(+)
and dept.dept_group_id = emp.dept_group_id(+);



my try

A reader, March 10, 2003 - 5:01 pm UTC

Hi tom,

i tried this way

select deptcd,dept_group_id,empid
from emp e,dept d
where e.deptcd = d.deptcd(+)
and e.dept_group_id = d.dept_group_id (+)

with a test data, it gave me correct result ?
can I trust on it ?

on it is just an one odd case ?

Tom Kyte
March 10, 2003 - 6:19 pm UTC

it is a correct outer join from DEPT to EMP given your schema.

Thanks tom

A reader, March 10, 2003 - 6:21 pm UTC


How ?

A reader, March 10, 2003 - 6:25 pm UTC

Hi tom,

Thanks for your quick reply and sorry for ambiguous description of the question.

can you please tell me
from emp, dept
where dept.deptcd = emp.deptcd(+) <<-
and dept.dept_group_id = emp.dept_group_id(+); <<-

how is it going to be eveluated ?

I mean isn't both conditions evaluationed seperately ?
if yes, then won't there be any dups in the result set ?

can you please explain in detail ?



Tom Kyte
March 10, 2003 - 6:55 pm UTC

sigh, it is a join, it won't "duplicate anything". it is just a join. it doesn't matter if your join key is 1 column or 20 columns -- it is just a join.

Thanks,

A reader, March 10, 2003 - 7:04 pm UTC

thanks tom !!

I thought that
the oracle engine will take (evaluate) one predicate at a time. so i thought that it will create one set

set A with dept.deptcd = emp.deptcd(+)

and then set b with

set B dept.dept_group_id = emp.dept_group_id(+)

now both have outer join so I was wondering
how will it will join two outer joins perserving the
primary key

Thanks,
vivek

Tom Kyte
March 10, 2003 - 7:19 pm UTC

the predicate is an entity, it applies to the entire thing, it is not done a piece at a time like that.




Thanks !!

A reader, March 10, 2003 - 7:20 pm UTC


Why not this then...?

Jon, March 11, 2003 - 2:17 am UTC

Following on from Darko, who wrote:

select a.emp_name, b.dep_name
from EMP a,
DEP b
WHERE a.dep_id = b.dep_id (+)
AND b.active_ind (+) = 'Y'

why wont Oracle allow us to extend this to:

select a.emp_name, b.dep_name
from EMP a,
DEP b
WHERE a.dep_id = b.dep_id (+)
AND (b.active_ind (+) = 'Y' OR b.active_ind (+) = 'X')

I can not see any logical reason why this could not be evaluated successfully, but as it stands it needs to be coded around using a union. Could you explain please?

Thanks.



Tom Kyte
March 11, 2003 - 7:53 am UTC

Either of:

ops$tkyte@ORA920> select ename, job, dname
  2    from emp, dept
  3   where dept.deptno = emp.deptno(+)
  4     and 'X' = decode(emp.job(+), 'ANALYST', 'X', 'SALESMAN', 'X' )
  5  /

ENAME      JOB       DNAME
---------- --------- --------------
                     ACCOUNTING
SCOTT      ANALYST   RESEARCH
FORD       ANALYST   RESEARCH
ALLEN      SALESMAN  SALES
WARD       SALESMAN  SALES
TURNER     SALESMAN  SALES
MARTIN     SALESMAN  SALES
                     OPERATIONS

8 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920> select ename, job, dname
  2    from (select * from emp where job in ( 'ANALYST', 'SALESMAN' ) ) emp,
  3         dept
  4   where dept.deptno = emp.deptno(+)
  5  /

ENAME      JOB       DNAME
---------- --------- --------------
                     ACCOUNTING
SCOTT      ANALYST   RESEARCH
FORD       ANALYST   RESEARCH
ALLEN      SALESMAN  SALES
WARD       SALESMAN  SALES
TURNER     SALESMAN  SALES
MARTIN     SALESMAN  SALES
                     OPERATIONS

8 rows selected.



works? 

Evaluation of Predicate

Jerry, March 11, 2003 - 8:44 am UTC

I know this is not outer-join related, but the discussion of predicates brought a question to my mind.  I have a colleague who uses a varchar2 to store a date value (I know, I know...).

Her code only works when the GT "space" condition is given first.  There are 2 values in the table, space, and a date value

This is 8.1.7.  Does Oracle sometimes enforce the predicate line-by-line?


SQL> select count(*) from ps_tl_rptd_elptime
  2  where to_date(user_field_3,'dd-mon-yyyy') = trunc(sysdate + 4)
  3  and user_field_3 > ' ';
where to_date(user_field_3,'dd-mon-yyyy') = trunc(sysdate + 4)
              *
ERROR at line 2:
ORA-01847: day of month must be between 1 and last day of month


SQL>
SQL> select count(*) from ps_tl_rptd_elptime
  2  where user_field_3 > ' '
  3  and to_date(user_field_3,'dd-mon-yyyy') = trunc(sysdate + 4);

  COUNT(*)
----------
         5

SQL>
SQL>
SQL> select user_field_3,count(*)
  2  from ps_tl_rptd_elptime
  3  group by user_field_3;

USER_FIELD_3      COUNT(*)
--------------- ----------
                        19
01-DEC-2002          18224
01-FEB-2003          11680
01-MAR-2003          12503
02-JUN-2002          19065
03-AUG-2002          11391
03-NOV-2002          18612 

Tom Kyte
March 11, 2003 - 9:17 am UTC

he he he.

if the table is not analyzed, analyze it and run again. see what surprising results you get.

If the table is analyzed, delete the stats for a minute. see what surprising results you get.

basically -- as the plans change so shall the order of evaluation of the predicate.

the safe way to query that data would be:

where user_field_3 > ' '
and CASE when user_field_3 > ' ' then to_date(user_field_3,'dd-mon-yyyy')
else null
end > trunc(sysdate+4);




Very cool...

Jon, March 11, 2003 - 6:30 pm UTC

that is some box of tricks you have at your disposal...

Tom Kyte
March 11, 2003 - 6:58 pm UTC

Let me clarify:

that is some box of tricks we all have at our disposal....




Data Compare

Gov, March 22, 2003 - 12:17 pm UTC

Tom ,
I have to compare compare colums of two tables t1 , t2 and load the t1 if the column pair does not exist in t2.
The colums( Product, term ) many be combinations of null.
for example
A123, null
null , 360
A123, 180
null, null
are different records.
The table t1 is very huge. I do not want to use Minus.
The query
t1 - a1, b1
t2 - a2 , b2

select

from t1 , t2
t1.a1 = t2.a2(+) and
t1.b1=t2.b2(+) and
t2.a2 is null and t2.b2 is null does not give the same result as Minus when the data has nulls.

a1 ,a2 aree not primary keys on the tables.

How do you handle this.

Thanks in advance.
Gov








Tom Kyte
March 22, 2003 - 12:43 pm UTC

why don't you want to use MINUS which is

a) brutally efficient
b) the right answer
c) the answer I'm going to give you....




In a Package ???

Gov, March 22, 2003 - 12:50 pm UTC

I am using this inside an ETL package.Is That Ok?
Does the Sze of t1 and t1 matter. I have about 2.5 Million records in t1 and about 100K on t2.

Thanks
Govind.

Tom Kyte
March 22, 2003 - 1:23 pm UTC

in a package, in a program, in sqlplus -- what difference would that make?

Anyway, on a "stock off the shelf $800 desktop PC"


big_table@ORA920> create table temp as
2 select -rownum id, a.*
3 from (select * from all_objects union all
4 select * from all_objects union all
5 select * from all_objects union all
6 select * from all_objects ) a
7 /

Table created.

Elapsed: 00:00:11.58
big_table@ORA920> analyze table temp compute statistics for table;

Table analyzed.

Elapsed: 00:00:01.39
big_table@ORA920>
big_table@ORA920> select count(*) from temp;

COUNT(*)
----------
119552

1 row selected.

Elapsed: 00:00:00.06
big_table@ORA920> select count(*) from big_table;

COUNT(*)
----------
3816192

1 row selected.

Elapsed: 00:00:02.13
big_table@ORA920>
big_table@ORA920> insert /*+ APPEND */ into big_table
2 select * from temp
3 MINUS
4 select * from big_table;

119552 rows created.

Elapsed: 00:01:14.62
big_table@ORA920>


So, you should expect even better on your database server....

How to do an outer join on composite keys?

A Reader, April 11, 2003 - 2:48 pm UTC

Hi Tom,
We have table t1 where PK has more than one column and table t2 that references the PK in t1. Some values in t1 are not in t2 and we want all the values in t1 show up in our query. Since we cannot do "where t1.pk(1)=t2.fk(1)(+) and t1.pk(2)=t2.fk(2)(+)" because only one outer join can be used in a query. What we do now is using inline views (or views) by concat the two PK columns into one and then do an outer join. Are there any other ways to do the outer join?
Thank you for your help.


Tom Kyte
April 13, 2003 - 7:25 pm UTC

umm, try again.

where t1.x = t2.x (+) and t1.y = t2.y(+)

most certainly does, has and will work.

Full Outer Join Bug ???

C.A., April 13, 2003 - 11:48 pm UTC

I think full outer join is very cool but -if I'm not doing anything wrong- it's not working very well with grouped by inline views.
--
select * from foj_test_01

STORE SKU TRAN_CODE REF_NO_1 TRAN_DATE RTD_UNITS
--------- --------- --------- --------- ----------- ---------
938 2020582 20 3020362 17-MAR-2003 3
938 2020582 22 7 31-MAR-2003 2
--
select * from foj_test_02

STORE SKU TRAN_CODE REF_NO_1 TRAN_DATE STD_UNITS
--------- --------- --------- --------- ----------- ---------
938 2020582 22 7 31-MAR-2003 2
938 2020582 22 13 27-MAR-2003 1
--
Direct table to table full outer join (works)
--
select nvl(std.store,rtd.store) store,nvl(std.sku,rtd.sku)
sku,nvl(std.tran_code,rtd.tran_code) tran_code
, nvl(std.ref_no_1,rtd.ref_no_1) ref_no_1,std.tran_date
store_tran_date,rtd.tran_date rms_tran_date
, std_units, rtd_units
from foj_test_01 std
full outer join
foj_test_02 rtd
on std.store=rtd.store
and std.sku=rtd.sku
and std.tran_code=rtd.tran_code
and std.ref_no_1=rtd.ref_no_1
and
std.tran_date=decode(std.tran_code,1,rtd.tran_date,std.tran_date)
/
STORE SKU TRAN_CODE REF_NO_1 STORE_TRAN_ RMS_TRAN_DA STD_UNITS
RTD_UNITS
--------- --------- --------- --------- ----------- ----------- ---------
---------
938 2020582 22 7 31-MAR-2003 31-MAR-2003 2
2
938 2020582 20 3020362 17-MAR-2003
3
938 2020582 22 13 27-MAR-2003 1
--
with group by inline views, each inline view retrieves same data with select * from table. at the 3rd line all fields are populated but the sum one (rtd_units=3)is not displayed.
--
select nvl(std.store,rtd.store) store,nvl(std.sku,rtd.sku)
sku,nvl(std.tran_code,rtd.tran_code) tran_code
, nvl(std.ref_no_1,rtd.ref_no_1) ref_no_1,std.tran_date
store_tran_date,rtd.tran_date rms_tran_date
, rtd_units , std_units
from( select store,sku,tran_code,nvl(ref_no_1,1) ref_no_1,tran_date,
sum(std_units) std_units
from foj_test_02
group by store,sku,tran_code,ref_no_1,tran_date
) std
full outer join
( select store,sku,tran_code,ref_no_1,tran_date, sum(rtd_units)
rtd_units
from foj_test_01
group by store,sku,tran_code,ref_no_1,tran_date
) rtd
on std.store=rtd.store
and std.sku=rtd.sku
and std.tran_code=rtd.tran_code
and std.ref_no_1=rtd.ref_no_1
and
std.tran_date=decode(std.tran_code,1,rtd.tran_date,std.tran_date)
/
STORE SKU TRAN_CODE REF_NO_1 STORE_TRAN_ RMS_TRAN_DA RTD_UNITS
STD_UNITS
--------- --------- --------- --------- ----------- ----------- ---------
---------
938 2020582 22 7 31-MAR-2003 31-MAR-2003 2
2
938 2020582 22 13 27-MAR-2003
1
938 2020582 20 3020362 17-MAR-2003

--
and try last query with right outer join instead of full outer join. Works well again.
--
STORE SKU TRAN_CODE REF_NO_1 STORE_TRAN_ RMS_TRAN_DA RTD_UNITS
STD_UNITS
--------- --------- --------- --------- ----------- ----------- ---------
---------
938 2020582 22 7 31-MAR-2003 31-MAR-2003 2
2
938 2020582 20 3020362 17-MAR-2003 3


Tom Kyte
April 14, 2003 - 7:25 am UTC

no versions...

in 9203 it did not reproduce, in 9201 and 9012 it did. Contact support.

Selecting unmatched records

ramks, May 07, 2003 - 8:47 am UTC

Hi Tom

I have used outer join in my query, I want to filter out
only unmatched rows between tables. How to do that? and what are all the ways?

thanks in adv


Tom Kyte
May 07, 2003 - 9:17 am UTC

select t1.*
from t1, t2
where t1.key = t2.key(+)
and t2.key IS NULL;


or

select * from t1 where key NOT IN ( select key from t2 )

or

select * from t1
minus
select * from t2;



Can I achieve FULL OUTER JOIN in 7.3.4?

VL, May 13, 2003 - 11:15 pm UTC

Dear Tom,

Can I achieve FULL OUTER JOIN in 7.3.4?

Rgs,
VL

Tom Kyte
May 14, 2003 - 6:37 am UTC

select *
from t1, t2
where t1.key = t2.key (+)
UNION ALL
select *
from t1, t2
where t1.key(+) = t2.key
and t1.key IS NULL;



Excellent

David Jiang, June 12, 2003 - 12:45 pm UTC

Hi, Tom,
Your explanation is great.
Here I have a query with some kind of difficult on FULL OUTER JOIN. Would you please tell me why it does not work?
Here is the one with Right outer join. It works fine.
SELECT a.INDEX_NAME, a.COLUMN_NAME , j.COLUMN_NAME
FROM ( SELECT * FROM (
SELECT INDEX_NAME, COLUMN_NAME, COLUMN_POSITION FROM BASELINE_INDEXES
MINUS
SELECT INDEX_NAME, COLUMN_NAME,COLUMN_POSITION
FROM REPORT_INDEXES)
WHERE INDEX_NAME NOT IN( SELECT INDEX_NAME from BASELINE_INDEXES
minus
SELECT INDEX_NAME FROM REPORT_INDEXES)) a
right outer join
(SELECT * FROM (
SELECT INDEX_NAME, COLUMN_NAME, COLUMN_POSITION
FROM REPORT_INDEXES
MINUS
SELECT INDEX_NAME, COLUMN_NAME, COLUMN_POSITION
FROM BASELINE_INDEXES )
WHERE INDEX_NAME NOT IN( SELECT INDEX_NAME from REPORT_INDEXES
minus
SELECT INDEX_NAME FROM BASELINE_INDEXES)) j
ON a.INDEX_NAME = j.INDEX_NAME
and a.COLUMN_POSITION = j.COLUMN_POSITION;

INDEX_NAME COLUMN_NAME COLUMN_NAME
--------------- ---------------- ----------------
INDEX2_IN_BASEL USER_ID INT2
INE

INDEX2_IN_BASEL INT1 INT3
INE

INT4
Here is the one with FULL Outer Join
SELECT a.INDEX_NAME, a.COLUMN_NAME , j.COLUMN_NAME
FROM ( SELECT * FROM (
SELECT INDEX_NAME, COLUMN_NAME, COLUMN_POSITION FROM BASELINE_INDEX
MINUS
SELECT INDEX_NAME, COLUMN_NAME,COLUMN_POSITION
FROM REPORT_INDEXES)
WHERE INDEX_NAME NOT IN( SELECT INDEX_NAME from BASELINE_INDEXES
minus
SELECT INDEX_NAME FROM REPORT_INDEXES)) a
full outer join
(SELECT * FROM (
SELECT INDEX_NAME, COLUMN_NAME, COLUMN_POSITION
FROM REPORT_INDEXES
MINUS
SELECT INDEX_NAME, COLUMN_NAME, COLUMN_POSITION
FROM BASELINE_INDEXES )
WHERE INDEX_NAME NOT IN( SELECT INDEX_NAME from REPORT_INDEXES
minus
SELECT INDEX_NAME FROM BASELINE_INDEXES)) j
ON a.INDEX_NAME = j.INDEX_NAME
and a.COLUMN_POSITION = j.COLUMN_POSITION;

It does not give me the expected result as right outer join:

INDEX_NAME COLUMN_NAME COLUMN_NAME
--------------- ---------------- ----------------
INDEX2_IN_BASEL USER_ID INT2
INE

INDEX2_IN_BASEL INT1 INT3
INE
Thanks a lot.

Tom Kyte
June 12, 2003 - 1:24 pm UTC

no test case. you should be able to simplify this, have the create tables, insert intos and as small of a query as possible (like i do, for you, with the examples)

Updated Information

David Jiang, June 12, 2003 - 5:03 pm UTC

Sorry, Tom,

Here are the test case. Plese help me!
I have two tables.
table T1
INDEX_NAME COLUMN_NAME COLUMN_POSITION
--------------- ---------------- ---------------
INDEX2_IN_BASE INT2 1
INDEX2_IN_BASE ROLE_ID 2
INDEX2_IN_BASE INT3 3
INDEX2_IN_BASE INT4 4


table T2:
INDEX_NAME COLUMN_NAME COLUMN_POSITION
--------------- ---------------- ---------------
INDEX2_IN_BASE INT1 3
INDEX2_IN_BASE ROLE_ID 2
INDEX2_IN_BASE USER_ID 1
INDEX2_IN_BASE INT7 4

I want a query to use the full out joint(?) to get the result like the following:

INDEX_NAME COLUMN_NAME INDEX_NAME COLUMN_NAME
--------------- ---------------- --------------- -----------
INDEX2_IN_BASE USER_ID INDEX2_IN_BASE INT2

INDEX2_IN_BASE INT1 INDEX2_IN_BASE INT3

INDEX2_IN_BASE INT4
INDEX2_IN_BASE INT7


I could not get a right query to get the result in this format. Thanks a lot.


Tom Kyte
June 12, 2003 - 7:59 pm UTC

well, now you really lost me. guess I'll back up. what exactly is the question you are trying to ask of this data -- forget your answer for a moment, what is the "question"

I see no question that would give rise to the "answer"

john123, June 13, 2003 - 3:12 am UTC

Is the keywords "inner join", "outer join" just pseudo code and Oracle really understand these? I have tried it but it seems Oracle doesn't understand. I am using Oracle 8.1.7.

Tom Kyte
June 13, 2003 - 8:08 am UTC

they are new Oracle9i syntax features.

Updated information

David Jiang, June 13, 2003 - 12:37 pm UTC

OK, Tom, 
Let me start from the beginning.
I have two tables t1 and t2:
SQL> select * from t1;
INDEX_NAME      COLUMN_NAME      COLUMN_POSITION
--------------- ---------------- ---------------
INDEX2_IN_BASE  INT1                           3
INDEX2_IN_BASE  USER_ID                        1
INDEX_IN_BASE   ACTION_DATE                    2
INDEX_IN_REPORT ACTION_ID                      2
INDEX_IN_REPORT TEST1                          1
INDEX_IN_REPORT TEST2                          3
SQL>select * from t2;
INDEX_NAME      COLUMN_NAME      COLUMN_POSITION
--------------- ---------------- ---------------
INDEX2_IN_BASE  INT2                           1
INDEX2_IN_BASE  INT3                           3
INDEX2_IN_BASE  INT4                           4
INDEX_IN_REPORT ACTION_ID                      2
INDEX_IN_REPORT USER_ID                        1
INDEX2_IN_BASE  ACTION_DATE                    2

I want to list t1.INDEX_NAME, t1.column_name,t2.INDEX_NAME, t2.column_name from t1 and t2 which have the same index_name, but different column_name according to their column_position.

Here is my query and result:
SQL> SELECT  a.INDEX_NAME, a.COLUMN_NAME,j.INDEX_NAME, j.COLUMN_NAME 
  2  FROM (
  3  SELECT INDEX_NAME, COLUMN_NAME,COLUMN_POSITION FROM t1
  4  MINUS 
  5  SELECT  INDEX_NAME, COLUMN_NAME,COLUMN_POSITION FROM t2) a 
  6  FULL OUTER JOIN
  7  (SELECT INDEX_NAME, COLUMN_NAME,COLUMN_POSITION  FROM t2
  8  MINUS
  9  SELECT INDEX_NAME, COLUMN_NAME, COLUMN_POSITION FROM t1) j 
 10  on a.INDEX_NAME = j.INDEX_NAME
 11  and a.COLUMN_POSITION  =  j.COLUMN_POSITION;

INDEX_NAME      COLUMN_NAME      INDEX_NAME      COLUMN_NAME
--------------- ---------------- --------------- -----------
INDEX2_IN_BASE  USER_ID          INDEX2_IN_BASE  INT2
INDEX2_IN_BASE  INT1             INDEX2_IN_BASE  INT3
INDEX_IN_REPORT TEST1            INDEX_IN_REPORT USER_ID
INDEX_IN_REPORT TEST2
INDEX_IN_BASE   ACTION_DATE

I missed COLUMN_NAME=INT4 from t2. This query is the same as Left outer join somehow. Here is my right outer join result:
SQL> SELECT  a.INDEX_NAME, a.COLUMN_NAME,j.INDEX_NAME, j.COLUMN_NAME
  2  FROM (
  3  SELECT INDEX_NAME, COLUMN_NAME,COLUMN_POSITION FROM t1
  4  MINUS 
  5  SELECT  INDEX_NAME, COLUMN_NAME,COLUMN_POSITION FROM t2) a 
  6  RIGHT OUTER JOIN
  7  (SELECT INDEX_NAME, COLUMN_NAME,COLUMN_POSITION  FROM t2
  8  MINUS
  9  SELECT INDEX_NAME, COLUMN_NAME, COLUMN_POSITION FROM t1) j 
 10  on a.INDEX_NAME = j.INDEX_NAME
 11  and a.COLUMN_POSITION  =  j.COLUMN_POSITION;

INDEX_NAME      COLUMN_NAME      INDEX_NAME      COLUMN_NAME
--------------- ---------------- --------------- -----------
INDEX2_IN_BASE  INT1             INDEX2_IN_BASE  INT3
INDEX2_IN_BASE  USER_ID          INDEX2_IN_BASE  INT2
INDEX_IN_REPORT TEST1            INDEX_IN_REPORT USER_ID
                                 INDEX2_IN_BASE  INT4
                                 INDEX2_IN_BASE  ACTION_DATE
I really need a FULL OUTER JOIN query to show the extra COLUNM_NAME from both t1 and t2.
Hope this will help you to understand what I try to do so that you will help me to get the right query.

Thank you very much. 

Tom Kyte
June 13, 2003 - 1:00 pm UTC

you way overcomplicated it then -- here is an example.  I believe you are trying to compare indexes in two different schema's or perhaps databases and finding differences.  Here is how:

ops$tkyte@ORA920> create user a identified by a;

User created.

ops$tkyte@ORA920> create user b identified by b;

User created.

ops$tkyte@ORA920> alter user a default tablespace users quota unlimited on users;

User altered.

ops$tkyte@ORA920> alter user b default tablespace users quota unlimited on users;

User altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table a.t1( a int, b int, c int, d int, e int, f int );

Table created.

ops$tkyte@ORA920> create index a.idx1 on a.t1(a,b,c);

Index created.

ops$tkyte@ORA920> create index a.idx2 on a.t1(d,e,f);

Index created.

ops$tkyte@ORA920> create index a.idx3 on a.t1(d,c,b,e,f,a);

Index created.

ops$tkyte@ORA920> create index a.idx10 on a.t1(a,b,c,d,e,f);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> create table b.t1( a int, b int, c int, d int, e int, f int );

Table created.

ops$tkyte@ORA920> create index b.idx2 on b.t1(e,f,d);

Index created.

ops$tkyte@ORA920> create index b.idx3 on b.t1(d,c,e,b,f,a);

Index created.

ops$tkyte@ORA920> create index b.idx4 on b.t1(a,b,c);

Index created.

ops$tkyte@ORA920> create index b.idx10 on b.t1(a,b,c,d,e,f);

Index created.

ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> break on index_name skip 1
ops$tkyte@ORA920>
ops$tkyte@ORA920> select nvl( t1.index_name, t2.index_name) index_name,
  2         t1.column_name,
  3         t2.column_name,
  4         nvl(t1.column_position,t2.column_position) pos
  5    from (select *
  6            from dba_ind_columns
  7           where index_owner = 'A') t1 full outer join
  8         (select *
  9            from dba_ind_columns
 10           where index_owner = 'B') t2
 11      on ( t1.index_name = t2.index_name
 12           and
 13           t1.column_position = t2.column_position )
 14   where t1.column_name <> t2.column_name
 15      or (t1.column_name is null and t2.column_name is not null)
 16          or (t1.column_name is not null and t2.column_name is null)
 17   order by  1, 4
 18  /

INDEX_NAME                     COLU COLU        POS
------------------------------ ---- ---- ----------
IDX1                           A                  1
                               B                  2
                               C                  3

IDX2                           D    E             1
                               E    F             2
                               F    D             3

IDX3                           B    E             3
                               E    B             4

IDX4                                A             1
                                    B             2
                                    C             3


11 rows selected.





 

Susan, June 13, 2003 - 2:39 pm UTC

Tom, when you say

If you see a query like this:

from t1, t2
where t1.x = t2.x(+)
and t2.any_other_column <some condition>

you know for a fact that outer join is a waste, a performance drain, something
to be removed from the query.

does that mean that an inner join will return the same results? I didn't write this view but was asked to tune it.

SELECT DISTINCT
MV.*, ...
AP.APPTDATETIME,
AP.PERCODE AP_PERCODE,
AP.COMMENTS...,
FROM
AREACODE_TIMEZONE AC,
APPOINTMENTS AP,
(select * from callrecord where (formstat_id,starttime) in (select formstat_id,max(starttime) from callrecord group by formstat_id))
CR,
EVENTCAT EC,
EVENTTYPE ET,
EVENTS EV,
FORM FO,
FORMSTATUS FS,
MAST_HOUS MV
WHERE
(EV.EVENTTYPE_ID = ET.EVENTTYPE_ID)
AND (FS.MASTER_ID = EV.MASTER_ID)
AND (EV.EVENTTYPE_ID = FS.EVENTTYPE_ID)
AND (FS.FORM_ID = FO.FORM_ID)
AND (FO.ISCATI =1)
AND (MV.MASTER_ID = EV.MASTER_ID)
AND (FS.FORMSTAT_ID = CR.FORMSTAT_ID (+) )
AND (ET.ECATCODE = EC.ECATCODE)
AND (FS.MASTER_ID = AP.MASTER_ID (+))
AND (FS.EVENTTYPE_ID = AP.EVENTTYPE_ID(+))
AND(AP.APPT_TYPE_ID(+) = 800)
AND (AP.APPTDATETIME(+)>TRUNC(SYSDATE))
AND (ET.USECATI = 1)
AND (AC.AREACODE = AreaCode (MV.CATIPHONE))
AND (FS.STATUS != 'M')
ORDER BY MV.MASTER_ID, FS.FORMSTAT_ID;

I removed the outer joins on AP, but, the results differed. Is there a better way? Thanks

Tom Kyte
June 13, 2003 - 3:44 pm UTC

how could you remove the outer join on AP? it did not meet the criteria.

You always outer joined to it -- only if you saw:

where ap.x(+) = fs.eventtype_id
and ap.y = 5

would you be able to safely remove it.

Outer Join Issue

Sikandar Hayatq, July 26, 2003 - 1:41 am UTC

During using out joins I am facing the following issue which I am unable to understand,

I am using two queries and just chanding e.name to e.empno and the query works but why not working with e.ename.

SQL>  select e.ename, d.dname from emp e full outer join dept d
  2   on (e.deptno=d.deptno);
 on (e.deptno=d.deptno)
                      *
ERROR at line 2:
ORA-00600: internal error code, arguments: [qcscpqbTxt], [904], [], [], [], [], [], []


SQL>  select e.empno, d.dname from emp e full outer join dept d
  2   on (e.deptno=d.deptno);

     EMPNO DNAME
---------- --------------
      7782 ACCOUNTING
      7839 ACCOUNTING
      7934 ACCOUNTING
      7369 RESEARCH
      7876 RESEARCH
      7902 RESEARCH
      7788 RESEARCH
      7566 RESEARCH
      7499 SALES
      7698 SALES
      7654 SALES
      7900 SALES
      7844 SALES
      7521 SALES
           OPERATIONS

15 rows selected.

SQL> 

 

Tom Kyte
July 26, 2003 - 12:41 pm UTC

please contact support and file a tar for that one.

I tried in 9ir1 and 9ir2 and could not reproduce your findings.

Order of FULL OUTER JOINS

Adrian Billington, August 11, 2003 - 9:56 am UTC

Tom

Every day we get a bunch of source files to be loaded to an accounts table. We have external tables over the current day's files and the previous day's files and we MINUS them to get just the changes that have occurred since yesterday. These deltas are inserted into GTTs.

I'm trying to MERGE the GTTs with the base table and because all the GTTs contain different parts of an account record, I am trying full outer joins to create a "logical" record for MERGE.

In my particular example, I have five delta GTTs ranging from 1 to 850 records only. If I FULL OUTER JOIN the GTTs in record count descending order, my load takes 34 CPU secs. If I FOJ in ascending order then it takes 790 CPU seconds.

How can I mitigate this ( short of programatically counting the records in the delta GTTs every day and dynamically full outer joining them in the descending order ). Is there some other method I should be thinking of, because FULL OUTER JOIN sure does cost a bit...

Regards
Adrian

Tom Kyte
August 11, 2003 - 10:06 am UTC

full outer joins are heinously expensive.

select * from emp full outer join dept on (emp.deptno = dept.deptno)

is processed much like:


select *
from emp, dept
where emp.deptno = dept.deptno(+)
UNION ALL
select *
from emp, dept
where emp.deptno(+) = dept.deptno
and emp.deptno IS NULL;


do that a couple of times and -- well -- it gets "hard"


are you using 9iR2? if so, read about OPTIMIZER_DYNAMIC_SAMPLING and set it to 3 or above.

I have my stats...

Adrian, August 11, 2003 - 12:03 pm UTC

Tom

Yes, I am on 9.2.0.3 and am using dynamic sampling at level 5 ( I love this feature the most of all ).

You're right - it starts to get horrible and seems to work much better when I work in rowcount descending order, but I can't guarantee that order every day. Perhaps I'm trying to be too clever, but if I don't make this stuff beat PowerMart I can kiss goodbye to my Oracle skills... :o(

Thanks
Adrian

Tom Kyte
August 11, 2003 - 2:00 pm UTC

what does the FOJ look like here?

One hell of a plan...

Adrian Billington, August 12, 2003 - 4:39 am UTC

Tom

Trying to upload the output of running a FOJ in rowcount descending and ascending orders but continually getting timeouts. Will mail it to thomas.kyte@oracle.com instead.

As an alternative in the meantime, I'm thinking of preparing the records up-front in a GTT, merging into the GTT one file (external table) at a time. Then when it's all ready, MERGE into the base table once.

Regards
Adrian

display rowwise

Tani, September 25, 2003 - 1:10 pm UTC

hi tom ..
is it possible to display the column values of a table in a single row ..

suppose for emp table ..if i write

select sal from emp .. it will display like this

sal
---
100
200
300
400
500

i want to display the same on a single row ..

100 200 300 400 500 ..

any suggesstion ....

though there is no relevance for this ..but somebody asks the same in interviews ..

Regards
Tani

Tom Kyte
September 25, 2003 - 11:28 pm UTC

only if you now the max number of rows, then yes you can. else no you cannot (well sort of, i can with object types -- cast it into a collection)

thankx

tanmoy datta choudhury, September 26, 2003 - 4:32 am UTC

thanks for the reply ...
now for emp table we knw that there are 14 rows ..
how to display sal in a single row ...then ..

Regards
Tani


Tom Kyte
September 26, 2003 - 6:21 am UTC

select max( decode( rownum, 1, sal ) ),
max( decode( rownum, 2, sal ) ),
max( decode( rownum, 3, sal ) ),
....
max( decode( rownum, 14, sal ) )
from emp;

Substring and OuterJoin

Bhavani, October 28, 2003 - 8:08 am UTC

Hi Tom,

I have a query like this:

create table t_tracking
as
select a.f_id, a.f_access_time, a.f_dest_page, a.f_status, a.f_cid, a.f_web_id, a.f_fe, b.f_id f_base_dest_page_id,
a.f_name from t_id_tracking1 a, t_id_category b
where a.f_access_time < '28-OCT-03'
and substr(f_dest_page, 1, instr(f_dest_page, '?') -1) (+) = lower(b.f_base_destination_page);

But I am getting:

ERROR at line 6:
ORA-00920: invalid relational operator

Can't we use substring and Outer Join in a single query? or Am I doing some thing wrong?

Thanks for your time.

Best Regards
Bhavani



Tom Kyte
October 28, 2003 - 8:22 am UTC

i see so many wrong things

what is '28-oct-03' -- is that supposed to be a date?  please -- use to_date and PLEASE use the absolute minimum number of characters needed to accurately and unambigously specify a year -- 4.  2003, not 03.  

You are joining based on functions. (ugh)

you seem to be storing data "willy nilly" -- the use of LOWER for example.  why would you have to lower the data?

I'm assuming f_dest_page is in table A.  If so, why would you "outer join" in one predicate -- but not the other?  That just seems plain wrong

You can use inline views as below -- but the performance of this query is going to be absolutely horrible

ops$tkyte@ORA920PC> select dname, ename
  2    from emp, dept
  3   where (substr( emp.deptno, 1, 2 )) (+) = to_char( dept.deptno )
  4  /
 where (substr( emp.deptno, 1, 2 )) (+) = to_char( dept.deptno )
                                      *
ERROR at line 3:
ORA-00920: invalid relational operator
 
 
ops$tkyte@ORA920PC> select dname, ename
  2    from (select substr(deptno,1,2) deptno, ename from emp ) emp,
  3         (select to_char(deptno) deptno, dname from dept ) dept
  4   where emp.deptno (+) = dept.deptno
  5  /
 
DNAME          ENAME
-------------- ----------
ACCOUNTING     CLARK
ACCOUNTING     KING
ACCOUNTING     MILLER
RESEARCH       SMITH
RESEARCH       ADAMS
RESEARCH       FORD
RESEARCH       SCOTT
RESEARCH       JONES
SALES          ALLEN
SALES          BLAKE
SALES          MARTIN
SALES          JAMES
SALES          TURNER
SALES          WARD
OPERATIONS
 
15 rows selected.




 

full outer join

Scott Wesley, November 05, 2003 - 2:47 am UTC

G'day Tom (long time, no post)

I hope I'm not flogging a dead horse here, but in regards to full outer joins on the example of Customers with Sales and Budgets (3 tables).
Similar to Anil's debit/credit example I guess, but in separate tables.
9i would allow FULL OUTER JOIN syntax, which I'm not familiar with, since I have 8174.

What do you think of the following solution (I didn't find the 817 dept-emp example appropriate):

select acc_nbr, sum(sales), sum(budget)
from
(select acc_nbr, sales, 0 budget
from ths_sales
union
select acc_nbr, 0, budget
from ths_budgets
)
group by acc_nbr, catgy;

in regards to performance, readability, alternatives...

We find this quite effective, and the only real solution to our problem where customer might have sales and no budget, or budget and no sales.

Solution found in Metalink was not suitable for our more complex example (</code> http://metalink.oracle.com/cgi-bin/cr/getfile.cgi?p_attid=132416.1:275803 <code>
Yes, we are using Discovererer.

Thanks.


Tom Kyte
November 05, 2003 - 8:38 am UTC

well, i would replace the UNION with UNION ALL:

drop table t1;
drop table t2;

create table t1 as select rownum acc_nbr, object_id sales, a.* from
all_objects a;
create table t2 as select rownum acc_nbr, data_object_id budget, a.* from
all_objects a;

analyze table t1 compute statistics for table;
analyze table t2 compute statistics for table;



select acc_nbr, sum(sales), sum(budget)
from
(select acc_nbr, sales, 0 budget
from t1 ths_sales
union
select acc_nbr, 0, budget
from t2 ths_budgets
)
group by acc_nbr

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2014 1.19 1.16 53 898 0 30192
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 2016 1.19 1.16 53 898 0 30192

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 211

Rows Row Source Operation
------- ---------------------------------------------------
30192 SORT GROUP BY (cr=898 r=53 w=0 time=1013152 us)
60383 VIEW (cr=898 r=53 w=0 time=794692 us)
60383 SORT UNIQUE (cr=898 r=53 w=0 time=643063 us)
60383 UNION-ALL (cr=898 r=53 w=0 time=398021 us)
30191 TABLE ACCESS FULL T1 (cr=456 r=1 w=0 time=45277 us)
30192 TABLE ACCESS FULL T2 (cr=442 r=52 w=0 time=48966 us)
********************************************************************************
select acc_nbr, sum(sales), sum(budget)
from
(select acc_nbr, sales, 0 budget
from t1 ths_sales
union all
select acc_nbr, 0, budget
from t2 ths_budgets
)
group by acc_nbr

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2014 0.99 0.94 55 898 0 30192
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 2016 0.99 0.95 55 898 0 30192

Rows Row Source Operation
------- ---------------------------------------------------
30192 SORT GROUP BY (cr=898 r=55 w=0 time=798323 us)
60383 VIEW (cr=898 r=55 w=0 time=556844 us)
60383 UNION-ALL (cr=898 r=55 w=0 time=406987 us)
30191 TABLE ACCESS FULL T1 (cr=456 r=1 w=0 time=45889 us)
30192 TABLE ACCESS FULL T2 (cr=442 r=54 w=0 time=52493 us)


but yes, this technique works QUITE nicely. You would be using it even in light of the HUGELY expensive full outer join (i'm not hugely fond of them, i see them as a forth coming performance issue. developers who don't know if they need to use it, will use it (like they use outer joins when its not needed) leading to really expensive plans)


select nvl( ths_sales.acc_nbr, ths_budget.acc_nbr ), sum(sales), sum(budget)
from t1 ths_sales full outer join t2 ths_budget on ( ths_sales.acc_nbr = ths_budget.acc_nbr )
group by nvl( ths_sales.acc_nbr, ths_budget.acc_nbr )

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2014 1.33 1.33 140 1796 0 30192
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 2016 1.33 1.33 140 1796 0 30192

Rows Row Source Operation
------- ---------------------------------------------------
30192 SORT GROUP BY (cr=1796 r=140 w=0 time=1177893 us)
30192 VIEW (cr=1796 r=140 w=0 time=1031680 us)
30192 UNION-ALL (cr=1796 r=140 w=0 time=950802 us)
30191 HASH JOIN OUTER (cr=898 r=67 w=0 time=424276 us)
30191 TABLE ACCESS FULL T1 (cr=456 r=13 w=0 time=47248 us)
30192 TABLE ACCESS FULL T2 (cr=442 r=54 w=0 time=55104 us)
1 HASH JOIN ANTI (cr=898 r=73 w=0 time=372206 us)
30192 TABLE ACCESS FULL T2 (cr=442 r=60 w=0 time=50634 us)
30191 TABLE ACCESS FULL T1 (cr=456 r=13 w=0 time=48348 us)


To "Bhavani from India"

Logan Palanisamy, November 05, 2003 - 6:24 pm UTC

Bhavani,

You wrote
<Quote>
I have a query like this:

create table t_tracking
as
select a.f_id, a.f_access_time, a.f_dest_page, a.f_status, a.f_cid, a.f_web_id,
a.f_fe, b.f_id f_base_dest_page_id,
a.f_name from t_id_tracking1 a, t_id_category b
where a.f_access_time < '28-OCT-03'
and substr(f_dest_page, 1, instr(f_dest_page, '?') -1) (+) =
lower(b.f_base_destination_page);

But I am getting:

ERROR at line 6:
ORA-00920: invalid relational operator

Can't we use substring and Outer Join in a single query? or Am I doing some
thing wrong?
<End Quote>

Your problem is the outer join notation "(+)" should come close to the column name, not before the = sign as you have specified.

The wrong way:
substr(f_dest_page, 1, instr(f_dest_page, '?') -1) (+) =

The right way:
substr(f_dest_page(+), 1, instr(f_dest_page, '?') -1) =

Of course, whatever Tom said are still valid.



To "Bhavani from India"

Logan Palanisamy, November 05, 2003 - 7:26 pm UTC

Oops!!

The really correct one is

substr(f_dest_page(+), 1, instr(f_dest_page(+), '?') -1) = ...


outer join OR something else

Karma, December 30, 2003 - 8:52 pm UTC

Hi: I have question where i think i need outer join and also other condition applied and dont know how to get correct answer: So i have 3 tables ab_mstr,ab_trx,and ab_rest. I want
All rows from ab_mstr that DO NOT exist in ab_trx (key = f_firm_c,f_br_c, f_ba_c) and
ALL rows from ab_trx where ab_trx.f_txn = 0 and discard or do not return this records if ab_rest.rest_id = 'Q1' or 'Q2' where trx (firM_c,f_br_c,f_ba_c) = rest.(firm_c,f_ba_c, f_br_c)

CREATE TABLE AB_MSTR
(
F_FIRM_C VARCHAR2(4 BYTE) NOT NULL,
F_BR_C VARCHAR2(3 BYTE) NOT NULL,
F_BA_C VARCHAR2(6 BYTE) NOT NULL,
EFF_D DATE NOT NULL

)
drop table ab_trx
create table ab_trx
( f_firm_c varchar2(4 byte) not null,
f_br_c varchar2 (3 byte) not null,
f_ba_c varchar2(6 byte) not null,
f_txn number,
trx_dt date
)

create table ab_rest
( f_firm_c varchar2(4 byte) not null,
f_br_c varchar2 (3 byte) not null,
f_ba_c varchar2(6 byte) not null,
rest_id varchar2(2)
)

insert into ab_mstr values ('0101','ABB','123456',sysdate)
insert into ab_mstr values ('0101','BCC','234567',sysdate)

insert into ab_trx values ('0101','ABB','123456',0,sysdate+5)
insert into ab_trx values ('0101','EEE','123456',0,sysdate)

insert into ab_rest values ('0101','EEE','123456','Q1')
insert into ab_rest values ('0101','EEE','123456','Q2')
insert into ab_rest values ('0101','EEE','123456','C1')

i do know this is wrong!!!

select trx.f_firm_c, trx.f_br_c, trx.f_ba_c from ab_trx trx, ab_rest rest
where trx.f_firm_c = rest.F_FIRM_C(+)
and trx.F_BR_C = rest.F_BR_C(+)
and trx.F_BA_C = rest.F_BA_C(+)
and trx.f_txn = 0
and rest.rest_id IN ('Q1','Q2')

Thanks

Tom Kyte
December 31, 2003 - 9:26 am UTC


....
All rows from ab_mstr that DO NOT exist in ab_trx (key = f_firm_c,f_br_c,
f_ba_c) and
ALL rows from ab_trx where ab_trx.f_txn = 0 and discard or do not return this
records if ab_rest.rest_id = 'Q1' or 'Q2' where trx (firM_c,f_br_c,f_ba_c) =
rest.(firm_c,f_ba_c, f_br_c)
.......


you want all rows from T1 that do not exist in T2
AND
you want all rows in T2 subject to some criteria


that -- well, it quite simply doesn't compute to me. don't see how they would be presented in a result set.

outer join with what?

karma, December 30, 2003 - 9:20 pm UTC

In previous case i forgot to mention but correct answer would be:
1 row returned from ab_mstr since this row only exists in ab_mstr and not in ab_trx.
('0101','BCC','234567')
also ONLY 1 row from ab_trx even though all 3 rows has f_txn = 0 but 2 rows gets rejected since they have rest_id of 'Q1' or 'Q2' in ab_rest table.
insert into ab_trx values ('0101','ABC','123456',0,sysdate+5)


outer join or core-related subquery

karma, December 31, 2003 - 9:39 am UTC

Well here is what T1, T2 and T3 means. T1 has all accounts, T2 has data for daily trading activity for accounts. T3 has list of restrictions codes assigned to accounts. I want to generate inactive report so
Given this any accounts that exists in T1 but not in T2 implies that this accounts do not have any activity and thus should be included. I also want to include any accounts in T2 where some count = 0 ( count = 0 means no trading activity) and EXCLUDE any of this accounts from T2 who may have some restrictions assinged to it in T3.

Hope this is clear

Tom Kyte
December 31, 2003 - 10:21 am UTC

something like this maybe.

select ...
from t1, t2
where t1.key = t2.fkey(+)
and ( t2.key is null -- get the t1 records that have no matches in t2
or -- get the records with count=0, and no restrictions
(t2.key is not null and
t2.count = 0 and
not exists ( select null
from t3
where t3.key = t2.fkey )
)



Performance issue

Justin, February 09, 2004 - 3:28 pm UTC

I'n having a peformance problem with this query:

SELECT DISTINCT t1.fi_nr,
t1.redemption_dt,
t1.redemption_am,
t1.void_reason_cd,
t1.void_dt,
t1.reissued_fi_cd,
t1.reissued_to_vendor_cd,
t9.vendor_id,
t6.family_id,
t7.Person_id,
t1.lost_stolen_cd,
t1.lost_stolen_dt,
t1.bank_extract_in,
t1.fi_proc_status_cd,
decode(rtrim(t7.last_name),NULL,'') + ', ' + decode(rtrim(t7.first_name),NULL,'') as PartName,
t1.fi_picked_up_in,
t1.not_to_exceed_am,
t1.t022_id,
t1.first_use_dt,
t1.last_use_dt,
t1.last_updt_user_id,
t1.last_updt_ts,
t1.last_updt_prg_id
FROM t038_fi t1
LEFT JOIN t040_FI_Spec t2 on t1.t040_id = t2.t040_id
LEFT JOIN t071_Prescription t3 on t2.T071_id = t3.T071_id
LEFT JOIN t067_Participant t4 on t3.t067_id = t4.t067_id
LEFT JOIN t069_Person t7 on t4.t069_id = t7.t069_id
LEFT JOIN t033_Fam_members t5 on t4.t069_id = t5.t069_id
LEFT JOIN t014_Benefit_Fam t6 on t5.t014_id = t6.t014_id
LEFT JOIN t093_vendor t9 on t1.t093_id = t9.t093_id
WHERE t5.effective_dt = (select max(effective_dt) from t033_fam_members t10 where t7.t069_id = t10.t069_id)
AND t7.person_id in (376566, 120750, 120751, 495142)
ORDER BY t6.family_id asc, t7.Person_id asc, t1.first_use_dt desc;

This query is executed with execute immediate and is built dynamically by passing the person ids in as a varchar2.

version 9.2.0.4

Here are table counts:

wic_owner8@DEV> select count(*) from t040_FI_Spec;

COUNT(*)
=================
2788236

wic_owner8@DEV> select count(*) from t071_Prescription;

COUNT(*)
=================
1061599

wic_owner8@DEV> select count(*) from t067_Participant;

COUNT(*)
=================
473394

wic_owner8@DEV> select count(*) from t069_Person;

COUNT(*)
=================
711300

wic_owner8@DEV> select count(*) from t033_Fam_members;

COUNT(*)
=================
798470

wic_owner8@DEV> select count(*) from t014_Benefit_Fam;

COUNT(*)
=================
272898

wic_owner8@DEV> select count(*) from t093_vendor;

COUNT(*)
=================
2118

If I change the outer joins to inner joins, the query executes in less than 1 second.
In SQL Server (much to my chagrin) the outer join version executes in less than 1 second.

Here is the execution plan for the outer join version... eeEWW

Execution Plan
==========================================================
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=1866655 Card=3584 Bytes=1150464)
1 0 SORT (UNIQUE) (Cost=1039445 Card=3584 Bytes=1150464)
2 1 FILTER
3 2 SORT (GROUP BY) (Cost=1039445 Card=3584 Bytes=1150464)
4 3 HASH JOIN (Cost=212235 Card=7954446 Bytes=2553377166)
5 4 INDEX (FAST FULL SCAN) OF 'T033_COLSET1_INDEX' (NON-UNIQUE) (Cost=4 Card=798605 Bytes=1118
0470)

6 4 FILTER
7 6 HASH JOIN (OUTER)
8 7 HASH JOIN (OUTER) (Cost=129856 Card=7085309 Bytes=1601279834)
9 8 HASH JOIN (OUTER) (Cost=103509 Card=7085309 Bytes=1466658963)
10 9 HASH JOIN (OUTER) (Cost=80934 Card=5583430 Bytes=1005017400)
11 10 HASH JOIN (OUTER) (Cost=66068 Card=5583430 Bytes=910099090)
12 11 HASH JOIN (OUTER) (Cost=49697 Card=5583430 Bytes=804013920)
13 12 HASH JOIN (OUTER) (Cost=32461 Card=5583430 Bytes=703512180)
14 13 TABLE ACCESS (FULL) OF 'T038_FI' (Cost=9403 Card=5583430 Bytes=597427010)
15 13 TABLE ACCESS (FULL) OF 'T040_FI_SPEC' (Cost=1899 Card=2789739 Bytes=530050
41)

16 12 TABLE ACCESS (FULL) OF 'T071_PRESCRIPTION' (Cost=841 Card=1061688 Bytes=1911
0384)

17 11 TABLE ACCESS (FULL) OF 'T067_PARTICIPANT' (Cost=710 Card=473389 Bytes=8994391)
18 10 TABLE ACCESS (FULL) OF 'T093_VENDOR' (Cost=8 Card=2118 Bytes=36006)
19 9 INDEX (FAST FULL SCAN) OF 'T033_COLSET1_INDEX' (NON-UNIQUE) (Cost=4 Card=798605 By
tes=21562335)

20 8 TABLE ACCESS (FULL) OF 'T014_BENEFIT_FAM' (Cost=327 Card=273034 Bytes=5187646)
21 7 TABLE ACCESS (FULL) OF 'T069_PERSON' (Cost=1392 Card=711346 Bytes=57619026)


Here is the execution plan for the inner join version...

Execution Plan
==========================================================
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=42 Card=1 Bytes=321)
1 0 SORT (UNIQUE) (Cost=39 Card=1 Bytes=321)
2 1 FILTER
3 2 SORT (GROUP BY) (Cost=39 Card=1 Bytes=321)
4 3 HASH JOIN (Cost=37 Card=62 Bytes=19902)
5 4 TABLE ACCESS (BY INDEX ROWID) OF 'T038_FI' (Cost=2 Card=2 Bytes=214)
6 5 NESTED LOOPS (Cost=28 Card=62 Bytes=18848)
7 6 NESTED LOOPS (Cost=15 Card=36 Bytes=7092)
8 7 NESTED LOOPS (Cost=10 Card=14 Bytes=2492)
9 8 NESTED LOOPS (Cost=8 Card=6 Bytes=960)
10 9 NESTED LOOPS (Cost=6 Card=6 Bytes=846)
11 10 NESTED LOOPS (Cost=4 Card=5 Bytes=570)
12 11 NESTED LOOPS (Cost=3 Card=4 Bytes=400)
13 12 INLIST ITERATOR
14 13 TABLE ACCESS (BY INDEX ROWID) OF 'T069_PERSON' (Cost=2 Card=4 Bytes=324)
15 14 INDEX (RANGE SCAN) OF 'X069IE01' (UNIQUE) (Cost=1 Card=4)
16 12 TABLE ACCESS (BY INDEX ROWID) OF 'T067_PARTICIPANT' (Cost=2 Card=1 Bytes=1
9)

17 16 INDEX (UNIQUE SCAN) OF 'X067IE01' (UNIQUE)
18 11 TABLE ACCESS (BY INDEX ROWID) OF 'T033_FAM_MEMBERS' (Cost=2 Card=1 Bytes=14)
19 18 INDEX (RANGE SCAN) OF 'X033FK02' (NON-UNIQUE)
20 10 TABLE ACCESS (BY INDEX ROWID) OF 'T033_FAM_MEMBERS' (Cost=2 Card=1 Bytes=27)
21 20 INDEX (RANGE SCAN) OF 'X033FK02' (NON-UNIQUE)
22 9 TABLE ACCESS (BY INDEX ROWID) OF 'T014_BENEFIT_FAM' (Cost=2 Card=1 Bytes=19)
23 22 INDEX (UNIQUE SCAN) OF 'X014PK' (UNIQUE)
24 8 TABLE ACCESS (BY INDEX ROWID) OF 'T071_PRESCRIPTION' (Cost=2 Card=2 Bytes=36)
25 24 INDEX (RANGE SCAN) OF 'X071FK01' (NON-UNIQUE)
26 7 TABLE ACCESS (BY INDEX ROWID) OF 'T040_FI_SPEC' (Cost=2 Card=3 Bytes=57)
27 26 INDEX (RANGE SCAN) OF 'X040FK01' (NON-UNIQUE)
28 6 INDEX (RANGE SCAN) OF 'X038FK03' (NON-UNIQUE)
29 4 TABLE ACCESS (FULL) OF 'T093_VENDOR' (Cost=8 Card=2118 Bytes=36006)

I completely agree with you about understanding the question before you tune a query. This query answers the question, so I am in tuning mode.

Does this query fall under the category of one of your previous answer that the outer joins are useless because of the "t7.personid IN (..." in the predicate?

I have been looking in EOBD (your book) on page 505 to see if that seems prudent, but I feel I should be able to get this query working without jumping through hoops.

Thanks much,

Tom Kyte
February 09, 2004 - 4:20 pm UTC

FROM t038_fi t1
LEFT JOIN t040_FI_Spec t2 on t1.t040_id = t2.t040_id
LEFT JOIN t071_Prescription t3 on t2.T071_id = t3.T071_id
LEFT JOIN t067_Participant t4 on t3.t067_id = t4.t067_id
LEFT JOIN t069_Person t7 on t4.t069_id = t7.t069_id
LEFT JOIN t033_Fam_members t5 on t4.t069_id = t5.t069_id
LEFT JOIN t014_Benefit_Fam t6 on t5.t014_id = t6.t014_id

LEFT JOIN t093_vendor t9 on t1.t093_id = t9.t093_id
WHERE t5.effective_dt = (select max(effective_dt) from t033_fam_members t10
where t7.t069_id = t10.t069_id)
AND t7.person_id in (376566, 120750, 120751, 495142)
ORDER BY t6.family_id asc, t7.Person_id asc, t1.first_use_dt desc;

do you understand what left join does?

t5 should not be left joined to. You have t5.column = <some value>. Well, if t5.column must be equal to some value -- it cannot be NULL hence, outer joining to it doesn't make sense.

t7.column = t10.column -- again, t7 as an outer join target = bad id.

lose the outerjoins, use them EXACTLY if and ONLY IF You know for a fact you need them.

reader, February 09, 2004 - 7:37 pm UTC

Hi, Tom

I have the following two questions about the outer join
1.
SELECT ...
FROM
.
.
.
AND T.A = T1.A (+)
AND T.B = T1.B(+)
AND T.C = T1.C (+)
AND T1.D (+) = 'Y'
AND T1.E (+) = 'Y'
.
.
.

What is means
"AND T1.D (+) = 'Y'
AND T1.E (+) = 'Y'"

Are they same as
"
AND T1.D = 'Y'
AND T1.E = 'Y'"



question2

What is the follow outer join for ?

SELECT ...
FROM T,T1
WHERE
.
.
.
AND
SYSDATE
BETWEEN
T1.DATA1(+) AND T2.DATA2(+);
.
.
.

Thank you!




Tom Kyte
February 09, 2004 - 8:51 pm UTC

1) think of 'Y' and 'Y' as columns "D" and "E" from T, like this:

ops$tkyte@ORA9IR2> create table t ( a int, b int, c int );
 
Table created.
 
ops$tkyte@ORA9IR2> insert into t values ( 1,1,1);
 
1 row created.
 
ops$tkyte@ORA9IR2> create table t1 ( a int, b int, c int, d char(1), e char(1) );
 
Table created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select *
  2    from t, t1
  3   where t.a = t1.a(+)
  4     and t.b = t1.b(+)
  5     and t.c = t1.c(+)
  6     and 'Y' = t1.d(+)
  7     and 'Y' = t1.e(+)
  8  /
 
         A          B          C          A          B          C D E
---------- ---------- ---------- ---------- ---------- ---------- - -
         1          1          1
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select *
  2    from t, t1
  3   where t.a = t1.a(+)
  4     and t.b = t1.b(+)
  5     and t.c = t1.c(+)
  6     and 'Y' = t1.d
  7     and 'Y' = t1.e
  8  /
 
no rows selected


Now you can see what happens.  If you outer join to T1 and in the predicate you have

t1.any_column = <some value>

you KNOW for a fact that either

a) you meant to outer join to some value
b) you didn't need to outer join in the first place...



2) does not compute, where is t2?
 

Partially it seems...

Justin, February 10, 2004 - 7:39 am UTC

An FI is considered a "bank check". We need to have check data returned to the screen that may not have bank redemption information yet. In other words, we may want to see check data for a check that has not been redeemed yet.

Here is what I understand about an outer join. If I need to see data for the driving table whether the other tables in the query return data or not. Is that correct? And T038_FI is my driving table, correct?

So is this what you are advocating?

FROM t038_fi t1
LEFT JOIN t040_FI_Spec t2 on t1.t040_id = t2.t040_id
LEFT JOIN t071_Prescription t3 on t2.T071_id = t3.T071_id
LEFT JOIN t067_Participant t4 on t3.t067_id = t4.t067_id
INNER JOIN t069_Person t7 on t4.t069_id = t7.t069_id
INNER JOIN t033_Fam_members t5 on t4.t069_id = t5.t069_id
LEFT JOIN t014_Benefit_Fam t6 on t5.t014_id = t6.t014_id
LEFT JOIN t093_vendor t9 on t1.t093_id = t9.t093_id
WHERE t5.effective_dt = (select max(effective_dt) from t033_fam_members t10
where t7.t069_id = t10.t069_id)
AND t7.person_id in (376566, 120750, 120751, 495142)
ORDER BY t6.family_id asc, t7.Person_id asc, t1.first_use_dt desc;


Thanks very much for your help.


Tom Kyte
February 10, 2004 - 7:45 am UTC

well, actually -- it goes further then that.


Lets just look at T5 for a moment.

t5.effective_dt = "some value" means -- no need to outer join to t5 since if we did -- effective_dt would be NULL and NULL is never equal to some value. Soooo

t4 must not need to be outer joined to EITHER. why? Cause we look up the record in T5 based on T4 and if t4.t069_id is NULL, well, there won't be a mate in T5 and since we are not outer joining to T5 well -- t4.t069_id being null doesn't work for us. So, t4 doesn't need it either.

Suggestion: sit down and rip this puppy apart. Understand what you are going for and why. By having the predicates on t5 and t7 -- you've probably blasted the outer joins out of "meaningful existence" here.

Very helpful response

Justin, February 10, 2004 - 8:18 am UTC

Thank you very much Tom.

You're up early!, and I appreciate that :)

Outer join

Krishna, February 10, 2004 - 1:14 pm UTC


outer join

reader, February 10, 2004 - 7:39 pm UTC

Hi, Tom

Thank you for the previous answers.

I have a furter question:

If

select count(*)
from t, t1
where t.a = t1.a(+)
and t.b = t1.b(+)
and t.c = t1.c(+)
and 'Y' = t1.d(+)
and 'Y' = t1.e(+)

will be the same with

select count(*)
from t, t1
where t.a = t1.a(+)
and t.b = t1.b(+)
and t.c = t1.c(+);

?

OR for some reason I only want to count some fields of table t from this query,


are the
and 'Y' = t1.d(+)
and 'Y' = t1.e(+)
filters ?

because if
and 'Y' = t1.d(+)
and 'Y' = t1.e(+)
are not filters , I can get rid of the join wiht t1.

Can I get rid of the join wiht t1 in this case ?


2, Sorry for the typo for previous question

should be :

What is the follow outer join for ?

SELECT ...
FROM T,T1
WHERE
.
.
T.a=T1.a(+)
AND
SYSDATE
BETWEEN
T1.DATA1(+) AND T1.DATA2(+)
.
.
.
;

What is means
"
SYSDATE
BETWEEN
T1.DATA1(+) AND T1.DATA2(+)
"

outer join here ?

Thank you!




Tom Kyte
February 11, 2004 - 8:59 am UTC

You are going to want to start trying to conceptualize what the outer join does.  Once you can "imagine it", you'll be able to answer these questions readily.

The 'Y' constraints only keep records from T1 where they are 'Y' and if no 'Y' records exist, it'll make up one.  So, knowing that, all you need is an example where there are lots of T1 records that match EXCEPT FOR 'Y' and it is easy to see that no, those two queries are no where similar:

ops$tkyte@ORA9IR2> select * from t;
 
         A          B          C
---------- ---------- ----------
         1          1          1
 
ops$tkyte@ORA9IR2> select * from t1;
 
         A          B          C D E
---------- ---------- ---------- - -
         1          1          1 n n
         1          1          1 n n
         1          1          1 n n
         1          1          1 n n
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select count(*)
  2   from t, t1
  3    where t.a = t1.a(+)
  4      and t.b = t1.b(+)
  5            and t.c = t1.c(+)
  6              and 'Y' = t1.d(+)
  7                    and 'Y' = t1.e(+)
  8  /
 
  COUNT(*)
----------
         1
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select count(*)
  2   from t, t1
  3    where t.a = t1.a(+)
  4      and t.b = t1.b(+)
  5            and t.c = t1.c(+)
  6  /
 
  COUNT(*)
----------
         4
 
ops$tkyte@ORA9IR2>



Your comments in #1 after that -- about removing the join, don't compute.  If you just wanted to count T, T1 should not be in the picture at all.  If you wanted to count the effects of joining T to T1 where (d,e) = ('Y','Y') OR there are no (d,e) = ('Y','Y'), you need the outer join.

what you need to code here is 100% dependent on the question you want to answer.

2) it means the same thing as anything when you see (+).  It says "if no match in the other table --t1 in this case -- for the join condition (yes, joins do not have to just be '=', they can be ANY operation really) make up a row with all nulls please"

ops$tkyte@ORA9IR2> select * from t;
 
         A
----------
         1
         2
 
ops$tkyte@ORA9IR2> select * from t1;
 
         A DATA1     DATA2
---------- --------- ---------
         1 10-FEB-04 12-FEB-04
         2 09-FEB-04 10-FEB-04
 
ops$tkyte@ORA9IR2> select *
  2    from t, t1
  3   where t.a = t1.a (+)
  4     and sysdate between t1.data1 (+) and t1.data2 (+)
  5  /
 
         A          A DATA1     DATA2
---------- ---------- --------- ---------
         1          1 10-FEB-04 12-FEB-04
         2
 
ops$tkyte@ORA9IR2>


so, all rows in T come back AND IF there is a row in T1 such that date1 <= sysdate <= date2 -- return that data as well.

 

OK

George, February 25, 2004 - 12:26 am UTC

Dear Sir,
Can we do Outer join with a *single* table?Please specify a
simple example for that?
Bye!

Tom Kyte
February 25, 2004 - 9:03 am UTC

does not make sense. give me a concrete example of what you mean by that.

Outer Join?

Vinnie, February 26, 2004 - 4:57 pm UTC

Tom,

I have the following:
Table A
table_name
col_name

Data for A
EMP NAME
EMP LNAME
EMP ID

Table B
table_name
col_name
flag

Data for B:
EMP FNAME Y
EMP LNAME N

I would like to return all COL_NAMES from both tables that match & also any col_names in table A that are not in table B. But only if B.FLAG = 'Y'.

ex.
result set
FNAME
ID

Tom Kyte
February 26, 2004 - 6:16 pm UTC

how can a row in A that is not in B have a B.FLAG='Y' pray tell?


Seems more like you want all rows in A that are not in B (a not in or not exists) UNIONED with all rows in B that have B.FLAG='Y' (a simple predicate).

Full Outer Join Bug???

Nopparat V., February 28, 2004 - 1:30 am UTC

Hi Tom,
Could you help me find out what's a problem on this query:

SQL> select nvl(a.pay_qtr, b.pay_quarter) pay_q,
  2         nvl(a.clm_l,0) clm_l, nvl(a.clm_a,0) clm_a,
  3         nvl(a.clm_t,0) clm_t, nvl(b.clm_h,0) clm_h
  4  from
  5   (select pay_qtr,
  6      sum(decode(flag_death,'Y', decode(plan_type,'L', re_pay_amt,0),0)) clm_l,
  7      sum(decode(flag_death,'Y', decode(plan_type,'R', re_pay_amt,0),0)) clm_a,
  8      sum(decode(flag_death,'N', decode(plan_type,'R', re_pay_amt,0),0)) clm_t
  9    from claim_paid_by_reinsurer
 10    where reinsurer_id = :reinsurer_id and pay_year = :pay_year
 11    group by pay_qtr) a
 12  full outer join
 13   (select pay_quarter, sum(re_pay_amt) clm_h
 14    from claim_h_paid_by_reinsurer
 15    where reinsurer_id = :reinsurer_id and pay_year = :pay_year
 16    group by pay_quarter) b
 17  on (a.pay_qtr = b.pay_quarter) ;
select nvl(a.pay_qtr, b.pay_quarter) pay_q,
*
ERROR at line 1:
ORA-00918: column ambiguously defined

I cannot find any columns that are ambiguous. And also when
I use left outer join instead, it give me then correct answers.

SQL> select nvl(a.pay_qtr, b.pay_quarter) pay_q,
  2         nvl(a.clm_l,0) clm_l, nvl(a.clm_a,0) clm_a,
  3         nvl(a.clm_t,0) clm_t, nvl(b.clm_h,0) clm_h
  4  from
  5   (select pay_qtr,
  6      sum(decode(flag_death,'Y', decode(plan_type,'L', re_pay_amt,0),0)) clm_l,
  7      sum(decode(flag_death,'Y', decode(plan_type,'R', re_pay_amt,0),0)) clm_a,
  8      sum(decode(flag_death,'N', decode(plan_type,'R', re_pay_amt,0),0)) clm_t
  9    from claim_paid_by_reinsurer
 10    where reinsurer_id = :reinsurer_id and pay_year = :pay_year
 11    group by pay_qtr) a
 12  left outer join
 13   (select pay_quarter, sum(re_pay_amt) clm_h
 14    from claim_h_paid_by_reinsurer
 15    where reinsurer_id = :reinsurer_id and pay_year = :pay_year
 16    group by pay_quarter) b
 17  on (a.pay_qtr = b.pay_quarter) ;

P      CLM_L      CLM_A      CLM_T      CLM_H
- ---------- ---------- ---------- ----------
1      50000          0      50000          0

Here is version I used :

SQL> select version from v$instance ;

VERSION
-----------------
9.2.0.1.0

In the query above, the first subquery has one row and the second has no row.
 

Tom Kyte
February 28, 2004 - 11:05 am UTC

can you give me a full, simple, small test case to play with? (create tables, et. al. something I too can run)

More details

Nopparat V., February 29, 2004 - 11:40 pm UTC

I'm afraid that I can't give a simple and small test case for you. claim_paid_by_reinsurer and claim_h_paid_by_reinsurer are some complex views. Did the full outer join have any problem when using on these views?

Anyway, the following is the structure of the view

SQL> desc claim_paid_by_reinsurer
 Name                           Null?    Type
 ---------------------------------------------------
 CLM_DOC_NO                     NOT NULL VARCHAR2(10)
 PLAN_TYPE                      NOT NULL VARCHAR2(1)
 RIDER_CODE                     NOT NULL VARCHAR2(2)
 OCCUR_DATE                              DATE
 CLM_CAUSE                               VARCHAR2(7)
 FLAG_DEATH                              VARCHAR2(1)
 POLICY_ID                               VARCHAR2(7)
 RE_YEAR                                 VARCHAR2(4)
 RE_QUARTER                              VARCHAR2(1)
 RE_COVERAGE_TYPE                        VARCHAR2(2)
 CERT_ID                                 VARCHAR2(6)
 REINSURER_ID                   NOT NULL VARCHAR2(2)
 TOTAL_SUM                               NUMBER(8)
 ER_FLAG                                 VARCHAR2(1)
 PAY_DATE                                DATE
 PAY_AMT                                 NUMBER(10,2)
 PAY_YEAR                                VARCHAR2(4)
 PAY_QTR                                 VARCHAR2(1)
 RE_PAY_AMT                              NUMBER
 PAY_RATE                                NUMBER(5,2)

SQL> desc claim_h_paid_by_reinsurer
 Name                           Null?    Type
 ---------------------------------------------------
 POLICY_ID                      NOT NULL VARCHAR2(7)
 REINSURER_ID                   NOT NULL VARCHAR2(2)
 PAY_DATE                                DATE
 PAY_YEAR                                VARCHAR2(4)
 PAY_QUARTER                             VARCHAR2(1)
 RE_PAY_AMT                              NUMBER

and here are their data :

SQL> select reinsurer_id, pay_year, pay_qtr,
  2    flag_death, plan_type, re_pay_amt
  3  from claim_paid_by_reinsurer ;

RE PAY_ P F P RE_PAY_AMT
-- ---- - - - ----------
02 2547 1 Y L      50000
02 2549 1 N R      50000
02 2549 1 N R      12500
05        Y R      22500
04        Y R      52500
02 2547 1 N R      50000
05 2547 4 Y L      22500
04 2547 4 Y L      52500
05 2547 2 Y L      15000
04 2547 2 Y L      35000
05 2547 2 Y R      15000

RE PAY_ P F P RE_PAY_AMT
-- ---- - - - ----------
04 2547 2 Y R      35000
02 2549 1 Y R      50000
02        Y R      50000

14 rows selected.

SQL> select reinsurer_id, pay_year, pay_quarter,
  2    re_pay_amt
  3  from claim_h_paid_by_reinsurer ;

RE PAY_ P RE_PAY_AMT
-- ---- - ----------
06                 0
02              3250
06             76500

Thank you very much for your helps.
 

Tom Kyte
March 01, 2004 - 7:32 am UTC

I actually get a "nasty error" on this one, not as nice as "ambigous column".

but only in 9201, not in 9204. In 9204 -- I'm not having any issues.

I'll have to refer you to support for this one, I cannot reproduce your issue example (would need the views and base tables I suppose)

Filtering rows after outer join

Jackson Murphy, March 02, 2004 - 1:02 am UTC

Dear Tom,
After we do a outer join,Is it possible to discard some rows
from the result set?
like emp.deptno = dept.deptno(+)
and dept.deptno not in (10,20)
Is it possible to do like this?
Please do reply.


Tom Kyte
March 02, 2004 - 7:38 am UTC

why outer join at all then?



if you say "and dept.deptno not in (10,20)" that is like saying

"and dept.deptno not in (10,20) AND dept.deptno is not null"


since NULL is not "not in(10,20)" (actually null is neither NOT IN nor IN 10,20).


what might make sense would be:

from emp, (select * from dept where deptno not in (10,20) ) dept
where emp.deptno = dept.deptno(+)


or

from emp, dept
where emp.deptno = dept.deptno(+)
and (dept.deptno is null OR dept.deptno not in (10,20) )


or

from emp, dept
where emp.deptno = dept.deptno(+)
and EMP.deptno not in (10,20)


not sure what you want really -- you'd have to phrase in english the result set you desired. something like:


I want a report for EVERY employee. If that emp is in a deptno other
than 10,20 -- I would like the dept information as well

that would be:

from emp, (select * from dept where deptno not in (10,20) ) dept
where emp.deptno = dept.deptno(+)
or
from emp, dept
where emp.deptno = dept.deptno(+)
and (dept.deptno is null OR dept.deptno not in (10,20) )



I want a report for every employee except for deptnos 10,20. I would like
dept information for that employee when available.

that would be:

from emp, dept
where emp.deptno = dept.deptno(+)
and EMP.deptno not in (10,20)



OK

Ben, April 10, 2004 - 5:36 am UTC

Dear Sir,
Do you have a simple example for a Three way join like
" If I have three tables A,B,C and I would Like to do a join as (A left join B right join C)".Is there any precedence in such a join?
Please do reply.
Bye!


Tom Kyte
April 10, 2004 - 11:54 am UTC

well, you haven't said how to join to c -- I can see a joins b, then the result of that would be joined with c but how?)


a left joined to b (all rows in A preserved) giving AB
then AB right joined to C (all rows in C preserved) 


you can see that with:

ops$tkyte@ORA9IR2> create table a ( a int );
 
Table created.
 
ops$tkyte@ORA9IR2> create table b ( b int );
 
Table created.
 
ops$tkyte@ORA9IR2> create table c ( c int, d int );
 
Table created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into a values ( 3 );
 
1 row created.
 
ops$tkyte@ORA9IR2> insert into b values ( 2 );
 
1 row created.
 
ops$tkyte@ORA9IR2> insert into c values ( 3, 4 );
 
1 row created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select *
  2    from a left join b on (a=b) right join c on (a=c and b=d)
  3  /
 
         A          B          C          D
---------- ---------- ---------- ----------
                               3          4

<b>A left join B was the set (3,null) and that right joined to C on columns from both A and B returns just the row from C</b>

 
ops$tkyte@ORA9IR2> select *
  2    from a left join b on (a=b) right join c on (a=c)
  3  /
 
         A          B          C          D
---------- ---------- ---------- ----------
         3                     3          4

<b>Now, A left join B returns the set (3,null) and that right joined to C based on the column in A returns the row from A and  C</b>

ops$tkyte@ORA9IR2> select *
  2    from a left join b on (a=b) right join c on (b=d)
  3  /
 
         A          B          C          D
---------- ---------- ---------- ----------
                               3          4
 
<b>and so on.....</b>

but I would prefer ALWAYS to be explicit:

 

outer join on view causing full table scan?

Josh, June 22, 2004 - 4:33 pm UTC

This site rocks!  Your efforts are much appreciated.

Due to an encryption project, we have replaced an existing table with a view of that same table only now two of the columns have user defined functions that decrypt as necessary.  Although we are not joining on either of these function based columns but rather a different indexed column, if we include either of the function based 
columns in the select and have an outer join then we see a full table scan. Why? Can this be avoided without removing the join or the function based columns?

Here's a simple reproduction of the issue...

Connected to:
Oracle8i Enterprise Edition Release 8.1.7.4.1 - Production
With the Partitioning option
JServer Release 8.1.7.4.1 - Production

SQL> CREATE TABLE my_table AS SELECT object_id,object_name FROM all_objects
  2  /

Table created.

SQL> CREATE INDEX my_table_object_id_idx ON my_table(object_id)
  2  /

Index created.

SQL> CREATE INDEX my_table_object_name_idx ON my_table(object_name)
  2  /

Index created.

SQL> CREATE TABLE my_other_table AS SELECT object_id,created FROM all_objects
  2  /

Table created.

SQL> CREATE INDEX my_other_table_created_idx ON my_other_table(created)
  2  /

Index created.

SQL> CREATE OR REPLACE FUNCTION my_upper (p IN VARCHAR2)
  2  RETURN VARCHAR2
  3  IS
  4  BEGIN
  5      RETURN upper(p);
  6  END;
  7  /

Function created.

SQL> CREATE OR REPLACE VIEW my_view AS
  2  SELECT object_id, my_upper(object_name) object_name 
  3  FROM my_table
  4  /

View created.

-- Now query does FTS on my_table
SQL> SET AUTOTRACE ON EXPLAIN
SQL> SELECT v.object_name,t.created
  2    FROM my_view v,
  3        my_other_table t
  4   WHERE t.created = TO_DATE('11/14/2000 8:44:17 PM','MM/DD/YYYY HH:MI:SS AM')
  5     AND t.object_id = v.object_id (+)
  6  /

9 rows selected.


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   MERGE JOIN (OUTER)
   2    1     SORT (JOIN)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'MY_OTHER_TABLE'
   4    3         INDEX (RANGE SCAN) OF 'MY_OTHER_TABLE_CREATED_IDX' (
          NON-UNIQUE)

   5    1     SORT (JOIN)
   6    5       VIEW OF 'MY_VIEW'
   7    6         TABLE ACCESS (FULL) OF 'MY_TABLE'

-- if we remove outer join the index is used as desired
SQL> SELECT v.object_name,t.created
  2    FROM my_view v,
  3        my_other_table t
  4   WHERE t.created = TO_DATE('11/14/2000 8:44:17 PM','MM/DD/YYYY HH:MI:SS AM')
  5     AND t.object_id = v.object_id
  6  /

9 rows selected.


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'MY_OTHER_TABLE'
   3    2       INDEX (RANGE SCAN) OF 'MY_OTHER_TABLE_CREATED_IDX' (NO
          N-UNIQUE)

   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'MY_TABLE'
   5    4       INDEX (RANGE SCAN) OF 'MY_TABLE_OBJECT_ID_IDX' (NON-UN
          IQUE)
 

Tom Kyte
June 22, 2004 - 10:14 pm UTC

I reproduced, in 8i using either the CBO or RBO, it refused to use the index.

in 9ir2 -- both used the index naturally.

can you use:

SELECT (select v.object_name from my_view v where v.object_id = t.object_id)
object_name,t.created
FROM
my_other_table t
WHERE t.created = TO_DATE('11/14/2000 8:44:17 PM','MM/DD/YYYY HH:MI:SS AM')
/


which does use the indexes? it'll work in the case where the relationship from T to V is 1 to 1 (a row in T joins to at most one row in V)

application generates sql dynamically

Josh, June 23, 2004 - 1:50 pm UTC

Thank you for the quick response.

Your suggested SQL works like a champ from the SQL prompt, unfortunately, the SQL in question is generated dynamically by our application and the application doesn't allow hints(other than CBO/RBO), user defined function calls (that's why we hid ours in the view) or sub-selects.

1. Out of curiosity, what has changed in the optimizer(s) in 9ir2 that allows the index to be used naturally in this situation whereas 8i does not?

I'll have to warn you not to drink anything while reading this next question or you may very well soak your monitor in reaction to it's futility and desperation...
2. Is there a setting that we could manipulate in 8i that would allow the index to be used without changing the dynamically generated SQL? Sorry, I had to ask...

BTW we've since crossed the following off of our list of things to try...
- sprinkling pixie dust
- alter session set always_use_index_on_outer_joined_view=1
- saying 'pretty please' to the db server when no one is listening (it never responds since I started back on my medication...)


Tom Kyte
June 23, 2004 - 2:18 pm UTC

1) incremental improvements to the optimizer.

2) here is magic pixie dust.  but don't forget, pixie dust can do good and bad, please test.  In 9i, this pixie dust is the default behaviour:


ops$tkyte@ORA817DEV> SET AUTOTRACE traceonly EXPLAIN
ops$tkyte@ORA817DEV> SELECT v.object_name,t.created
  2    FROM my_view v,
  3        my_other_table t
  4   WHERE t.created = TO_DATE('11/14/2000 8:44:17 PM','MM/DD/YYYY HH:MI:SS AM')
  5     AND t.object_id = v.object_id (+)
  6  /
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   MERGE JOIN (OUTER)
   2    1     SORT (JOIN)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'MY_OTHER_TABLE'
   4    3         INDEX (RANGE SCAN) OF 'MY_OTHER_TABLE_CREATED_IDX' (NON-UNIQUE)
   5    1     SORT (JOIN)
   6    5       VIEW OF 'MY_VIEW'
   7    6         TABLE ACCESS (FULL) OF 'MY_TABLE'
 
 
 
ops$tkyte@ORA817DEV> alter session set "_COMPLEX_VIEW_MERGING" = TRUE;
 
Session altered.
 
ops$tkyte@ORA817DEV> SELECT v.object_name,t.created
  2    FROM my_view v,
  3        my_other_table t
  4   WHERE t.created = TO_DATE('11/14/2000 8:44:17 PM','MM/DD/YYYY HH:MI:SS AM')
  5     AND t.object_id = v.object_id (+)
  6  /
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   NESTED LOOPS (OUTER)
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'MY_OTHER_TABLE'
   3    2       INDEX (RANGE SCAN) OF 'MY_OTHER_TABLE_CREATED_IDX' (NON-UNIQUE)
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'MY_TABLE'
   5    4       INDEX (RANGE SCAN) OF 'MY_TABLE_OBJECT_ID_IDX' (NON-UNIQUE)
 
 
 
ops$tkyte@ORA817DEV> set autotrace off
 

so far, so good...

Josh, June 23, 2004 - 3:14 pm UTC

This is exactly what we were looking for. Per your reccommendation we will thoroughly test.

You're pretty good with this Oracle stuff - you might want to see if they're hiring... :-)

My sincerest thanks.


outer join question

A reader, June 29, 2004 - 11:20 am UTC

"If you see a query like this:

from t1, t2
where t1.x = t2.x(+)
and t2.any_other_column <some condition>

you know for a fact that outer join is a waste, a performance drain, something
to be removed from the query.
"

Are there any exceptions to this rule?
e.g. can't you have the following?

where t1.x = t2.x(+)
and t2.any_other_column(+) = <some other column>



Tom Kyte
June 29, 2004 - 4:12 pm UTC

that is differnt, there is a (+) after it.

the situation is:

where t1.x = t2.x(+)
and t2.anyother_column_without_outer_joins = <something>

I think I need full outer join, but am using Oracle 8i.

Reena, July 01, 2004 - 1:53 pm UTC

Tom, Thank you for EVERYTHING! Finally, I understand things that I've been using for years! :)

My problem, I am joining three tables. Major, degree and department. I need to write a query to display

1. the count(*) of number of bachelor's, masters, doctoral and vet med degrees in each department. I used your pivot table trick. Thank you for that trick explained on a different thread.

2. I have to do this for 5 fiscal years.

The key tables are dept and degree. The major table is used only to get the name of the department that the degree is associated to.

(
SELECT t5.coll5, t5.deptname5, t5.unique_id5, t5.maj_term, t5.deg_degree, decode(mod(r5.row5,4), 1, t5.bachelors5, 2, t5.masters5, 3, t5.doctoral5, 0, t5.vet5) deg5,
decode(r5.row5, 1, t5.bachsum5, 2, t5.mastsum5, 3, t5.docsum5, 4, t5.vetsum5) year5, r5.row5
FROM
( SELECT
degr5.MAJOR_COLLEGE COLL5,
dept5.deptname deptname5,
dept5.unique_id unique_id5,
max (decode(substr(degr5.degree,1,1),'B','Bachelor''s Degrees','Bachelor''s Degrees')) Bachelors5,
sum(decode(substr(degr5.degree,1,1),'B',1,0)) bachsum5,
max (decode(substr(degr5.degree,1,1),'M','Master''s Degrees','Master''s Degrees')) Masters5,
sum(decode(substr(degr5.degree,1,1),'M',1,0)) mastsum5,
max (decode(substr(degr5.degree,1,1),'P','Doctoral Degrees','Doctoral Degrees')) Doctoral5,
sum(decode(substr(degr5.degree,1,1),'P',1,0)) docsum5,
max(decode(substr(degr5.degree,1,1),'D','Doctor of Veterinary Medicine', 'Doctor of Veterinary Medicine')) Vet5,
sum(decode(substr(degr5.degree,1,1),'D',1,0)) vetsum5
FROM
DEGREE degr5,
DEPTXREF dept5
,MAJOR maj5
WHERE
degr5.MAJOR_COLLEGE = SUBSTR(dept5.ISIS_DEPT,3,2)
and degr5.major_college = maj5.major_college
and maj5.major = degr5.major
and maj5.major_submajor = degr5.major_submajor
and maj5.major_dept = dept5.isis_dept
and maj5.term = degr5.term
and degr5.term = dept5.term
and dept5.fiscal_year = '2003'
and (degr5.term = 'SM02' or degr5.term = 'FA02' or degr5.term = 'SP03')
GROUP BY
degr5.MAJOR_COLLEGE,
dept5.deptname,
dept5.unique_id
) t5, (select rownum row5 from all_objects where rownum <= 4) r5
order by t5.coll5, t5.deptname5, r5.row5
) to5

I repeat this query 4 more times, naming them to4, to3, to2, to1, the only different between the queries being the fiscal year and terms. So for fiscal year 2002, 2001, 2000, 1999.

Then I put a join condition below,

where to5.coll5 = to4. coll4(+)
and to5.unique_id5 = to4.unique_id4(+)
and to5.deg5 = to4.deg4(+)
and to4.coll4 = to3.coll3(+)
and to4.unique_id4 = to3.unique_id3(+)
and to4.deg4 = to3.deg3(+)
and to3.coll3 = to2.coll2(+)
and to3.unique_id3 = to2.unique_id2(+)
and to3.deg3 = to2.deg2(+)
and to2.coll2 = to1.coll1(+)
and to2.unique_id2 = to1.unique_id1(+)
and to2.deg2 = to1.deg1(+)

I get most of what I want. The only problem is that by doing this I am making to5 the master. So if there are zero degrees offered in the fiscal year 2003, for any given department, it does not display that department for any of the other fiscal years. I don't want this to happen. I want the data for the departments listed if any of the years have degrees offered.

Since I use Oracle 8i, I cannot use full outer join. Is there any solution, other than doing a union 5 times for this already humongous query!

Or is there any other much better way of doing this, that I am not aware of?

Please help!

Tom Kyte
July 01, 2004 - 7:50 pm UTC

but you can pivot right in there on the dates as well.

that have one query get all 5 years.

decode on the YEAR and the other field.

make sense? you can get all of the data in one pass.

Outer Join Bug in 10g

A reader, July 20, 2004 - 8:57 pm UTC

Hi Tom,

I ran the following in 10g. Please remind the t3.t3.username in both statements.

select 1 from all_users t1
inner join all_users t2 on (t1.username = t2.username)
inner join all_users t3 on (t2.username = t3.username)
where t3.t3.username = user;
ORA-00904: "T3"."T3"."USERNAME": invalid identifier

select 1 from all_users t1
inner join all_users t2 on (t1.username = t2.username)
left join all_users t3 on (t2.username = t3.username)
where t3.t3.username = user;
1
----------
1
1 row selected


Is this a known bug? Will there be any much more serious side-effects?



Tom Kyte
July 20, 2004 - 9:17 pm UTC

I see there is t3.t3 in both -- my comment was "i think t3.t3 is wrong, so I think the second query if any is an issue".

you seemed to think the first is?

re: Outer Join bug

A reader, July 20, 2004 - 10:15 pm UTC

Hi Tom,

The difference is that the second query uses a "LEFT OUTER JOIN", where as the first using "INNER JOIN".

select 1 from all_users t1
inner join all_users t2 on (t1.username = t2.username)
inner join all_users t3 on (t2.username = t3.username)
where t3.t3.username = user;
ORA-00904: "T3"."T3"."USERNAME": invalid identifier

select 1 from all_users t1
inner join all_users t2 on (t1.username = t2.username)
left join all_users t3 on (t2.username = t3.username)
where t3.t3.username = user;
1
----------
1
1 row selected


I believe it's a bug.


Tom Kyte
July 21, 2004 - 7:25 am UTC

I understand that -- but again, you seem to think "t3.t3.username" is "correct".

I believe "t3.t3.username" to be incorrect.

is that a correct observation on my part, and if so -- why do you think t3.t3 is correct.

Selective rows with joins

A reader, September 10, 2004 - 9:31 pm UTC

Hi Tom,

Consider the following:

create table x (col1 number, col2 number);
insert into x values (1, 1);
insert into x values (2, 3);
insert into x values (4, 5);
commit;

create table y (col1 number, col3 number, col4 char(1));
insert into y values (1, 9, 'Y');
insert into y values (1, 7, 'N');
insert into y values (1, 5, 'Y');
insert into y values (1, 6, 'Y');
insert into y values (2, 8, 'Y');
commit;

I have a query like

select x.col1, x.col2, y.col3
from x, y
where x.col1 = y.col1
and y.col4 = 'Y';

The result is:
col1 col2 col3
---- ----- -----
1 1 9
1 1 7
1 1 5
1 1 6
2 3 8

But I want to see the result as

col1 col2 col3
---- ----- -----
1 1 9
2 3 8

That is for every match I want to see only one instance.

Pls help.



Tom Kyte
September 11, 2004 - 8:05 am UTC

ops$tkyte@ORA9IR2> select * from (
  2  select x.col1, x.col2, y.col3 ,
  3         row_number() over (partition by x.col1 order by 'foo') rn
  4  from x, y
  5  where x.col1 = y.col1
  6  and y.col4 = 'Y' )
  7  where rn = 1;
 
      COL1       COL2       COL3         RN
---------- ---------- ---------- ----------
         1          1          5          1
         2          3          8          1


order by col3 desc would just happen to get your answer, but you just said "i want to see one instance", if you were more specific, we could do "better".  otherwise order by anything and just take the first one.

another technique:

ops$tkyte@ORA9IR2> select col1, col2, (select col3 from y where col1 = x.col1 and rownum=1) col3
  2    from x
  3   where (select col3 from y where col1 = x.col1 and rownum=1) is not null
  4  /
 
      COL1       COL2       COL3
---------- ---------- ----------
         1          1          7
         2          3          8
 

A reader, March 08, 2005 - 11:47 am UTC

create table functions
(object_id number(10) not null,
name varchar2(20) );
create table client_pricing
(object_id number(10),
client varchar2(20),
function_id number(10),
fee number(6.2));
insert into functions
(1,'printing');
insert into functions
(2,'scanning');
insert into functions
(3,'letter');
insert into functions
(4,'email');
insert into client_pricing
(1,'A',1,23.12);
insert into client_pricing
(2,'A',2,0.12);
insert into client_pricing
(3,'B',1,20.12);
insert into client_pricing
(4,'B',4,07);
And I need:
client function price
A printing 23.12
A scanning 0.12
A letter -
A email -
B printing 20.12
B scanning -
B letter -
B email 0.7

Please help. thnaks.













Tom Kyte
March 08, 2005 - 12:32 pm UTC

you need a set of unique client ids, you need to cartesian product that with functions -- then outer join to client_pricing (in 10g, you could use a partitioned outer join to avoid the cartesian product)

ops$tkyte@ORA10GR1> with
  2  clients
  3  as
  4  (select distinct client from client_pricing),
  5  clients_functions
  6  as
  7  (select * from clients, functions)
  8  select cf.client, cf.name, cp.fee
  9    from client_pricing cp, clients_functions cf
 10   where cp.client(+) = cf.client
 11     and cp.function_id(+) = cf.object_id;
 
CLIENT               NAME                                  FEE
-------------------- ------------------------------ ----------
A                    printing                            23.12
A                    scanning                              .12
B                    printing                            20.12
B                    email                                   7
B                    letter
B                    scanning
A                    letter
A                    email
 
8 rows selected.
 



in 10g --- you can do this:



ops$tkyte@ORA10GR1> select client, name, fee
  2    from client_pricing partition by (client)
  3           right outer join functions on (client_pricing.function_id = functions.object_id)
  4  /
 
CLIENT               NAME                                  FEE
-------------------- ------------------------------ ----------
A                    printing                            23.12
A                    scanning                              .12
A                    letter
A                    email
B                    printing                            20.12
B                    scanning
B                    letter
B                    email                                   7
 
8 rows selected.
 

A reader, March 08, 2005 - 2:03 pm UTC


question about (+) on columns not part of the join

Laly, June 23, 2005 - 6:51 am UTC

Dear Tom,


I have on question regarding the use of (+) on a column that is not part of the join; I cannot understand the results of this : 

SQL> create table A(x number);

Table created.

SQL> 
SQL> create table B(x number, y number);

Table created.

SQL> 
SQL> insert into A values(1);

1 row created.

SQL> 
SQL> insert into A values(2);

1 row created.

SQL> 
SQL> 
SQL> insert into B values(1, 1);

1 row created.

SQL> 
SQL> insert into B values(2, 2);

1 row created.

SQL> 
SQL> 
SQL> select * from A, B where A.x = b.x (+);

         X          X          Y
---------- ---------- ----------
         1          1          1
         2          2          2

SQL> 
SQL> select * from A, B where A.x = b.x (+) and b.x (+) = 1;

         X          X          Y
---------- ---------- ----------
         1          1          1
         2

SQL> 


I formerly thought that the last query would bring only one row, ie the first one.

How does it happen that the columns of the second table are replaced with NULL when the condition b.x (+) = 1 is not verified ? Is this normal ?

I tought that : b.x (+) = 1 and (b.x = 1 or b.x is null) where equivalent (of course on a not null column).


Best regards, 

Laly. 

Tom Kyte
June 23, 2005 - 6:42 pm UTC

consider:

select *
from ( select a.*, 1 new_col
from a ) A,
b
where a.x = b.x(+)
and b.x(+) = a.new_col;

it is "as if" 1 were a join column from A - no different.

A reader, June 24, 2005 - 8:47 am UTC

Thanks a lot Tom,


I now understand the difference and moreover I have a simple way to remember it.


Best regards.

Laly.

Another Question on Outer Join

A reader, June 28, 2005 - 10:06 am UTC

Hi tom, I am trying to work on an outerjoin between two tables. Department table has all deptno's and emp_dept table has list of all employees and departments that employee is incharge off

create table dept (deptno integer);

insert into dept values (10);
insert into dept values (20);
insert into dept values (30);
insert into dept values (40);
insert into dept values (50);
insert into dept values (60);

Create table emp_dept (eno integer,dno integer);

insert into emp_dept values (1234,20);
insert into emp_dept values (1234,10);
insert into emp_dept values (2345,20);
insert into emp_dept values (2345,30);
insert into emp_dept values (2345,50);
insert into emp_dept values (3456,40);
insert into emp_dept values (3456,20);

I want all dept no's that are not managed by employee 1234

Select d.deptno from emp_dept e,dept d
where e.deptno(+)=d.deptno
and e.dno not in (select e1.dno from emp_dept e1
where e1.eno=1234)

Is there any other way of doing this instead of using a sub query?

Thanks,


Tom Kyte
June 28, 2005 - 10:22 am UTC

why not just

select deptno
from dept
where dno not in ( select dno from emp_date where eno = 1234 )

why is there an outer join (that is defeated by the predicate on e.dno by the way) here at all?

outer join on self join

sat, August 11, 2005 - 9:58 am UTC

Hello Tom,

I have question on outerjoin on a self join.

Let say the table T I have three rows

PROUCT NO PAuxno Product name product price
P100 ABC1 sugar packs 1000
P100 PAU101 S Tax 50
p100 PAU102 S TAX1 10




My day should be like this

PROUCT NO PAuxno_a ,pauxno_b, Product_name_a,product_name_b,product_price_a,product_price_b
p100 ABC1 paux101 sugar packs S tax 1000 50
p100 ABC1 paux102 sugar packs S tax1 1000 10

I know this is possible using self join only (Assume I am right?)

Select A.*,B.*
from product_tab a ,
product_tab b
where a.product_no= b.product_no
and a.PAuxno not in ('PAU101','PAU102')
and b.PAuxno in ('PAU101','PAU102')


suppose if the customer is tax exemptable then I dont see the tax rows then My data should be like this

PROUCT NO PAuxno_a ,pauxno_b, Product_name_a,product_name_b,product_price_a,product_price_b
p100 paux100 sugar packs S tax 1000 0



Since I dont have any control on PAUXNO other tax codes (ie ABC1, some times it may be XYZ1)

How do we create the above query.

Thanks for the help




Tom Kyte
August 11, 2005 - 6:08 pm UTC

can the data have more than one "ABC" row per product? or just one?

outerjoin on a self join

A reader, August 11, 2005 - 4:00 pm UTC

Hello Tom,

I have question on outerjoin on a self join.

Let say the table T I have three rows

PROUCT NO PAuxno Product name product price
P100 ABC1 sugar packs 1000
P100 PAU101 S Tax 50
p100 PAU102 S TAX1 10




My day should be like this

PROUCT NO PAuxno_a ,pauxno_b,
Product_name_a,product_name_b,product_price_a,product_price_b
p100 ABC1 paux101 sugar packs S tax 1000
50
p100 ABC1 paux102 sugar packs S tax1 1000
10

I know this is possible using self join only (Assume I am right?)

Select A.*,B.*
from product_tab a ,
product_tab b
where a.product_no= b.product_no
and a.PAuxno not in ('PAU101','PAU102')
and b.PAuxno in ('PAU101','PAU102')


suppose if the customer is tax exemptable then I dont see the tax rows then My
data should be like this

PROUCT NO PAuxno_a ,pauxno_b,
Product_name_a,product_name_b,product_price_a,product_price_b
p100 paux100 sugar packs S tax 1000
0



Since I dont have any control on PAUXNO/other tax codes (ie ABC1, some times it
may be XYZ1)

How do we create the above query.

Thanks for the help


Tom Kyte
August 12, 2005 - 8:17 am UTC

Hello Reader,

please see question above before reposting.

outer join on self join

sat, August 12, 2005 - 12:11 pm UTC

Hello Tom,

I have question on outerjoin on a self join.

Let say the table T I have three rows

PROUCT NO PAuxno Product name product price
P100 ABC1 sugar packs 1000
P100 PAU101 S Tax 50
p100 PAU102 S TAX1 10




My day should be like this

PROUCT NO PAuxno_a ,pauxno_b,
Product_name_a,product_name_b,product_price_a,product_price_b
p100 ABC1 paux101 sugar packs S tax 1000
50
p100 ABC1 paux102 sugar packs S tax1 1000
10

I know this is possible using self join only (Assume I am right?)

Select A.*,B.*
from product_tab a ,
product_tab b
where a.product_no= b.product_no
and a.PAuxno not in ('PAU101','PAU102')
and b.PAuxno in ('PAU101','PAU102')


suppose if the customer is tax exemptable then I dont see the tax rows then My
data should be like this

PROUCT NO PAuxno_a ,pauxno_b,
Product_name_a,product_name_b,product_price_a,product_price_b
p100 paux100 sugar packs S tax 1000
0



Since I dont have any control on PAUXNO other tax codes (ie ABC1, some times it
may be XYZ1)

How do we create the above query.

Thanks for the help





Followup:
can the data have more than one "ABC" row per product? or just one?

---

in some cases I will receive more than one ABC rows.
Also There is one more column called Sales_tax_applicable -> which 'Y or 'N'
Those I have to sum all 'ABC' , if the sales_tax_applicable 'Y . hope this info is sufficient to get my answer

Thanks for the help



Tom Kyte
August 13, 2005 - 9:39 am UTC

stop reproducing the entire thing -- I can page up.


Do you see how this tiny bit of information:

Also There is one more column called Sales_tax_applicable -> which 'Y or 'N'
Those I have to sum all 'ABC' , if the sales_tax_applicable 'Y .

might have been really relevant to getting any answer that would actually work???

Now, you need to be much more detailed.

('PAU101','PAU102')

are they "special" and everything else is tax? How do you id the TAX records?

And supply the create table, insert into statements to test with.

And be explicit. Like you were assigning this to a programmer -- give the detailed specifications. Pretend the guy looking at writing this query knowns little to nothing about the data (hey -- that is reality here, I don't). Cover all of the bases.




outer join on self join

satya, August 15, 2005 - 7:55 am UTC

These codes ('PAU101','PAU102') are tax codes and they are fixed . But The data codes ie ABC1, ABC2,XYZ1 These codes had no control

table name is ptab

PROUCT NO PAuxno Product name product price Sales_tax
P100 ABC1 sugar packs 1000 Y
P100 ABC1 sugar packs 2000 Y
P100 ABC1 sugar packs 1000 N
P100 PAU101 S Tax 50
p100 PAU102 S TAX1 10



I need the data row this way ( a,b are self join columns)

PROUCT NO PAuxno_a ,pauxno_b, Product_name_a,product_name_b,product_price_a,product_price_b
p100 ABC1 paux101 sugar packs S tax 3000 50
p100 ABC1 paux102 sugar packs S tax1 3000 10


the above is the actual requirement.


If possible please give me the solution using analtical as well as no analtical. Since we are using stil 8.1.7 is Development and

the lower version in production .Planning to move to oracle 9 or 10g veryshortly. No body here is knowledgable on Analatical functions.


Thanks for the help


Tom Kyte
August 15, 2005 - 8:14 am UTC

It takes all of 30 minutes to become knowlegable on Analytics. Don't let lack of knowledge prevent you from using something.

You still have never given me the create or inserts, so this'll be the last entry I have on this. You don't give specifications one can actually code from. Product Name -- just one? how does it relate? fit in?

try this

select product_no, product_name, sum(price) price
from t
where pauxno not in ( 'PAU101', 'PAU102' )
group by product_no, product_name


Call that Q1

select product_no, product_name, price
from t
where pauxno in ( 'PAU101', 'PAU102' )

Call that Q2


select ...
from (Q1) a,
(Q2) b
where a.product_no = b.product_no;




A reader, August 25, 2005 - 4:04 pm UTC


SUM( DECODE( tr.id, 10, TO_CHAR(TRUNC((tr.total1 + tr.total2)/60, 2),'000.99'), '000.00' ) ) "One",
SUM( DECODE( tr.id, 20, TO_CHAR(TRUNC((tr.total1 + tr.total2)/60, 2),'000.99'), '000.00' ) ) "Two",
SUM( DECODE( tr.id, 30, TO_CHAR(TRUNC((tr.total1 + tr.total2)/60, 2),'000.99'), '000.00' ) ) "Three"

Tom,

Here I am using a sum of decode function on a table to collect total1 and total2 values of respective ids (10, 20, 30) and transform rows into columns.

Th sum of total1 and total2 is divided by 60 to get minutes from hours. Total1 and Total2
denote hours. All the minutes should be in the 999.99 form. i.e if it is 30.20, it should become 030.20. If it is zero, it should be 000.00. I am struggling with the right function. I tried using another decode statement inside, but since this being a SUM(DECODE)) it won't work. I then tried using to_char as shown above and it won't work either. Any help?






Tom Kyte
August 25, 2005 - 6:43 pm UTC

example data?

A reader, August 25, 2005 - 5:11 pm UTC

Tom,

Create statement and inserts for the above question:

CREATE TABLE t1(id VARCHAR2(2), total1 NUMBER(10), total2 NUMBER(10) );

INSERT INTO t1 VALUES(10,100,101);
INSERT INTO t1 VALUES(10,102,103);
INSERT INTO t1 VALUES(10,106,105);
INSERT INTO t1 VALUES(20,200,201);
INSERT INTO t1 VALUES(20,202,203);
INSERT INTO t1 VALUES(20,204,205);
INSERT INTO t1 VALUES(30,300,301);
INSERT INTO t1 VALUES(30,302,303);
INSERT INTO t1 VALUES(30,304,305);
INSERT INTO t1 VALUES(30,306,307);

Tom Kyte
August 25, 2005 - 6:51 pm UTC

ops$tkyte@ORA9IR2> select
  2  to_char( SUM( DECODE( tr.id, 10, TRUNC((tr.total1 + tr.total2)/60, 2))),'000.00') "one",
  3  to_char( SUM( DECODE( tr.id, 20, TRUNC((tr.total1 + tr.total2)/60, 2))),'000.00')"two",
  4  to_char( SUM( DECODE( tr.id, 30, TRUNC((tr.total1 + tr.total2)/60, 2))),'000.00') "three"
  5  from t1 tr;

one     two     three
------- ------- -------
 010.27  020.24  040.45


use an nvl on the sum if you need to to make it zero, if that is what you want. 

A reader, August 28, 2005 - 2:40 pm UTC

Tom,

Continuuing the above topic,

Assuming that table t1 has some more columns, col1 and col2

select tr.col1, tr.col2
to_char( SUM( DECODE( tr.id, 10, TRUNC((tr.total1 + tr.total2)/60, 2))),'000.00') "one",
to_char( SUM( DECODE( tr.id, 20, TRUNC((tr.total1 + tr.total2)/60, 2))),'000.00')"two",
to_char( SUM( DECODE( tr.id, 30, TRUNC((tr.total1 + tr.total2)/60, 2))),'000.00') "three"

from t1 tr, t2 ty

where ..... joins etc ...
;


The problem here is, the to_char(SUM(DECODE)) is requiring a group by ... i.e whatever I select from two or three tables joined here should be a part of group by ..
If I require a total of 5 fields, two from t1, two from another table and one from some other table, how can I write this sql without putting them in group by? Bottomline is that I have to transfer the rows into cols of one table and also join that with other tables and I don't want them to involve in group by ....




Tom Kyte
August 28, 2005 - 4:08 pm UTC

example please. tables and creates to work with that demonstrate the issue.

Making up rows...

Karmit, October 05, 2005 - 6:12 am UTC

Hi,

I have a table defined as follow...

CREATE TABLE TEST_TABLE
(
FLD1 NUMBER NOT NULL,
FLD2 VARCHAR2(10)
);


and some records in it like...

INSERT INTO TEST_TABLE (FLD1, FLD2) VALUES (1, 'CODEA');
INSERT INTO TEST_TABLE (FLD1, FLD2) VALUES (2, 'CODEB');
INSERT INTO TEST_TABLE (FLD1, FLD2) VALUES (3, 'CODEC');
INSERT INTO TEST_TABLE (FLD1, FLD2) VALUES (5, 'CODEE');
INSERT INTO TEST_TABLE (FLD1, FLD2) VALUES (8, 'CODEH');
INSERT INTO TEST_TABLE (FLD1, FLD2) VALUES (15,'CODEK');
INSERT INTO TEST_TABLE (FLD1, FLD2) VALUES (17,'CODEL');
INSERT INTO TEST_TABLE (FLD1, FLD2) VALUES (30,'CODEY');

commit;



I give a SELECT...

SELECT DECODE(NVL(fld1,0),0,0,1) AS FOUND, fld1, fld2
FROM TEST_TABLE
WHERE FLD2 = 'CODEA'
OR FLD2 = 'CODEB'
OR FLD2 = 'CODEC'
OR FLD2 = 'CODED'
OR FLD2 = 'CODEE'
OR FLD2 = 'CODEF'
OR FLD2 = 'CODEG'
OR FLD2 = 'CODEH'
;



to get an output like this -

FOUND FLD1 FLD2
---------- ---------- ----------
1 1 CODEA
1 2 CODEB
1 3 CODEC
1 5 CODEE
1 8 CODEH



but I need the output to be something like...

FOUND FLD1 FLD2
---------- ---------- ----------
1 1 CODEA
1 2 CODEB
1 3 CODEC
0 CODED
1 5 CODEE
0 CODEF
0 CODEG
1 8 CODEH

i.e with rows "made up".. How can this be done?


Thanks,
Karmit


Tom Kyte
October 05, 2005 - 7:44 am UTC

You need a table (or a set somewhere) with CODEA .. CODEH in it

then you can outer join that table to this table.

Making up records..

Karmit, October 05, 2005 - 8:18 am UTC

Ok! I tried your suggestion and came up with this...

SELECT DECODE(NVL(TT.fld1,0),0,0,1) AS FOUND,
TT.fld1,
CODESET.CODE
FROM TEST_TABLE TT, ( select 'CODEA' AS CODE FROM DUAL
union
select 'CODEB' AS CODE FROM DUAL
union
select 'CODEC' AS CODE FROM DUAL
union
select 'CODED' AS CODE FROM DUAL
union
select 'CODEE' AS CODE FROM DUAL
union
select 'CODEF' AS CODE FROM DUAL
union
select 'CODEG' AS CODE FROM DUAL
union
select 'CODEH' AS CODE FROM DUAL
) CODESET
WHERE CODESET.CODE = TT.FLD2(+)
ORDER BY 3
;

which now gives the desired result...

FOUND FLD1 CODE
---------- ---------- -----
1 1 CODEA
1 2 CODEB
1 3 CODEC
0 CODED
1 5 CODEE
0 CODEF
0 CODEG
1 8 CODEH

8 rows selected.

However I don't like the multiple select's from DUAL to "generate" the set... Is there any other simpler/sophisticated method to generate this.. I feel there is something I'm missing here?!

Thanks,
karmit


Tom Kyte
October 05, 2005 - 11:06 am UTC

put the data into a table?  

You can use str2tbl (search site for this)



ops$tkyte@ORA9IR2> create or replace type str2tblType as table of varchar2(30)
  2  /

Type created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace function str2tbl( p_str in varchar2, p_delim in varchar2 default ',' ) return str2tblType
  2  PIPELINED
  3  as
  4      l_str      long default p_str || p_delim;
  5      l_n        number;
  6  begin
  7      loop
  8          l_n := instr( l_str, p_delim );
  9          exit when (nvl(l_n,0) = 0);
 10          pipe row( ltrim(rtrim(substr(l_str,1,l_n-1))) );
 11          l_str := substr( l_str, l_n+1 );
 12      end loop;
 13      return;
 14  end;
 15  /

Function created.

ops$tkyte@ORA9IR2> @test
ops$tkyte@ORA9IR2> with data as
  2  (select column_value, rownum r from table(str2tbl( 'A,B,C,D' )) )
  3  select * from data
  4  /

COLUMN_VALUE                            R
------------------------------ ----------
A                                       1
B                                       2
C                                       3
D                                       4 

select from dual

Jagjeet Singh, October 05, 2005 - 9:08 am UTC

view can be used using some logic if possible. 

  1  select translate(code,0123456789,'ABCDEFGHIJKLM')  from
  2* ( select 'CODE'||rownum code  from dict where rownum < 10 )
SQL> /

TRANSLATE(CODE,0123456789,'ABCDEFGHIJKLM')
--------------------------------------------
CODEA
CODEB
CODEC
CODED
CODEE
CODEF
CODEG
CODEH
CODEI

 

Madeup rows...

Karmit, October 05, 2005 - 9:52 am UTC

Hi Jagjeet,
Yes.. I see your point... but unfortunately the CODEx lookups I gave was only an example... in reality the
values are diverse like "ACCOUNT NAME"/ "DATE OF BIRTH"
etc and there is no logic to generate them :-(
Cheers,
Karmit


how to make outer join disappear

A reader, October 26, 2005 - 3:26 pm UTC

Hi

We have a query using outer join and the performance is horrible (Oracle takes ages to parse the query, join of 14 tables).

Someone rewrote the query and it runs fast but when I see the execution plan I see the nested loop outer disappeared from the execution plan.

Is that possible? We still have the (+) in the query!

Tom Kyte
October 27, 2005 - 4:45 am UTC

what is ages (and it should only take long once per instance - as the rest would be soft parses??)


I'd need to see an example to comment on it.

Is full outer join needed here?

VA, November 04, 2005 - 1:43 pm UTC

create table t1
(
pk1 int,pk2 int,
col1 varchar2(10),
col2,varchar2(10),
constraint t1_pk primary key(pk1,pk2)
);

create table t2
(
pk1 int,pk2 int,
col1 varchar2(10),
col2,varchar2(10)
constraint t2_pk primary key(pk1,pk2)
);
insert into t1 values (1,1,'one','one');
insert into t1 values (1,2,'two','two');
insert into t1 values (1,3,'three','three');

insert into t2 values (2,2,'two','two');
insert into t2 values (2,3,'three','three');
insert into t2 values (2,4,'four','four');

I want to match on the pk2 column and show all rows and all columns from both tables such that pk2 is found in t1 and not in t2 or found in t2 and not found in t1.

So in the above example, I would need rows for pk2=1 and pk2=4

This is the classic definition of a full outer join, right? I read this thread that they are horribly expensive and I dont want to go down this path unless absolutely necessary.

Thanks

Tom Kyte
November 04, 2005 - 5:36 pm UTC

... found in t1 and not in t2 or found in t2 and not found
in t1. ....


either full outer join or a union all of two "not ins"

they are expensive and therefore SHOULD ONLY BE USED WHEN NECESSARY
and when necessary, well, they are the PRICE OF ADMINISSION


do not do them

unless you need them, then do them

makes sense doesn't it? don't do unnecessary work, only do what you need to do.


Why are results different?

VA, November 06, 2005 - 10:41 am UTC

In my example above, if I do

select * from
(select * from t1 where pk1=1) lhs
full outer join
(select * from t2 where pk1=2) rhs
on (lhs.pk2=rhs.pk2)

I get a certain set of results which are correct (2 rows)

If I change pk1=2 to pk1=3, I again get correct results. (3 rows)

But if I combine it like

select * from
(select * from t1 where pk1=1) lhs
full outer join
(select * from t2 where pk1 in (2,3)) rhs
on (lhs.pk2=rhs.pk2)

I get incorrect results i.e. the resultset for pk1=2 that I had got earlier is not in this combined resultset.

I expected the IN query to give me 2+3 = 5 rows.

Why is this?

Thanks

Tom Kyte
November 06, 2005 - 11:28 am UTC

why? you were joining by pk2, not pk1 - I would not expect this to be additive.

You took all fo the pk1 = 1 values from t1:

ops$tkyte@ORA10GR2> select * from t1 where pk1=1;

 PK1  PK2 COL1  COL2
---- ---- ----- -----
   1    1 one   one
   1    2 two   two
   1    3 three three

and the pk1's in 2,3 from t2:

ops$tkyte@ORA10GR2> select * from t2 where pk1 in (2,3);

 PK1  PK2 COL1  COL2
---- ---- ----- -----
   2    2 two   two
   2    3 three three
   2    4 four  four


and said to join by pk2.  

pk2 = 2 and 3 belong in both and each have one row (so they join one to one, this is two rows)

t1 has one pk2 = 1 and t2 doesn't (so they do not join, 1 row is made up in t2 and this ONE row is output)

t2 has one pk2 = 4 and t2 doesn't - so we make up ONE row.

4 rows. 

Why are results different?

VA, November 06, 2005 - 1:27 pm UTC

OK let me try to explain this in a different way...

I am interested in t1.pk1=1, always.

For all the t2.pk1's, show me all the "exceptional" t1.pk2's.

So in the above example, I would need rows for pk2=1 and pk2=4

It works fine when I do it for one t2.pk1 at a time, but when I combine them, things dont add up and I want them to add up. I really want to "iterate" over the various t2.pk1's because clubbing them together is affecting the output.

Almost seems like your "think sets" advice is hindering rather than helping here?

Let me put this in the context of a real world example...

pk1 corresponds to a store_id and pk2 corresponds to a item_id.

I have identified pk1=1 as a "model store". I want to compare sales in this model store to all my other stores. So, for all the other stores, show me the "exceptions" i.e. item_ids that were sold in that store but not in the model store or sold in the model store and not in that store.

Thanks

Tom Kyte
November 06, 2005 - 1:52 pm UTC

"think sets" is not hindering here.

maybe your requirements are not what you mean.


t1.pk1 = 1. IT WAS THERE.

as were the others. What row was "missing" in your mind. I see only 4 possible rows out of this.


so, show the answer you got and point out the data you believe has gone "missing"

Why are results different?

A reader, November 06, 2005 - 2:55 pm UTC

OK you have my the create tables above, insert the following data

insert into t1 values (1,1,'one','one');
insert into t1 values (1,2,'two','two');
insert into t1 values (1,3,'three','three');

insert into t2 values (2,2,'two','two');
insert into t2 values (2,3,'three','three');
insert into t2 values (2,4,'four','four');

insert into t2 values (3,2,'two','two');
insert into t2 values (3,3,'three','three');
insert into t2 values (3,4,'four','four');

insert into t2 values (4,3,'three','three');
insert into t2 values (4,4,'four','four');
insert into t2 values (4,5,'five','five');

I want the following output

1. For t2.pk1=2: t1.pk2=1 and t2.pk2=4 (2 rows)
2. For t2.pk1=3: t1.pk2=1 and t2.pk2=4 (2 rows)
3. For t2.pk1=4: t1.pk2=1, t1.pk2=2, t2.pk2=4 and t2.pk2=5 (4 rows)

So, I want 2+2+4=8 rows in my output.

Thanks

Tom Kyte
November 06, 2005 - 3:54 pm UTC

I don't really understand what "for t2.pk1=2: t1.pk2=1 and t2.pk2=4 (2 rows)" means.


I don't see any 4's in T1 at all. confusion here on my part.


Do you understand the semantics of a full outer join? You get the set of

all keys in T1 such that they are not in T2.
all keys in T2 such that they are not in T1.
a natural join of T1 to T2.


You cannot take two different queries and say "I will, when combined, get the card(query1) + card(query2)"

Perhaps you are looking for union all, not sure - but I don't understand what you mean by your "i want the following output"

VA, November 06, 2005 - 3:07 pm UTC

SQL> l
  1  select * from  (
  2  select lhs.pk1 lhs_pk1,lhs.pk2 lhs_pk2,rhs.pk1 rhs_pk1,rhs.pk2 rhs_pk2
  3  from
  4  (select * from t1 where pk1=1) lhs
  5  full outer join
  6  (select * from t2 where pk1 in (2)) rhs on (lhs.pk2=rhs.pk2)
  7  )
  8* where lhs_pk2 is null or rhs_pk2 is null
SQL> /

   LHS_PK1    LHS_PK2    RHS_PK1    RHS_PK2
---------- ---------- ---------- ----------
         1          1
                               2          4

SQL> c/(2)/(3)
  6* (select * from t2 where pk1 in (3)) rhs on (lhs.pk2=rhs.pk2)
SQL> /

   LHS_PK1    LHS_PK2    RHS_PK1    RHS_PK2
---------- ---------- ---------- ----------
         1          1
                               3          4

SQL> c/(3)/(4)
  6* (select * from t2 where pk1 in (4)) rhs on (lhs.pk2=rhs.pk2)
SQL> /

   LHS_PK1    LHS_PK2    RHS_PK1    RHS_PK2
---------- ---------- ---------- ----------
         1          1
         1          2
                               4          4
                               4          5

Those are the 8 (2+2+4) rows I want in 3 separate queries i.e. when I iterate over t2.pk1

But when I combine them in one query like

SQL> l
  1  select * from  (
  2  select lhs.pk1 lhs_pk1,lhs.pk2 lhs_pk2,rhs.pk1 rhs_pk1,rhs.pk2 rhs_pk2
  3  from
  4  (select * from t1 where pk1=1) lhs
  5  full outer join
  6  (select * from t2 where pk1 in (2,3,4)) rhs on (lhs.pk2=rhs.pk2)
  7  )
  8* where lhs_pk2 is null or rhs_pk2 is null
SQL> /

   LHS_PK1    LHS_PK2    RHS_PK1    RHS_PK2
---------- ---------- ---------- ----------
         1          1
                               2          4
                               3          4
                               4          4
                               4          5

5 rows selected.

I get only 5 rows, where are the missing 3 rows?

Thanks 

Tom Kyte
November 06, 2005 - 4:03 pm UTC

You are joining - there is *nothing* missing here.


you are joining lhs.pk2 to rhs.pk2.

When you use the query:

ops$tkyte@ORA10GR2> select * from t1 where pk1=1;

 PK1  PK2 COL1  COL2
---- ---- ----- -----
   1    1 one   one
   1    2 two   two
   1    3 three three


and:

ops$tkyte@ORA10GR2> select * from t2 where pk1 in (2,3,4);

 PK1  PK2 COL1  COL2
---- ---- ----- -----
   2    2 two   two
   2    3 three three
   2    4 four  four
   3    2 two   two
   3    3 three three
   3    4 four  four
   4    3 three three
   4    4 four  four
   4    5 five  five

9 rows selected.


Now, you are keeping only the "made up rows" (your predicate "where lhs_pk2 is null or rhs_pk2 is null")....

Now:

ops$tkyte@ORA10GR2> select pk2, count(*) from t1 where pk1 = 1 group by pk2 order by 1;

 PK2   COUNT(*)
---- ----------
   1          1
   2          1
   3          1

ops$tkyte@ORA10GR2> select pk2, count(*) from t2 where pk1 in (2,3,4) group by pk2 order by 1;

 PK2   COUNT(*)
---- ----------
   2          2
   3          3
   4          3
   5          1



1 is in QUERY1 and not in QUERY2 - that one meets your criteria.  There is one of them.

4 and 5 are in QUERY2 and not in QUERY1.  There are 3 and 1 of those repspectively.

Hence, you will get 1+3+1 = 5 records from this full outer join.


YOU CANNOT take three independent DIFFERENT queries, combine their predicates and expect the cardinality to be the same as all three put together one after the other.

perhaps you are looking for union all. 

Why are results different?

VA, November 06, 2005 - 5:10 pm UTC

"perhaps you are looking for union all"

That is what I was trying to say with my comments about "iterate over" and "think sets doesnt work here"!

Thats why my first question was "is a full outer join even the right technique here"? Seems like it is not, right?

So, are you saying that I cannot get from here to there using pure SQL? I would need to procedurally build up my result set? UNION ALL would work if my set of "iterations" is known before-hand, but in my case, it is not, it is stored in the table and I want to, literally, iterate over that i.e. I want as many UNION ALL queries as number of distinct t2.pk1's.

Ideas?

Thanks

Tom Kyte
November 07, 2005 - 8:35 am UTC

Given the level of detail in the original question - full outer join was the answer. You said:

....
I want to match on the pk2 column and show all rows and all columns from both
tables such that pk2 is found in t1 and not in t2 or found in t2 and not found
in t1.
.......

that would be a full outer join.


what is not "pure" about union all??

Union all

VA, November 07, 2005 - 9:15 am UTC

"what is not "pure" about union all?? "

What I meant was that I cannot use UNION ALL in this case because the number of union all "sections" is not pre-determined, it is dependent on the data.

In other words,

select * from (
select lhs.pk1 lhs_pk1,lhs.pk2 lhs_pk2,rhs.pk1 rhs_pk1,rhs.pk2 rhs_pk2
from
(select * from t1 where pk1=1) lhs
full outer join
(select * from t2 where pk1 in (2,3,4)) rhs on (lhs.pk2=rhs.pk2)
)
where lhs_pk2 is null or rhs_pk2 is null

If I knew that 2,3,4 above before-hand, yes, I could write the above FOJ query 3 times, once for each value and UNION ALL.

But I want to do

select * from (
select lhs.pk1 lhs_pk1,lhs.pk2 lhs_pk2,rhs.pk1 rhs_pk1,rhs.pk2 rhs_pk2
from
(select * from t1 where pk1=1) lhs
full outer join
(select * from t2 where pk1 in (select pk1 from <query>)) rhs on (lhs.pk2=rhs.pk2)
)
where lhs_pk2 is null or rhs_pk2 is null

So, how can I do UNION ALL?

I would need to *iterate* over it like
for rec in (select pk1 from <query>) loop
insert into resultset
<the full outer join for rec.pk1>
end loop;

So, can I use UNION ALL here or do I need to go for a procedural solution to build up my resultset?

Thanks

Tom Kyte
November 07, 2005 - 11:50 am UTC

got 10g?  a union all with partitioned outer joins might do the trick.

ops$tkyte@ORA10GR2> select *
  2    from  ( select lhs.pk1 lhs_pk1,lhs.pk2 lhs_pk2,rhs.pk1 rhs_pk1,rhs.pk2 rhs_pk2
  3              from (select *
  4                      from t1 where pk1=1) lhs
  5              right outer join
  6                   (select *
  7                      from t2
  8                     where pk1 in (2,3,4)) rhs partition by (rhs.pk1) on (lhs.pk2=rhs.pk2)
  9          )
 10  where lhs_pk2 is null
 11  union all
 12  select *
 13    from  ( select lhs.pk1 lhs_pk1,lhs.pk2 lhs_pk2,rhs.pk1 rhs_pk1,rhs.pk2 rhs_pk2
 14              from (select *
 15                      from t1 where pk1=1) lhs
 16              left outer join
 17                   (select *
 18                      from t2
 19                     where pk1 in (2,3,4)) rhs partition by (rhs.pk1) on (lhs.pk2=rhs.pk2)
 20          )
 21  where rhs_pk2 is null
 22  /

   LHS_PK1    LHS_PK2    RHS_PK1    RHS_PK2
---------- ---------- ---------- ----------
                               2          4
                               3          4
                               4          4
                               4          5
         1          1          2
         1          1          3
         1          1          4
         1          2          4

8 rows selected.
 

No 10g

VA, November 07, 2005 - 1:30 pm UTC

Unfortunately, we are still on 9iR2, so I guess pure SQL is out, I would need to loop over my IN clause and build up my resultset in a table.

Thanks for your help.

full outer join

Peter, November 28, 2005 - 12:03 pm UTC

Hi Tom,

I do have large customer table (2.5 mil rows every month)
with relation employee->customer i want to see for every given month how customer migrated between employees (new customers, left customer, current customers) here is example

CREATE TABLE cus_tmp (
as_of_date DATE,
emp_id INT,
cus_no INT)

INSERT INTO cus_tmp VALUES('31. 1. 2005', 1, 1);
INSERT INTO cus_tmp VALUES('31. 1. 2005', 2, 2);

-- cus_no 2 left, cus_no 3 came to emp_id 2
INSERT INTO cus_tmp VALUES('28. 2. 2005', 1, 1);
INSERT INTO cus_tmp VALUES('28. 2. 2005', 2, 3);

-- cus_no 1 moved to emp_id 2
INSERT INTO cus_tmp VALUES('31. 3. 2005', 2, 1);
INSERT INTO cus_tmp VALUES('31. 3. 2005', 2, 3);

I want to get:
as_of_date emp_id cus_no TYPE
28.2.2005 1 1 'current'
28.2.2005 2 2 'left_out'
28.2.2005 2 3 'new_out'
31.3.2005 1 1 'left_shift'
31.3.2005 2 1 'new_shift'
31.3.2005 2 3 'current'

where
'current' - cus_no has not change emp_id from last month

'new_out' - cus_no came from outside, no record in previous month for this cus_no

'left_out' - cus_no left to outside, no more record for cus_no in this month, but there was record in previous month with given emp_id

'new_shift' - cus_no moved from other emp_id in previous month to given emp_id in this month

'left_shift' - customer moved from given emp_id in previous month to other emp_id in this month

uh, it's tricky to write down,hope i made it clear:-)

Is this possible with use of analytics or do i have
to use full outer joins for each consecutive month?

thanks a lot

Tom Kyte
November 28, 2005 - 2:11 pm UTC

I didn't get the customer 2 report for feb.

cus_no = 2, emp_id = 2 in jan
cus_no = 2, emp_id = 3 in feb

doen't seem that cus_no = 2 "left" or "new_out"

seems cus_no = 2 is a new shift

Peter, November 29, 2005 - 5:00 am UTC

There are 4 situation with customers and company
1) new customers are coming to company (new_out)
2) customers are leaving company (left_out)
3) customers do not change emp_id (current)
4) customers are shifting between emp_id's from one month to another, for old emp_id it's left_shift and for new emp_id it's new_shift

for february we have (as_of_date, emp_id,cus_no)
INSERT INTO cus_tmp VALUES('28. 2. 2005', 1, 1);
INSERT INTO cus_tmp VALUES('28. 2. 2005', 2, 3);
emp_id:1 cus_no: 1
emp_id:2 cus_no: 3

so cus_no 2 is left_out for february and emp_id 2 (january emp_id)

wasted outer join **OR** misplaced predicate ?

Duke Ganote, November 29, 2005 - 4:04 pm UTC

Tom--
You wrote:

>If you see a query like this:
>
> from t1, t2
> where t1.x = t2.x(+)
> and t2.any_other_column <some condition>
>
>you know for a fact that outer join is a waste, a
>performance drain, something
>to be removed from the query.

"Something to be removed"? Actually, the outer join could be vital -- but the query author was too familiar with inner joins and did not test the results sufficiently. "A friend of mine" wrote something like:

select t1.*, t2.any_other_column
from t1, t2
where t1.x = t2.x(+)
and t2.any_other_column <some condition>

when in fact I (oops, he :) needed this:

select t1.*, t2.any_other_column
from t1,
( select *
from t2
where t2.any_other_column <some condition> ) t2
where t1.x = t2.x(+)

The outer join was necessary, but the predicate was misplaced!

Tom Kyte
November 30, 2005 - 11:04 am UTC

I disagree, I have to assume the query is "correct", returning the answer the person wanted.

In either case though, it shows a "misunderstanding" of how things work.

I have to assume the query was "correctly returning the right data", else what we have is a bug in the developed code.

Ambiguous column follow-up

Jon Waterhouse, November 30, 2005 - 8:48 am UTC

Hi Tom,

This is a follow-up on the ambiguous column names around the middle of this thread.

I was wondering why the query below does not throw an ambiguous column error. All three tables
have a "date_created" column. The one actually "used" in evaluating the WHERE condition and in
the data returned from the query, is the date_created in the CASE table.

Using 9.2 Same on .0.1 and .0.6.


select r.client_role_id,file_no,
decode(short_desc,'Adult 1','A1','Adult 2','A2','Child','C','Dependent Adult','DA','New') role,
r.start_date,r.end_date,
r.date_created dc1,m.date_created dc2,s.date_created dc3,mod_last_dt
FROM client_role r INNER JOIN table_maintain m ON (r.client_role_typ_id=m.table_maintain_id)
INNER JOIN case s ON (s.case_id=r.case_id)
WHERE date_created IS NULL ;

It seems to be related to the INNER JOIN syntax. The "traditional" form of the query

select r.client_role_id,file_no,
decode(short_desc,'Adult 1','A1','Adult 2','A2','Child','C','Dependent Adult','DA','New') role,
r.start_date,r.end_date,
r.date_created dc1,m.date_created dc2,s.date_created dc3,mod_last_dt
FROM client_role r,table_maintain m,case s
WHERE r.client_role_typ_id=m.table_maintain_id
AND s.case_id=r.case_id
AND date_created IS NULL ;

does throw the error. It looks like a bug to me, but maybe there is something I don't
understand about the JOIN operation?

Just to say... I have learnt so much from this site over the past couple of years; I might well have given up on Oracle without it.

Thanks,

Jon

CREATE TABLE CLIENT_ROLE
(
CLIENT_ID NUMBER(10) NOT NULL,
CASE_ID NUMBER(10) NOT NULL,
CLIENT_ROLE_TYP_ID NUMBER(10) NOT NULL,
START_DATE DATE NOT NULL,
END_DATE DATE,
MOD_LAST_DT DATE,
DATE_CREATED DATE NOT NULL,
CLIENT_ROLE_ID NUMBER(10) NOT NULL
);

CREATE TABLE CASE
(
CASE_ID NUMBER(10) NOT NULL,
FILE_NO VARCHAR2(7 BYTE) NOT NULL,
ADULTS NUMBER(1),
CHILDREN NUMBER(2) NOT NULL,
DATE_CREATED DATE
);

CREATE TABLE TABLE_MAINTAIN
(
TABLE_MAINTAIN_ID NUMBER(10),
CODE NUMBER(10),
SHORT_DESC VARCHAR2(50 BYTE),
DATE_CREATED DATE
);


INSERT INTO CASE ( CASE_ID, FILE_NO, ADULTS, CHILDREN, DATE_CREATED ) VALUES (
58362, '0626382', 2, 0, NULL);
INSERT INTO CASE ( CASE_ID, FILE_NO, ADULTS, CHILDREN, DATE_CREATED ) VALUES (
64285, '0601351', 0, 0, NULL);
COMMIT;
INSERT INTO TABLE_MAINTAIN ( TABLE_MAINTAIN_ID, CODE, SHORT_DESC,
DATE_CREATED ) VALUES (
484, 483, 'Adult 1', TO_Date( '04/04/2002 04:01:12 PM', 'MM/DD/YYYY HH:MI:SS AM'));
INSERT INTO TABLE_MAINTAIN ( TABLE_MAINTAIN_ID, CODE, SHORT_DESC,
DATE_CREATED ) VALUES (
485, 483, 'Adult 2', TO_Date( '04/04/2002 04:01:12 PM', 'MM/DD/YYYY HH:MI:SS AM'));
COMMIT;
INSERT INTO CLIENT_ROLE ( CLIENT_ID, CASE_ID, CLIENT_ROLE_TYP_ID, START_DATE, END_DATE, MOD_LAST_DT,
DATE_CREATED, CLIENT_ROLE_ID ) VALUES (
100127, 58362, 484, TO_Date( '09/30/2005 11:16:58 PM', 'MM/DD/YYYY HH:MI:SS AM')
, NULL, NULL, TO_Date( '09/30/2005 11:16:58 PM', 'MM/DD/YYYY HH:MI:SS AM'), 103228);
INSERT INTO CLIENT_ROLE ( CLIENT_ID, CASE_ID, CLIENT_ROLE_TYP_ID, START_DATE, END_DATE, MOD_LAST_DT,
DATE_CREATED, CLIENT_ROLE_ID ) VALUES (
100128, 58362, 485, TO_Date( '09/30/2005 11:16:58 PM', 'MM/DD/YYYY HH:MI:SS AM')
, NULL, NULL, TO_Date( '09/30/2005 11:16:58 PM', 'MM/DD/YYYY HH:MI:SS AM'), 103229);
INSERT INTO CLIENT_ROLE ( CLIENT_ID, CASE_ID, CLIENT_ROLE_TYP_ID, START_DATE, END_DATE, MOD_LAST_DT,
DATE_CREATED, CLIENT_ROLE_ID ) VALUES (
110741, 64285, 484, TO_Date( '09/30/2005 11:16:58 PM', 'MM/DD/YYYY HH:MI:SS AM')
, NULL, NULL, TO_Date( '09/30/2005 11:16:58 PM', 'MM/DD/YYYY HH:MI:SS AM'), 114077);
COMMIT;


Tom Kyte
November 30, 2005 - 2:50 pm UTC

I agree, looks like a problem - both should throw an error.

do you have access to support to file this? this test case is perfect.

if not, let me know and I'll file it.

Misplaced (or nonplussed) predicate is a bug? Of course...

Duke Ganote, November 30, 2005 - 3:38 pm UTC

Tom-- Above you wrote:
> I have to assume the query is "correct",
> returning the answer the person wanted.
> In either case though, it shows a "misunderstanding"
> of how things work.

Given that outer joins are infrequently used (compared to equijoins), I'm merely pointing out that understanding that predicates are applied AFTER the outer join may not be "obvious". For inner joins, the predicates may be applied prior to the join for optimization, right?

It's similar to when you wrote:
> this is a concept I bring up in some of my seminars.
> I show a piece of procedural code
> Then I write out what it does.
>
> Then I explain how the customer I was benchmarking
> [what it did, but they] said "it doesn't do that"
>
> And I have to show them it did.
>
> Then they realized they had a bug.
-- your response at
</code> http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:5500485045890#47352208686203 <code>

When using the Oracle-style outer joins, perhaps a (+) was forgotten after "t2.any_other_column" in the predicate. Perhaps they meant to code:

select t1.*, t2.any_other_column
from t1, t2
where t1.x = t2.x(+)
and t2.any_other_column(+) <some condition>

rather than

select t1.*, t2.any_other_column
from t1, t2
where t1.x = t2.x(+)
and t2.any_other_column <some condition>

It'd be an easy mistake to make!

I know: RTM !

Duke Ganote, November 30, 2005 - 5:39 pm UTC

Those manuals aren't for speed reading, are they?

"If the WHERE clause contains a condition that compares a column from table B with a constant, then the (+) operator must be applied to the column so that Oracle returns the rows from table A for which it has generated nulls for this column. Otherwise Oracle will return only the results of a simple join."
</code> http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96540/queries7.htm#2062277 <code>

ANSI joins and ambiguous columns

Jon Waterhouse, December 01, 2005 - 3:43 pm UTC

I looked on Metalink just after posting (should have before, but I had forgotten my password again).

It is a known bug with ANSI joins. According to the docs it was fixed in 9.2.0.6 and 10.2 (not sure about the minor numbers for 10).

I still see the problem on 9.2.0.6 under Linux, though, so I'll file it.

Thanks,

Jon

Help Reqd | Outer Join

Rahul Dutta, February 13, 2006 - 1:56 pm UTC

Hi Tom,

My question is relating Outer join, though I know that it degrades the performance. Can you please help me in coming out with a best query for the given below problem:

Table A : ID NUMBER(5),
Name VARCHAR2(20),
Status CHAR(1)

Table B : ID NUMBER(5),
Store VARCHAR2(20),
Type CHAR(1)

Table C : ID NUMBER(5),
Dept VARCHAR2(20)
================================================
The query which I've used with Simple Join is:
SELECT a.id,a.name,b.store,c.dept
FROM a,b,c
WHERE a.id = b.id and
a.id = c.id and
b.type = 'ACTIVE' and
a.id = 404;
-------------------------------------------------
The above query is running successfully. But my problem comes here, when my requirement says that incase the required id does not exist in either table b or table c, the query should successfully execute and show the rows from table a.

Please help me in coming out with a good query to suffice my requirement. I hope I have been able to explain my problem clearly.

Thanks a lot..

Rahul

Tom Kyte
February 13, 2006 - 3:10 pm UTC

though it may sometimes preclude plans from consideration - that if we used would result in a different answer.

Meaning, use the outer join when you must, don't when you need not. It is something you use to get the right answer.


use the outer join syntax or outer join to an inline view of B.

select ..
from a, (select * from b where type='ACTIVE')b, c
where a.id = b.id(+)
and a.id = c.id(+)
and a.id = 404;

Help Reqd | Outer Join

Rahul Dutta, February 14, 2006 - 12:06 am UTC

Thanks a lot Tom... It was really helpfull...

FOJ performance

Kevin Kirkpatrick, February 16, 2006 - 10:39 am UTC

Tom,

You make the comment that FOJs are heinously expensive... I'd agree, but I think Oracle (at least, 9i and earlier) makes it much worse than need be.

Consider:

SELECT *
FROM CHEAP_QUERY NATURAL FULL OUTER JOIN EXPENSIVE_QUERY

As far as I can tell, in Oracle 9i, this will always be executed by resolving EXPENSIVE_QUERY twice. This is particularly painful for one process of mine, which joins and summarizes 6 million+ row tables.

1) Is this the case?

2) Is this logically necessary (why can't Oracle just run EXPENSIVE_QUERY once, then make and use two copies of the result set: one for the join, one for the anti-join)?

3) If the answer to #2 is "no", are there plans to fix this perfomance defect in future releases?


Thanks in advance!

Tom Kyte
February 17, 2006 - 8:36 am UTC

you have the with query if you like.

What is more "expensive", running a storing a query and then joining to the already run, stuffed into temp query - OR just doing it.

Think about it.

Joining

Tony, May 09, 2006 - 1:35 pm UTC

om,

I have a table testing with the folloing values.

Now i want to make the tree structure for this.

For example:

A01
|
--|
|
|
---------
| |
A06 A07
| |
| |
-- --
| |
| |
A08 A09
|
|
--
|
|
A10


Can you please provide some guidance.

CREATE TABLE TESTING
(
INDEX_NO VARCHAR2(10 BYTE),
FLD_NAME VARCHAR2(100 BYTE),
BASE_IDX_NAME VARCHAR2(10 BYTE)
);

INSERT INTO TESTING ( INDEX_NO, FLD_NAME, BASE_IDX_NAME ) VALUES (
'A01', 'A', NULL);
INSERT INTO TESTING ( INDEX_NO, FLD_NAME, BASE_IDX_NAME ) VALUES (
'A02', 'B', NULL);
INSERT INTO TESTING ( INDEX_NO, FLD_NAME, BASE_IDX_NAME ) VALUES (
'A03', 'C', NULL);
INSERT INTO TESTING ( INDEX_NO, FLD_NAME, BASE_IDX_NAME ) VALUES (
'A04', 'D', NULL);
INSERT INTO TESTING ( INDEX_NO, FLD_NAME, BASE_IDX_NAME ) VALUES (
'A05', 'E', NULL);
INSERT INTO TESTING ( INDEX_NO, FLD_NAME, BASE_IDX_NAME ) VALUES (
'A06', 'F', 'A01');
INSERT INTO TESTING ( INDEX_NO, FLD_NAME, BASE_IDX_NAME ) VALUES (
'A07', 'G', 'A01');
INSERT INTO TESTING ( INDEX_NO, FLD_NAME, BASE_IDX_NAME ) VALUES (
'A08', 'H', 'A06');
INSERT INTO TESTING ( INDEX_NO, FLD_NAME, BASE_IDX_NAME ) VALUES (
'A09', 'H', 'A07');
INSERT INTO TESTING ( INDEX_NO, FLD_NAME, BASE_IDX_NAME ) VALUES (
'A10', 'I', 'A09');
COMMIT;

Many thanks in advance.

Tony


Tom Kyte
May 09, 2006 - 1:47 pm UTC

ops$tkyte@ORA10GR2> select rpad( '*', 2*level, '*') || index_no
  2    from testing
  3   start with index_no = 'A01'
  4  connect by prior index_no = base_idx_name;
 
RPAD('*',2*LEVEL,'*')||INDEX_NO
-------------------------------------------------------------------------------
**A01
****A06
******A08
****A07
******A09
********A10
 
6 rows selected.
 

Joining

tony, May 10, 2006 - 3:50 am UTC

Tom,

Thanks for the information.

Now in case if i want to print the result in horizontel way .i.e

Level1 | Level2 | Level3 | Level4
-----------------------------
A01 A06 A08
A07 A09 A10

Tom Kyte
May 10, 2006 - 8:05 am UTC

I don't see an "easy way" to do this, likely would be something you would do in the client application (formatting the output).

That said, I am now sure someone will come along with a query for you :)

maybe something like this:

ops$tkyte@ORA10GR2> column l1 format a3
ops$tkyte@ORA10GR2> column l2 format a3
ops$tkyte@ORA10GR2> column l3 format a3
ops$tkyte@ORA10GR2> column l4 format a3
ops$tkyte@ORA10GR2> column l5 format a3
ops$tkyte@ORA10GR2> break on l1
ops$tkyte@ORA10GR2> select substr( scbp, instr(scbp,',',1,1)+1, instr(scbp,',',1,2)-instr(scbp,',',1,1)-1 ) l1,
  2         substr( scbp, instr(scbp,',',1,2)+1, instr(scbp,',',1,3)-instr(scbp,',',1,2)-1 ) l2,
  3         substr( scbp, instr(scbp,',',1,3)+1, instr(scbp,',',1,4)-instr(scbp,',',1,3)-1 ) l3,
  4         substr( scbp, instr(scbp,',',1,4)+1, instr(scbp,',',1,5)-instr(scbp,',',1,4)-1 ) l4,
  5         substr( scbp, instr(scbp,',',1,5)+1, instr(scbp,',',1,6)-instr(scbp,',',1,5)-1 ) l5
  6    from (
  7  select index_no, sys_connect_by_path( index_no, ',' )||',' scbp
  8    from testing
  9   where connect_by_isleaf = 1
 10   start with index_no = 'A01'
 11  connect by prior index_no = base_idx_name
 12         )
 13  /
 
L1  L2  L3  L4  L5
--- --- --- --- ---
A01 A06 A08
    A07 A09 A10
 

Joining

Tony, May 10, 2006 - 11:50 am UTC

Tom,

this was the one of the greates query i ever seen.

As i am using Oracle 9.1.2 the function "connect_by_isleaf" is not usefull for me. I thing this was the new function included in 10g .

Is there any othere way I can impliment this?

Tony

Tom Kyte
May 11, 2006 - 7:48 am UTC

ops$tkyte@ORA9IR2> select substr( scbp, instr(scbp,',',1,1)+1, instr(scbp,',',1,2)-instr(scbp,',',1,1)-1 ) l1,
  2         substr( scbp, instr(scbp,',',1,2)+1, instr(scbp,',',1,3)-instr(scbp,',',1,2)-1 ) l2,
  3         substr( scbp, instr(scbp,',',1,3)+1, instr(scbp,',',1,4)-instr(scbp,',',1,3)-1 ) l3,
  4         substr( scbp, instr(scbp,',',1,4)+1, instr(scbp,',',1,5)-instr(scbp,',',1,4)-1 ) l4,
  5         substr( scbp, instr(scbp,',',1,5)+1, instr(scbp,',',1,6)-instr(scbp,',',1,5)-1 ) l5
  6    from (
  7  select index_no, sys_connect_by_path( index_no, ',' )||',' scbp
  8    from testing
  9   where index_no not in ( select base_idx_name from testing where base_idx_name is not null )
 10   start with index_no = 'A01'
 11  connect by prior index_no = base_idx_name
 12         )
 13  /

L1  L2  L3  L4  L5
--- --- --- --- ---
A01 A06 A08
    A07 A09 A10
 

Joining

A reader, May 12, 2006 - 6:03 am UTC

Tom,

Thanks very much.



discard group of rows

A reader, July 31, 2006 - 5:28 pm UTC

Hi Tom

I have this query which returns this result set (using emp table)

SELECT deptno, comm,
CASE
WHEN comm > 9000
THEN -1
ELSE NVL (comm, 0)
END comm_threshold
FROM emp
ORDER BY deptno


DEPTNO COMM COMM_THRESHOLD
---------- ---------- --------------
10 0
10 0
10 10000 -1
20 0
20 0
20 0
20 0
20 0
30 300 300
30 0
30 1400 1400
30 0
30 0 0
30 500 500


I would like to discard the department that has an employee whose commision is bigger than 9000, in this case DEPTNO 10.

I am stucked here, dont know how to filter the DEPTNO 10 out. May you throw some lights?

Thanks a lot



Tom Kyte
July 31, 2006 - 9:03 pm UTC

ops$tkyte%ORA10GR2> select *
  2    from
  3  (
  4  SELECT   deptno, comm,
  5           CASE
  6              WHEN comm > 9000
  7                 THEN -1
  8              ELSE NVL (comm, 0)
  9           END comm_threshold,
 10           max(comm) over (partition by deptno) max_comm
 11      FROM emp
 12  )
 13  where nvl(max_comm,0) < 9000
 14  ORDER BY deptno
 15  /

    DEPTNO       COMM COMM_THRESHOLD   MAX_COMM
---------- ---------- -------------- ----------
        10                         0
        10                         0
        10                         0
        20                         0
        20                         0
        20                         0
        20                         0
        20                         0
        30        500            500       1400
        30          0              0       1400
        30        300            300       1400
        30                         0       1400
        30                         0       1400
        30       1400           1400       1400

14 rows selected.

ops$tkyte%ORA10GR2> update emp set comm = 10000 where deptno = 10 and rownum = 1;

1 row updated.

ops$tkyte%ORA10GR2> select *
  2    from
  3  (
  4  SELECT   deptno, comm,
  5           CASE
  6              WHEN comm > 9000
  7                 THEN -1
  8              ELSE NVL (comm, 0)
  9           END comm_threshold,
 10           max(comm) over (partition by deptno) max_comm
 11      FROM emp
 12  )
 13  where nvl(max_comm,0) < 9000
 14  ORDER BY deptno
 15  /

    DEPTNO       COMM COMM_THRESHOLD   MAX_COMM
---------- ---------- -------------- ----------
        20                         0
        20                         0
        20                         0
        20                         0
        20                         0
        30        500            500       1400
        30          0              0       1400
        30        300            300       1400
        30                         0       1400
        30                         0       1400
        30       1400           1400       1400

11 rows selected.

 

discarding group of rows

A reader, August 01, 2006 - 2:36 am UTC

In this case since number are interested MAX can be used. What if varchar2 is involved instead?

For example:

SELECT deptno, comm,job,
CASE
WHEN JOB = 'PRESIDENT'
THEN 'NOT WANTED'
ELSE 'OTHERS'
END job_interested
FROM emp
ORDER BY deptno;


DEPTNO COMM JOB JOB_INTERE
---------- ---------- --------- ----------
10 MANAGER OTHERS
10 PRESIDENT NOT WANTED
10 10000 CLERK OTHERS
20 CLERK OTHERS
20 CLERK OTHERS
20 ANALYST OTHERS
20 ANALYST OTHERS
20 MANAGER OTHERS
30 300 SALESMAN OTHERS
30 MANAGER OTHERS
30 1400 SALESMAN OTHERS
30 CLERK OTHERS
30 0 SALESMAN OTHERS
30 500 SALESMAN OTHERS

The departement which contains PRESIDENT should be discarded.

TIA

Tom Kyte
August 01, 2006 - 7:19 am UTC

max( case when job = 'PRESIDENT' then 1 end ) over (partition by deptno) is_pres


and then add

nvl(is_pres,0) <> 1


or using your example, return 1 and 0 instead of president and not wanted (you didn't need to use strings, you chose to use strings)




Full outer join

Kumar, August 14, 2006 - 5:01 pm UTC

Tom,

I am trying to get the following query work using FULL OUTER JOIN but is giving End of Communication Channel error.

Can you please look into it? Also, can you suggest a better alternative to acheive the desired result?

drop table t;
create table t (id number,qty number,side char(1));

Insert into t values(1,50,'S');
insert into t values(1,50,'S');
Insert into t values(1,50,'B');
insert into t values(2,100,'B');
insert into t values(3,200,'S');
insert into t values(3,100,'B');
insert into t values(3,200,'S');
insert into t values(4,200,'S');
COMMIT;

I need the final output that would look like:
ID FINAL_QTY
----------------------
1 50
2 100
3 300
4 200
The FINAL_QTY in the result is derived by the difference of the SUM(QTY)for side 'B' and 'S' grouped by ID.

My attempt for this was:

SELECT NVL(S.ID,B.ID), ABS(NVL(S.QTY,0)- NVL(B.QTY,0)) FROM
(SELECT ID,SIDE,SUM(QTY) QTY FROM T WHERE SIDE='S' GROUP BY ID,SIDE) S
FULL OUTER JOIN
(SELECT ID,SIDE,SUM(QTY) QTY FROM T WHERE SIDE='B' GROUP BY ID,SIDE) B
ON( S.ID =B.ID )

the above query gives ORA-03113

If I use either RIGHT OUTER JOIN or LEFT OUTER JOIN, there is no error, but does not produce the desired result.

Thanks for the time.

Tom Kyte
August 14, 2006 - 5:33 pm UTC

I cannot reproduce  but there is zero chance that I would use a join of any sort for this!

ops$tkyte%ORA10GR2> select id,
  2         sum(case when side='S' then qty end) s,
  3         sum(case when side='B' then qty end) b,
  4         abs( nvl(sum(case when side='S' then qty end),0)
  5              -nvl(sum(case when side='B' then qty end),0)) s_minus_b
  6    from t
  7   group by id
  8   order by id;

        ID          S          B  S_MINUS_B
---------- ---------- ---------- ----------
         1        100         50         50
         2                   100        100
         3        400        100        300
         4        200                   200


but please do contact support with the specifics of your 3113. 

Version 9i

Kumar, August 15, 2006 - 8:47 am UTC

Tom,

Thanks for the reply. That helps a lot. Which version did you try my query? I had this problem both with 9i and 10.2 on a windows 2003 platform.



Tom Kyte
August 15, 2006 - 12:15 pm UTC

10gr2

10.2.0.2

Need Help

akhilesh, August 24, 2006 - 2:01 am UTC

how to used ANSI joins like, a single query need to have RIGHT, LEFT and INNER join. Please let me know the syntax with example

thanks in advance

Tom Kyte
August 27, 2006 - 7:40 pm UTC

have you checked out, well, the sql reference manual for syntax?

</code> http://docs.oracle.com/docs/cd/B19306_01/server.102/b14200/statements_10002.htm#i2107296 <code>


Outer join to 2 tables

Rish G., August 29, 2006 - 6:59 pm UTC

Tom,
 How is it possible to achieve the equivalent of an outer join to 2 different tables?

Consider the scenario I have 

create table cm (
icn varchar2(10),
r_id varchar2(5),
firstdos date) -- the first date of service on the claim;

insert into cm
values('1098060011', '10010', to_date('07/10/2006', 'mm/dd/yyyy'));
insert into cm
values('1098060012', '10010', to_date('07/10/2006', 'mm/dd/yyyy'));
insert into cm
values('1098060013', '10020', to_date('07/10/2006', 'mm/dd/yyyy'));

create table cd(
icn varchar2(10),
dos date, - dates of service for detail
line varchar2(2));

insert into cd
values('1098060011', to_date('07/01/2006', 'mm/dd/yyyy'), '01');

insert into cd
values('1098060011', to_date('07/02/2006', 'mm/dd/yyyy'), '02');

insert into cd
values('1098060012', to_date('07/10/2006', 'mm/dd/yyyy'), '01');

insert into cd
values('1098060012', to_date('07/15/2006', 'mm/dd/yyyy'), '02');

insert into cd
values('1098060013', to_date('07/10/2006', 'mm/dd/yyyy'), '01');

insert into cd
values('1098060013', to_date('07/02/2006', 'mm/dd/yyyy'), '02');

create table rm(
r_id varchar2(5),
medstat varchar2(2),
beg_date date,
end_date date)

insert into rm
values('10010', 'SE', to_date('06/01/2006', 'mm/dd/yyyy'), to_date('07/01/2006', 'mm/dd/yyyy'));

insert into rm
values('10010', 'SC', to_date('07/02/2006', 'mm/dd/yyyy'), to_date('07/10/2006', 'mm/dd/yyyy'));

insert into rm
values('10020', 'SE', to_date('07/12/2006', 'mm/dd/yyyy'), to_date('07/20/2006', 'mm/dd/yyyy'));


CM and CD are linked by ICN. They have a one to many relationship
CM and RM are linked by r_id. Again a one to many relationship.

What I'm trying here is to retrieve ALL the records from CM and CD and those corresponding records from RM that where the 'DOS' from CD falls between beg_date and end_date of RM. 

I tried this and I dont get the required answer for the obvious reasons
SQL> select cm.icn, cm.r_id, cd.line, cm.firstdos, cd.dos, rm.medstat, rm.beg_date, rm.end_date
  2  from cm, cd, rm
  3  where cm.icn = cd.icn
  4  and cm.r_id = rm.r_id(+)
  5  and cd.dos between nvl(beg_date, to_date('01/01/1900', 'mm/dd/yyyy')) and nvl(end_date, to_date('01/01/1900', 'mm/dd/yyyy'))
  6  /

ICN        R_ID  LI FIRSTDOS  DOS       ME BEG_DATE  END_DATE
---------- ----- -- --------- --------- -- --------- ---------
1098060011 10010 01 01-JUL-06 01-JUL-06 SE 01-JUN-06 01-JUL-06
1098060011 10010 02 01-JUL-06 02-JUL-06 SC 02-JUL-06 10-JUL-06
1098060012 10010 01 10-JUL-06 10-JUL-06 SC 02-JUL-06 10-JUL-06
1098060013 10020 02 10-JUL-06 12-JUL-06 SE 12-JUL-06 20-JUL-06

I came up with the answer, but am not sure if its the correct answer or the 'right' approach and was wondering if you have a different approach.
SQL>  select cm.icn, cm.r_id, cd.line, cm.firstdos, cd.dos, rm.medstat, rm.beg_date, rm.end_date
  2   from cm, cd, rm
  3   where cm.icn = cd.icn
  4   and cm.r_id = rm.r_id(+)
  5   and cd.dos between nvl(beg_date, to_date('01/01/1900', 'mm/dd/yyyy')) and nvl(end_date, to_date('01/01/2099', 'mm/dd/yyyy'))
  6    union all
  7   select cm.icn, cm.r_id, cd.line, cm.firstdos, cd.dos, null medstat, null beg_date, null end_date
  8   from cm, cd
  9   where cm.icn = cd.icn
 10   and not exists (select 'x' from rm
 11                    where rm.r_id = cm.r_id
 12        and cd.dos between nvl(beg_date, to_date('01/01/1900', 'mm/dd/yyyy')) and nvl(end_date, to_date('01/01/2099', 'mm/dd/yyyy')))
 13  order by icn, line;

ICN        R_ID  LI FIRSTDOS  DOS       ME BEG_DATE  END_DATE
---------- ----- -- --------- --------- -- --------- ---------
1098060011 10010 01 01-JUL-06 01-JUL-06 SE 01-JUN-06 01-JUL-06
1098060011 10010 02 01-JUL-06 02-JUL-06 SC 02-JUL-06 10-JUL-06
1098060012 10010 01 10-JUL-06 10-JUL-06 SC 02-JUL-06 10-JUL-06
1098060012 10010 02 10-JUL-06 15-JUL-06
1098060013 10020 01 10-JUL-06 10-JUL-06
1098060013 10020 02 10-JUL-06 12-JUL-06 SE 12-JUL-06 20-JUL-06

6 rows selected.

Any comments on a different approach?

Thanks a lot,




 

Tom Kyte
August 30, 2006 - 7:59 am UTC

I'd be thinking "inline views"

write your query to join CM and CD together. Call it "Q", then join "Q" to rm

select *
from (Q) q, rm
where.....


putting in place of (Q) the actual text of the query to join CM and CD



Outer join into Inner join by CBO

Vladimir Sadilovskiy, September 01, 2006 - 8:29 pm UTC

Mr. Kyte,

Let's assume there is a an outer join with an inner predicate on one side:

explain plan for
select superclass.*, superclass_attr.attribute_value
from superclass, superclass_attr
where superclass.superclass_id = superclass_attr.superclass_ref(+)
and superclass_attr.attribute_value = 'A';

------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 26 | 3 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 1 | 26 | 3 (0)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID| SUPERCLASS_ATTR | 1 | 23 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | SUPERCLASS_ATTR_VALUE | 1 | | 1 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | SUPERCLASS_ID | 1 | 3 | 1 (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------

3 - access("SUPERCLASS_ATTR"."ATTRIBUTE_VALUE"='A')
4 - access("SUPERCLASS"."SUPERCLASS_ID"="SUPERCLASS_ATTR"."SUPERCLASS_REF")

So CBO converts outer join to inner join and that is right as you wrote in the original answer:
<quote>
Ask yourself this -- how can a row in T2 (EMP in your example) be both MISSING
(made up due to the outer join) AND have a column that is equal to something???
It cannot be.
</quote>

My question is can that be done if there are more than one outer joined tables? I'm interested in any possible short term solution beside code change.

explain plan for
select subclass.*, superclass_attr.attribute_value
from subclass, superclass, superclass_attr
where subclass.superclass_ref = superclass.superclass_id(+)
and superclass.superclass_id = superclass_attr.superclass_ref(+)
and superclass_attr.attribute_value = 'A';

--------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8126 | 269K| 20 (5)| 00:00:01 |
|* 1 | FILTER | | | | | |
|* 2 | HASH JOIN RIGHT OUTER | | 8126 | 269K| 20 (5)| 00:00:01 |
| 3 | INDEX FAST FULL SCAN | SUPERCLASS_ID | 1000 | 3000 | 2 (0)| 00:00:01 |
| 4 | MERGE JOIN CARTESIAN | | 8126 | 246K| 17 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| SUPERCLASS_ATTR | 1 | 23 | 2 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | SUPERCLASS_ATTR_VALUE | 1 | | 1 (0)| 00:00:01 |
| 7 | BUFFER SORT | | 6437 | 51496 | 15 (0)| 00:00:01 |
| 8 | TABLE ACCESS FULL | SUBCLASS | 6437 | 51496 | 15 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter("SUPERCLASS"."SUPERCLASS_ID"="SUPERCLASS_ATTR"."SUPERCLASS_REF")
2 - access("SUBCLASS"."SUPERCLASS_REF"="SUPERCLASS"."SUPERCLASS_ID"(+))
6 - access("SUPERCLASS_ATTR"."ATTRIBUTE_VALUE"='A')


Ideally, I'm looking for the following plan:


explain plan for
select subclass.*, superclass_attr.attribute_value
from subclass, superclass, superclass_attr
where subclass.superclass_ref = superclass.superclass_id--(+)
and superclass.superclass_id = superclass_attr.superclass_ref(+)
and superclass_attr.attribute_value = 'A';

----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8 | 272 | 9 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID | SUBCLASS | 6 | 48 | 6 (0)| 00:00:01 |
| 2 | NESTED LOOPS | | 8 | 272 | 9 (0)| 00:00:01 |
| 3 | NESTED LOOPS | | 1 | 26 | 3 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID| SUPERCLASS_ATTR | 1 | 23 | 2 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | SUPERCLASS_ATTR_VALUE | 1 | | 1 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | SUPERCLASS_ID | 1 | 3 | 1 (0)| 00:00:01 |
|* 7 | INDEX RANGE SCAN | SUBCLASS_SUPERCLASS_REF | 6 | | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

5 - access("SUPERCLASS_ATTR"."ATTRIBUTE_VALUE"='A')
6 - access("SUPERCLASS"."SUPERCLASS_ID"="SUPERCLASS_ATTR"."SUPERCLASS_REF")
7 - access("SUBCLASS"."SUPERCLASS_REF"="SUPERCLASS"."SUPERCLASS_ID")


Data for testing:

drop table subclass;
drop table superclass;
drop table superclass_attr;

create table subclass (child_id number, superclass_ref number);
insert into subclass select object_id, mod(object_id,1000) from all_objects;
create table superclass (superclass_id number);
insert into superclass select distinct id from (select mod(object_id,1000) id from all_objects);
create table superclass_attr (superclass_ref number, attribute_value varchar2(4000));
insert into superclass_attr select mod(object_id,1000), object_name from all_objects;

create index superclass_attr_value on superclass_attr (attribute_value);
create index superclass_id on superclass (superclass_id);
create index subclass_superclass_ref on subclass (superclass_ref);

begin
dbms_stats.gather_table_stats(user, 'subclass',
method_opt=>'for all columns size 254',cascade=>true);
dbms_stats.gather_table_stats(user, 'superclass',
method_opt=>'for all columns size 254',cascade=>true);
dbms_stats.gather_table_stats(user, 'superclass_attr',
method_opt=>'for all columns size 254',cascade=>true);
end;
/

Thanks a lot.

- Vlad

Tom Kyte
September 02, 2006 - 11:50 am UTC

you've got a nasty bug in that query? what is the issue with correcting it - none of the outer joins semantically belong there, they only show the developer of the query did not understand the characters they were typing on the keyboard???




Multiple outer joins - eliminated

Jonathan Lewis, September 02, 2006 - 6:51 pm UTC

10gR2 can recognise "outer join elimination" already.
For example:

select
/*+ ordered*/
t1.small_vc,
t2.small_vc,
t3.small_vc
from
t1, t2, t3
where
t1.padding = 'xxxx'
and t2.id(+) = t1.id
and t3.small_vc(+) = t2.small_vc
and t3.padding = 'xxxxx'
;

9i would notice that the fourth predicate makes the join from t2 to t3 an inner join rather than an outer join, but fail to carry this back to the join from t1 to t2. But the plan in 10gR2 is:

| Id | Operation | Name | Rows | Bytes | Cost |
------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 242 | 14 |
|* 1 | HASH JOIN | | 1 | 242 | 14 |
|* 2 | HASH JOIN | | 1 | 130 | 9 |
|* 3 | TABLE ACCESS FULL| T1 | 1 | 115 | 4 |
| 4 | TABLE ACCESS FULL| T2 | 1000 | 15000 | 4 |
|* 5 | TABLE ACCESS FULL | T3 | 1 | 112 | 4 |
------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("T3"."SMALL_VC"="T2"."SMALL_VC")
2 - access("T2"."ID"="T1"."ID")
3 - filter("T1"."PADDING"='xxxx')
5 - filter("T3"."PADDING"='xxxxx')


There are a number of features in the 10g optimizer which operate to "correct" errors in SQL - largely, I think, because lots of systems use generic views which front-end tools then add predicates to.

I've seen cases quite often where the "template" has an outer join, but the effect of adding the end-user's predicates (with literal text inviting SQL injection, of course) has the effect of converting the outer join to an inner join.


Kudos to Oracle

Vladimir Sadilovskiy, September 06, 2006 - 8:18 pm UTC

Tom, Jonathan. Thanks for your help. Developer is finally convinced that the query should be fixed.

The main problem was that the query for which I made up a test case was generated by an application server code.

I actually tested both queries on 10gR1 and 9i and I'm glad Oracle 10gR2 is catching up with such "small" but very useful enhancements.

- Vlad

Help required

Anil, September 18, 2006 - 9:01 am UTC

Hello Tom,
Here is the sample query written in 10g.

SELECT T1.COL1, T2.COL2, T3.COL3
FROM T1 LEFT OUTER JOIN ON T1.COL1 = T2.COL1
AND T1.COL2 = T2.COL2
LEFT OUTER JOIN T3 ON T1.COL3 = T3.COL3
WHERE T1.COL5 <> 'X'

(a) How can I rewrite this?
(b) Instead of WHERE T1.COL5 <> 'X' can I write something like this:

SELECT T1.COL1, T2.COL2, T3.COL3
FROM (SELECT * FROM COL5 <> 'X') T1 LEFT OUTER JOIN ON T1.COL1 = T2.COL1
AND T1.COL2 = T2.COL2
LEFT OUTER JOIN T3 ON T1.COL3 = T3.COL3

(c) Can you give an example for queries with OUTER JOINs on 3 or more tables?


Thanks
Anil

Tom Kyte
September 18, 2006 - 1:37 pm UTC

a) to what exactly????? rewrite "as what", to do "what".

b) yes, you could

c) just keep going - oh wait, you did. You are joining 3 (or more) tables using an outer join.

Help required

Anil, September 19, 2006 - 1:08 am UTC

Hello Tom,
Thanks for replying.

May be I didn't put the question in a proper way.

SELECT T1.COL1, T2.COL2, T3.COL3
FROM T1 LEFT OUTER JOIN ON T1.COL1 = T2.COL1
AND T1.COL2 = T2.COL2
LEFT OUTER JOIN T3 ON T1.COL3 = T3.COL3
WHERE T1.COL5 <> 'X'

(a) Is there a better way of writing the query?

(b) Instead of WHERE T1.COL5 <> 'X' if I write something like this will it be efficent

SELECT T1.COL1, T2.COL2, T3.COL3
FROM (SELECT * FROM COL5 <> 'X') T1 LEFT OUTER JOIN ON T1.COL1 = T2.COL1
AND T1.COL2 = T2.COL2
LEFT OUTER JOIN T3 ON T1.COL3 = T3.COL3


Thanks
Anil



Tom Kyte
September 19, 2006 - 2:26 pm UTC

a) looks okey dokey to me - if you want to get every row from T1, such that t1.col5 <> 'x', and if it has mates in T2 - you would like to see them as well.

b) we call that

"six one way"
"half dozen the other"

pretty much "the same"

One more question

Anil, September 19, 2006 - 8:11 am UTC

Hello Tom,
Continuation of the previous question.

SELECT T1.COL1, T2.COL2, T3.COL3
FROM T1 LEFT OUTER JOIN ON T1.COL1 = T2.COL1
AND T1.COL2 = T2.COL2
LEFT OUTER JOIN T3 ON T1.COL3 = T3.COL3
WHERE T1.COL5 <> 'X'

To my question "Can you give an example for queries with OUTER JOINs on 3 or more tables?" you replied "just keep going - oh wait, you did. You are joining 3 (or more) tables using an outer join."

What does this mean? Is it improper to write such a query because we have many queries which have been written in this way where we are joining 3 or more tables using outer joins.

Thanks





Tom Kyte
September 19, 2006 - 2:35 pm UTC

you are already outer joining with three tables - t1, t2, t3. You answered your own question was my point.

Is it correct?

Rambabu, September 27, 2006 - 6:09 am UTC

Hello Tom,

We have a query which has 3 tables which a left outer join. I'm seeing the cost is too high. I generated the statistics but still it too high. I was thinking of rewriting the query with UNION ALL.

Here is an example of what I was thiking of doing.

Can I write like this?



CREATE TABLE T1 AS SELECT ROWNUM RN, OBJECT_NAME, OBJECT_TYPE , CREATED
FROM ALL_OBJECTS
WHERE OBJECT_TYPE IN ('LOB', 'JOB', 'DIRECTORY')

CREATE TABLE TLOB AS SELECT ROWNUM RN, OBJECT_NAME, OBJECT_TYPE , CREATED
FROM ALL_OBJECTS
WHERE OBJECT_TYPE = 'LOB'


CREATE TABLE TJOB AS SELECT ROWNUM RN, OBJECT_NAME, OBJECT_TYPE , CREATED
FROM ALL_OBJECTS
WHERE OBJECT_TYPE = 'JOB'



SELECT T1.OBJECT_NAME, T1.RN, T1.OBJECT_TYPE, T1.CREATED
FROM T1 LEFT OUTER JOIN TLOB ON T1.OBJECT_NAME = TLOB.OBJECT_NAME
AND T1.OBJECT_TYPE <> 'DIRECTORY'
UNION
SELECT T1.OBJECT_NAME, T1.RN, T1.OBJECT_TYPE, T1.CREATED
FROM T1 LEFT OUTER JOIN TJOB ON T1.OBJECT_NAME = TJOB.OBJECT_NAME
AND T1.OBJECT_TYPE <> 'DIRECTORY'


SELECT T1.OBJECT_NAME, T1.RN, T1.OBJECT_TYPE, T1.CREATED
FROM T1 LEFT OUTER JOIN TLOB ON T1.OBJECT_NAME = TLOB.OBJECT_NAME
LEFT OUTER JOIN TJOB ON T1.OBJECT_NAME = TJOB.OBJECT_NAME
AND T1.OBJECT_TYPE <> 'DIRECTORY'

Thanks


Tom Kyte
September 27, 2006 - 7:19 am UTC

what cost is too high for you?

I would just let it outer join myself, PDE - pretty darn efficient if you want to get all of the rows from all of the tables (three nice full scans, two hash outer joins, I'd be happy)

One Last Thing

Tom, October 11, 2006 - 11:35 am UTC

On page 115 of OCP Introduction to Oracle9i: SQL Exam Guide
it says:
"from tab_a a left outer join tab_b b on a.col_ = b.col_1 and b.col_2 = 'VALUE'"

I've never gotten that to work properly. I have always used:
"from tab_a a left outer join tab_b b on a.col_ = b.col_1 where b.col_2 = 'VALUE'"
Although I rarely use ansi/iso. OK, only for the test.

Is there something I'm missing here?

Tom Kyte
October 11, 2006 - 4:02 pm UTC


scott%ORA10GR2> select dept.dname, emp.ename, emp.job
2 from dept left join emp on (dept.deptno = emp.deptno and emp.job = 'CLERK');

DNAME ENAME JOB
-------------- ---------- ---------
RESEARCH SMITH CLERK
RESEARCH ADAMS CLERK
SALES JAMES CLERK
ACCOUNTING MILLER CLERK
OPERATIONS


works fine for me?

Su Baba, December 11, 2006 - 8:21 pm UTC

I'd like to get all the departments and its employees info, including departments that don't have any employees. However, I'd like to filter out employess that don't have a manager.  How do I do that?

SQL> SELECT d.dname, e.ename, e.mgr
  2  FROM   scott.dept d, scott.emp 
  3  WHERE  d.deptno = e.deptno (+)
  4  /

DNAME          ENAME             MGR
-------------- ---------- ----------
ACCOUNTING     CLARK            7839
ACCOUNTING     KING                  --> want to eliminate this
ACCOUNTING     MILLER           7782
RESEARCH       JONES            7839
RESEARCH       FORD             7566
RESEARCH       ADAMS            7788
RESEARCH       SMITH            7902
RESEARCH       SCOTT            7566
SALES          WARD             7698
SALES          TURNER           7698
SALES          ALLEN            7698
SALES          JAMES            7698
SALES          BLAKE            7839
SALES          MARTIN           7698
OPERATIONS

15 rows selected.
 

Tom Kyte
December 12, 2006 - 6:39 am UTC

....
from dept, (select * from emp where mgr is not null) emp
.....

adding 2 more tables to 3 original, using outer join

Zoe, February 21, 2007 - 10:49 am UTC

Hi there. I'm having problems trying to outer join 2 more tables to 3 existing ones: the 1st three give about 1400 records. I want to try to outer join the next two (so I still have 1400 recs, not 120), with occasional lines of tables 4 and 5 in synch, i.e.

a.col_1 a.col_2 b.col_3 c.col_4 d.col_5 e.col_6
---------------------------------------------------------
AAAA aaaa BBBB CCCC DDDD eeee (yes to this)
AaAa aAaA BbBb CcCc (yes to this)
A a B C D e (yes to this)
AA aa BB CC ee (NO)

So tables D,E are either both there or both not there
I tried to do this.....

Select a.col_1, a.col_2, b.col_3, c.col_4 d.col_5, e.col_6
From a,b,c,d,e
Where a.col1 = b.col1
And a.col2 = b.col2
Where a.col1 = c.col1
And a.col2 = c.col2
And (date restrictions, other conditions)
--new bit
Where a.col_1 = d.col_1(+) \--or some other thing here, I know this doesn¿t work now
And a.col2 = d.col2(+) /--as gives only 124 records now instead of 1400
And d.col_5 = e.col_5
And e.col_6 = some other conditions

Is this possible? To keep the same number of records? but the 5th table and its own join to the 4th is maybe making the attempt not work?
I'm very confused now; can you help?

Tom Kyte
February 21, 2007 - 11:12 am UTC

you think you are confused?

at least you have the schema's to look at.


select ....
  from (select .... 
          from a, b, c ) X,
       Y,
       Z
 where x.column = y.column(+)
   and x.column = z.column(+)


in general.

I thought about that but...

Zoe, March 05, 2007 - 11:13 am UTC

you had
select ....
from (select ....
from a, b, c ) X,
Y,
Z
where x.column = y.column(+)
and x.column = z.column(+)
sorry but that didn't work, I'd already tried this and similar, and resorted to using a temporary table to write a one-off script to insert into it with 2 cursors, then extracted the table to a spreadsheet I could send, rather than one select.

Main quibble was that X(a,b,c) links to y, but y _only_ links to z (and not X), and I could not get it to show the correct (thousands of) records where a,b,c(X) always there, and y,z either both filled or both empty.

outer join

A reader, July 09, 2007 - 5:43 am UTC

Hi Tom,

I tried to join these tables using ANSI join syntax, but stuck on syntax.

I've two tables rosters and roster_slots, where roster_slots is the child/detailed of rosters.

The query as follow:

select r1.......
from rosters r1
join roster_slots w1 on (
r1.depot = w1.depot and
r1.roster_type = w1.roster_type and
r1.resource_type = w1.resource_type and
r1.roster_id = w1.roster_id )
left outer join rosters r2 on (
r1.depot = r2.depot and
r1.roster_type = r2.roster_type and
r1.resource_type = r2.resource_type and
r1.cycle = r2.cycle and
r2.cycle_seq = 2 and -- Week 2 of the Cycle
r1.cycle_seq = 1 -- Week 1 of the cycle
)
left outer join roster_slots w2 on (
r2.depot = w2.depot and
r2.roster_type = w2.roster_type and
r2.resource_type = w2.resource_type and
r2.roster_id = w2.roster_id )
where w1.depot = w2.depot -- << need outer join here >>
and w1.roster_type = w2.roster_type
and w1.resource_type = w2.resource_type
and w1.slot_no = w2.slot_no
and w1.resource_id = w2.resource_id
and w1.sequence = w2.sequence
;

The query above returns the result correctly if I have week 1 and week 2. However if there isn't week 2 data, it won't return week 1, because of the join between w1 and w2.

How can I change the join between w1 and w2 to outer join as well.

Thanks in advance.

Tu-Oanh
Tom Kyte
July 09, 2007 - 7:12 am UTC

you have the syntax perfect. - well, you obviously do not want that predicate in the where clause that joins by depot, roster_type - as they would "undo the outer join" bit.


you would need to

a) give us create tables
b) insert intos

that are small, but demonstrate the issue. what you need is called a partitioned outer join in 10g or in 9i, we'll have to do a bit of cartesian product magic to materialize the results.

A reader, July 09, 2007 - 11:49 pm UTC

Hi,

I've prepared 3 scripts, but how do I load the scripts up?
The insert script is a bit large, it's creating 290 rows.

I'll try insert here ...

Just the create table script
CREATE TABLE ROSTERS
(
  DEPOT                 VARCHAR2(15 BYTE)       NOT NULL,
  RESOURCE_TYPE         VARCHAR2(15 BYTE)       NOT NULL,
  ROSTER_TYPE           VARCHAR2(15 BYTE)       NOT NULL,
  ROSTER_ID             VARCHAR2(8 BYTE)        NOT NULL,
  WEEK_ENDING           DATE,
  CYCLE                 VARCHAR2(15 BYTE),
  CYCLE_SEQ             NUMBER
);

ALTER TABLE ROSTERS ADD (
  CONSTRAINT ROSTERS_PK
 PRIMARY KEY
 (DEPOT, RESOURCE_TYPE, ROSTER_ID, ROSTER_TYPE));



CREATE TABLE ROSTER_SLOTS
(
  SLOT_SYSID        NUMBER(12)                  NOT NULL,
  ROSTER_ID         VARCHAR2(8 BYTE)            NOT NULL,
  DEPOT             VARCHAR2(15 BYTE)           NOT NULL,
  ROSTER_TYPE       VARCHAR2(15 BYTE)           NOT NULL,
  RESOURCE_TYPE     VARCHAR2(15 BYTE)           NOT NULL,
  SLOT_NO           NUMBER(3)                   NOT NULL,
  RESOURCE_ID       NUMBER(5),
  SEQUENCE          NUMBER(2)                   NOT NULL,
  SUN_WK_ID         VARCHAR2(15 BYTE),
  SUN_WK_TYPE       VARCHAR2(1 BYTE),
  SUN_TIME_ON       VARCHAR2(5 BYTE),
  SUN_TIME_OFF      VARCHAR2(5 BYTE),
  MON_WK_ID         VARCHAR2(15 BYTE),
  MON_WK_TYPE       VARCHAR2(1 BYTE),
  MON_TIME_ON       VARCHAR2(5 BYTE),
  MON_TIME_OFF      VARCHAR2(5 BYTE),
  TUE_WK_ID         VARCHAR2(15 BYTE),
  TUE_WK_TYPE       VARCHAR2(1 BYTE),
  TUE_TIME_ON       VARCHAR2(5 BYTE),
  TUE_TIME_OFF      VARCHAR2(5 BYTE)
);

CREATE UNIQUE INDEX ROSTER_SLOTS_PK ON ROSTER_SLOTS
(SLOT_SYSID);

ALTER TABLE ROSTER_SLOTS ADD (
  CONSTRAINT RSLOTS_ROSTER_FK 
 FOREIGN KEY (DEPOT, RESOURCE_TYPE, ROSTER_ID, ROSTER_TYPE) 
 REFERENCES ROSTERS (DEPOT,RESOURCE_TYPE,ROSTER_ID,ROSTER_TYPE));

The select ...

select *
from rosters
;

--  You will find cycle 'CYCLEAB' and 'CYCLEAC' have two weeks (by cycle_seq) and CYCLE27 and CYCLEAA only have one week

select r.cycle, r.cycle_seq, s.roster_id, count(*)
from rosters r, roster_slots s
where r.roster_id = s.roster_id 
group by r.cycle, r.cycle_seq, s.roster_id
order by cycle, cycle_seq
;

CYCLE CYCLE_SEQ ROSTER_ID COUNT(*)
CYCLE27 1       PIC4/5Z    12
CYCLEAA 1       PIC4/5AA    10
CYCLEAB 1       PIC4/5AB    66
CYCLEAB 2       PIC4/5AC    69
CYCLEAC 1       PIC4/5AD    62
CYCLEAC 2       PIC4/5AE    62

-- Also the number of records in ROSTER_SLOTS might not be the same for week 1 and week 2 within the cycle (ie. CYCLEAB)

--  I need to create a view that return all records on both table. 
select  r1.cycle wk1_cycle, r1.cycle_seq wk1_cycle_seq, r1.roster_id wk1_roster_id 
       , w1.slot_sysid wk1_slot_sysid, w1.slot_no wk1_slot_no, w1.sequence wk1_sequence
       , w1.resource_id wk1_resource_id
       , r2.cycle wk2_cycle, r2.cycle_seq wk2_cycle_seq, r2.roster_id wk2_roster_id
       , w2.slot_sysid wk2_slot_sysid, w2.slot_no wk2_slot_no, w2.sequence wk2_sequence
       , w2.resource_id wk2_resource_id 
from rosters r1 
  join roster_slots w1 on ( 
      r1.depot = w1.depot and 
      r1.roster_type = w1.roster_type and 
      r1.resource_type = w1.resource_type and 
      r1.roster_id = w1.roster_id
  --and   w1.slot_no = 1
  ) 
  left outer join rosters r2 on ( 
      r1.depot = r2.depot and  
      r1.roster_type = r2.roster_type and  
      r1.resource_type = r2.resource_type and  
      r1.cycle = r2.cycle and
      r2.cycle_seq = 2   -- Week 2 of the Cycle 
      ) 
  left outer join roster_slots w2 on ( 
      r2.depot = w2.depot and 
      r2.roster_type = w2.roster_type and 
      r2.resource_type = w2.resource_type and 
      r2.roster_id = w2.roster_id
  --and w2.slot_no = 1
    ) 
where r1.cycle_seq = 1    -- Week 1 of the cycle 
order by r1.roster_id, w1.slot_no, w1.sequence
;

-- The question is, how do I join W1 to W2 on these column below, bare in mind there might not be W2 that matches to W1 on those
--  join criterias. 

and w1.depot = w2.depot 
and w1.roster_type = w2.roster_type 
and w1.resource_type = w2.resource_type 
and w1.slot_no = w2.slot_no 
and w1.resource_id = w2.resource_id 
and w1.sequence = w2.sequence 

Tom Kyte
July 10, 2007 - 11:50 am UTC

sorry, this is bigger than a breadbox.

make it smaller - entirely smaller. did you read about partitioned outer joins?

I know you can demonstrate this with

o smaller create tables
o a lot less than 290 rows.

A reader, July 09, 2007 - 11:53 pm UTC

I will split it up

Here is the first part...

Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, MON_WK_ID, MON_TIME_ON, MON_TIME_OFF, TUE_WK_ID, TUE_TIME_ON, TUE_TIME_OFF)
 Values
   (48327, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5Z', 1, 1, 10104, 'RDO', '2526', '01:30', '09:30', '3528', '06:30', '14:30');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, MON_WK_ID, TUE_WK_ID)
 Values
   (49655, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AA', 1, 1, 19691, 'RDO', 'ANNUAL', 'ANNUAL');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, MON_WK_ID)
 Values
   (50982, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AB', 1, 1, 19985, 'RDO', 'ANNUAL');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, MON_WK_ID, TUE_WK_ID)
 Values
   (52338, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AC', 1, 1, 97130, 'RDO', 'ANNUAL', 'ANNUAL');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, MON_WK_ID, TUE_WK_ID)
 Values
   (53659, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AD', 1, 1, 97130, 'ANNUAL', 'ANNUAL');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, MON_WK_ID, TUE_WK_ID)
 Values
   (54967, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AE', 1, 1, 97130, 'ANNUAL', 'ANNUAL');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, SUN_TIME_ON, SUN_TIME_OFF, MON_WK_ID, MON_TIME_ON, MON_TIME_OFF, TUE_WK_ID, TUE_TIME_ON, TUE_TIME_OFF)
 Values
   (48328, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5Z', 2, 1, 19809, 'Y2SU', '09:00', '18:00', '2881', '06:00', '15:00', 'L1TU', '08:55', '17:55');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, SUN_TIME_ON, SUN_TIME_OFF, MON_WK_ID, MON_TIME_ON, MON_TIME_OFF, TUE_WK_ID)
 Values
   (49656, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AA', 2, 1, 10104, '1238', '05:00', '13:00', '2824', '10:15', '18:15', 'RDO');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, MON_WK_ID, TUE_WK_ID)
 Values
   (50983, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AB', 2, 1, 19691, 'NO TASK', 'ANNUAL', 'ANNUAL');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, SUN_TIME_ON, SUN_TIME_OFF, MON_WK_ID, MON_TIME_ON, MON_TIME_OFF, TUE_WK_ID)
 Values
   (52339, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AC', 2, 1, 19985, 'LEARN', '04:10', '12:10', 'LEARN', '04:50', '12:50', 'RDO');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, SUN_TIME_ON, SUN_TIME_OFF, MON_WK_ID, MON_TIME_ON, MON_TIME_OFF, TUE_WK_ID)
 Values
   (53660, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AD', 2, 1, 19985, '1235', '14:00', '22:00', '2235', '14:00', '22:00', 'RDO');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, MON_WK_ID, MON_TIME_ON, MON_TIME_OFF, TUE_WK_ID, TUE_TIME_ON, TUE_TIME_OFF)
 Values
   (54968, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AE', 2, 1, 19985, 'NO TASK', 'C2MO', '14:00', '23:00', '3534', '15:45', '23:45');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, SUN_TIME_ON, SUN_TIME_OFF, MON_WK_ID, MON_TIME_ON, MON_TIME_OFF, TUE_WK_ID, TUE_TIME_ON, TUE_TIME_OFF)
 Values
   (48329, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5Z', 3, 1, 18129, '1526', '06:45', '14:45', 'W2MO', '16:45', '00:45', 'C2TU', '14:00', '23:00');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, SUN_TIME_ON, SUN_TIME_OFF, MON_WK_ID, MON_TIME_ON, MON_TIME_OFF, TUE_WK_ID, TUE_TIME_ON, TUE_TIME_OFF)
 Values
   (49657, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AA', 3, 1, 19809, 'W2SU', '16:45', '00:45', 'W2MO', '16:45', '00:45', 'C2TU', '14:00', '23:00');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, SUN_TIME_ON, SUN_TIME_OFF, MON_WK_ID, MON_TIME_ON, MON_TIME_OFF, TUE_WK_ID)
 Values
   (50984, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AB', 3, 1, 10104, '1235', '14:00', '22:00', 'P2MO', '13:30', '21:30', 'RDO');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, MON_WK_ID, TUE_WK_ID)
 Values
   (52340, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AC', 3, 1, 19691, 'NO TASK', 'ANNUAL', 'ANNUAL');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, MON_WK_ID, TUE_WK_ID, TUE_TIME_ON, TUE_TIME_OFF)
 Values
   (53661, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AD', 3, 1, 19691, 'NO TASK', 'ANNUAL', '3540', '14:00', '22:00');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, TUE_WK_ID, TUE_TIME_ON, TUE_TIME_OFF)
 Values
   (54969, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AE', 3, 1, 19691, 'RDO', '3528', '06:30', '14:30');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, SUN_TIME_ON, SUN_TIME_OFF, MON_WK_ID, MON_TIME_ON, MON_TIME_OFF, TUE_WK_ID, TUE_TIME_ON, TUE_TIME_OFF)
 Values
   (48330, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5Z', 4, 1, 96081, 'C3SU', '22:00', '07:00', 'L2MO', '23:40', '08:40', '3826', '22:45', '06:45');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, SUN_TIME_ON, SUN_TIME_OFF, MON_WK_ID, MON_TIME_ON, MON_TIME_OFF, TUE_WK_ID, TUE_TIME_ON, TUE_TIME_OFF)
 Values
   (49658, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AA', 4, 1, 18129, 'C3SU', '22:00', '07:00', 'A2MO', '21:00', '06:00', '3826', '22:45', '06:45');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, MON_WK_ID, MON_TIME_ON, MON_TIME_OFF, TUE_WK_ID, TUE_TIME_ON, TUE_TIME_OFF)
 Values
   (50985, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AB', 4, 1, 19809, 'NO TASK', 'C2MO', '14:00', '23:00', '3534', '15:45', '23:45');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, MON_WK_ID, MON_TIME_ON, MON_TIME_OFF, TUE_WK_ID, TUE_TIME_ON, TUE_TIME_OFF)
 Values
   (52341, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AC', 4, 1, 10104, 'NO TASK', 'C1MO', '06:00', '15:00', '3534', '15:45', '23:00');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, TUE_WK_ID, TUE_TIME_ON, TUE_TIME_OFF)
 Values
   (53662, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AD', 4, 1, 10104, 'RDO', '3ST9', '06:30', '14:30');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, MON_WK_ID, MON_TIME_ON, MON_TIME_OFF, TUE_WK_ID, TUE_TIME_ON, TUE_TIME_OFF)
 Values
   (54970, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AE', 4, 1, 10104, 'Y1MO', '01:00', '10:00', 'W1TU', '04:45', '14:15');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, SUN_TIME_ON, SUN_TIME_OFF, TUE_WK_ID, TUE_TIME_ON, TUE_TIME_OFF)
 Values
   (48331, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5Z', 5, 1, 14650, '1824', '09:15', '17:15', 'M1TU', '04:40', '12:40');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, TUE_WK_ID, TUE_TIME_ON, TUE_TIME_OFF)
 Values
   (49659, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AA', 5, 1, 96081, 'RDO', 'SHED', '04:40', '12:40');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, TUE_WK_ID, TUE_TIME_ON, TUE_TIME_OFF)
 Values
   (50986, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AB', 5, 1, 18129, 'RDO', '3528', '06:30', '14:30');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, TUE_WK_ID, TUE_TIME_ON, TUE_TIME_OFF)
 Values
   (52342, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AC', 5, 1, 19809, 'RDO', '3528', '06:30', '14:30');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, MON_WK_ID, MON_TIME_ON, MON_TIME_OFF, TUE_WK_ID, TUE_TIME_ON, TUE_TIME_OFF)
 Values
   (53663, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AD', 5, 1, 19809, 'REQUEST', 'Y1MO', '01:00', '10:00', '3LT9', '04:00', '12:00');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, MON_WK_ID, MON_TIME_ON, MON_TIME_OFF, TUE_WK_ID)
 Values
   (54971, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AE', 5, 1, 19809, 'Y2MO', '09:00', '18:00', 'RDO');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, SUN_TIME_ON, SUN_TIME_OFF, MON_WK_ID, MON_TIME_ON, MON_TIME_OFF, TUE_WK_ID, TUE_TIME_ON, TUE_TIME_OFF)
 Values
   (48332, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5Z', 6, 1, 10087, '1526', '06:45', '14:45', 'WIMO', '04:45', '12:45', 'LEARN', '04:50', '12:50');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, MON_WK_ID, TUE_WK_ID)
 Values
   (49660, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AA', 6, 1, 14650, 'NO TASK', 'ANNUAL', 'ANNUAL');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, SUN_TIME_ON, SUN_TIME_OFF, MON_WK_ID, MON_TIME_ON, MON_TIME_OFF, TUE_WK_ID, TUE_TIME_ON, TUE_TIME_OFF)
 Values
   (50987, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AB', 6, 1, 96081, 'PISU', '04:10', '12:10', 'Y1MO', '01:00', '10:00', 'Y1TU', '01:00', '10:00');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, SUN_TIME_ON, SUN_TIME_OFF, MON_WK_ID, MON_TIME_ON, MON_TIME_OFF, TUE_WK_ID, TUE_TIME_ON, TUE_TIME_OFF)
 Values
   (52343, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AC', 6, 1, 18129, 'PISU', '04:10', '12:10', 'Y1MO', '01:00', '10:00', 'Y1TU', '01:00', '10:00');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, SUN_TIME_ON, SUN_TIME_OFF, MON_WK_ID, MON_TIME_ON, MON_TIME_OFF, TUE_WK_ID)
 Values
   (53664, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AD', 6, 1, 18129, 'L1SU', '09:10', '18:10', 'Y2MO', '09:00', '18:00', 'RDO');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, SUN_TIME_ON, SUN_TIME_OFF, MON_WK_ID, MON_TIME_ON, MON_TIME_OFF, TUE_WK_ID, TUE_TIME_ON, TUE_TIME_OFF)
 Values
   (54972, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AE', 6, 1, 18129, 'W2SU', '20:45', '05:45', 'C3MO', '22:00', '07:00', 'W2TU', '20:45', '05:45');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, SUN_TIME_ON, SUN_TIME_OFF, MON_WK_ID, MON_TIME_ON, MON_TIME_OFF, TUE_WK_ID, TUE_TIME_ON, TUE_TIME_OFF)
 Values
   (48333, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5Z', 7, 1, 19731, 'C2SU', '14:00', '23:00', 'S2MO', '15:40', '00:40', '3235', '14:00', '22:00');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, SUN_TIME_ON, SUN_TIME_OFF, MON_WK_ID, MON_TIME_ON, MON_TIME_OFF, TUE_WK_ID, TUE_TIME_ON, TUE_TIME_OFF)
 Values
   (49661, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AA', 7, 1, 10087, 'C2SU', '14:00', '23:00', 'S2MO', '15:40', '00:40', 'W2TU', '16:45', '00:45');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, MON_WK_ID, TUE_WK_ID)
 Values
   (50988, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AB', 7, 1, 14650, 'NO TASK', 'ANNUAL', 'ANNUAL');
Insert into ROSTER_SLOTS
   (SLOT_SYSID, DEPOT, RESOURCE_TYPE, ROSTER_TYPE, ROSTER_ID, SLOT_NO, SEQUENCE, RESOURCE_ID, SUN_WK_ID, MON_WK_ID, MON_TIME_ON, MON_TIME_OFF, TUE_WK_ID)
 Values
   (52344, 'PICTON', 'LOCO L4/5', 'OPERATIONAL', 'PIC4/5AC', 7, 1, 96081, 'REQUEST', 'C2MO', '14:00', '23:00', 'RDO');

A reader, July 10, 2007 - 11:01 pm UTC

Sorry about the inserts.

I've looked up on partitioned outer join in the SQL Reference and your web site but there is not enough related examples. I will search the internet for more examples.

Thanks very much.

A reader, July 11, 2007 - 11:17 pm UTC

Hi Tom,

I've worked out the query I needed. Howevever it needs some tunning. I tried using PUSH_PRED but does not seem to make any different.

The view is..

create or replace view roster_vw as
with all_slots as (
   select distinct r.depot, r.roster_type, r.resource_type, r.cycle, s.slot_no, s.sequence
   from   rosters r join roster_slots s on ( r.depot = s.depot and
                                             r.resource_type = s.resource_type and
                                             r.roster_id = s.roster_id and
                                             r.roster_type = s.roster_type                                              
                                             )
   )
select col1,......
from  all_slots left outer join (
         select col1,.....               
         from  rosters r1  left outer join roster_slots s1 on ( r1.depot = s1.depot and
                                                                r1.resource_type = s1.resource_type and
                                                                r1.roster_type = s1.roster_type and
                                                                r1.roster_id = s1.roster_id 
                                                              )
         where r1.cycle_seq = 1  -- Week 1
         -- << I need the filters to apply here >>
       ) w1  on ( all_slots.depot = w1.depot and
              all_slots.roster_type = w1.roster_type and
          all_slots.resource_type = w1.resource_type and
          all_slots.cycle = w1.cycle and
              all_slots.slot_no = w1.slot_no and
              all_slots.sequence = w1.sequence )
       left outer join (
         select  col1,...
         from  rosters r1  left outer join roster_slots s1 on ( r1.depot = s1.depot and
                            r1.resource_type = s1.resource_type and
                                                                r1.roster_type = s1.roster_type and
                                                                r1.roster_id = s1.roster_id
                                                              )
         where  r1.cycle_seq = 2   -- Week 2
         -- << and here... >>
       ) w2  on ( all_slots.depot = w2.depot and
              all_slots.roster_type = w2.roster_type and
          all_slots.resource_type = w2.resource_type and
          all_slots.cycle = w2.cycle and
              all_slots.slot_no = w2.slot_no and
              all_slots.sequence = w2.sequence );  

select *
from roster_vw
where depot = 'DEPOT'
and   roster_type  = 'ROSTER'
and   resource_type = 'RESOURCE'
and   cycle = 'CYCLE';

The explain plan as follow

Query Plan
--------------------------------------------------------------------------------
SELECT STATEMENT   [ALL_ROWS] Cost = 1696
  VIEW  CRS_CYCLE_ROSTERS_VW
    HASH JOIN OUTER
      HASH JOIN OUTER
        VIEW
          HASH UNIQUE
            NESTED LOOPS
              INDEX RANGE SCAN RS_PK [ANALYZED]
              TABLE ACCESS BY INDEX ROWID ROSTERS [ANALYZED]
                INDEX UNIQUE SCAN ROS_PK [ANALYZED]
        VIEW
          NESTED LOOPS OUTER
            TABLE ACCESS FULL ROSTERS [ANALYZED]
            TABLE ACCESS BY INDEX ROWID ROSTER_SLOTS [ANALYZED]
              INDEX RANGE SCAN RS_PK [ANALYZED]
      VIEW
        NESTED LOOPS OUTER
          TABLE ACCESS FULL ROSTERS [ANALYZED]
          TABLE ACCESS BY INDEX ROWID ROSTER_SLOTS [ANALYZED]
            INDEX RANGE SCAN RS_PK [ANALYZED]


As you can see it does full table access on ROSTERS in the view. I tried adding hint ' PUSH_PRED (w1, w2)' but it makes no different.

I need to apply the filters to the in-line views to avoid full table access.

Can view be created with parameters? If yes, how do I do that, or using PUSH_PRED is the only way.

Thanks very much for your help.

Tu-Oanh


Left Outer Join Output Doesnot Make Sense.

Richard Xu, August 23, 2007 - 7:12 pm UTC

I have a complex query, It like this.
mh > select RegionGrid.regionid,
  2                 max(nvl(LSGrid.distributionitemvalue,1)) maxvalue
  3             from
  4            (select DR.regionid, DI.distributionitemseq
  5               from distributions D
  6              inner join distributionregions DR
  7                 on (D.distributionid = DR.distributionid)
  8              inner join distributionitems DI
  9                 on (D.distributionid = DI.distributionid)
 10              where DI.distributionitemexpdt is null
 11                and D.DISTRIBUTIONEXPDT is null
 12                and DR.EXPIRYDT is null
 13                and D.distributiontypeid = 54
 14                and DI.distributionitemvalue = 1) RegionGrid
 15           left outer join
 16           (select DI.distributionitemseq,
 17                   DI.distributionitemvalue
 18              from distributionitems DI
 19             inner join distributions D
 20                on (D.DISTRIBUTIONID = DI.DISTRIBUTIONID)
 21             where DI.distributionitemexpdt is null
 22               and D.DISTRIBUTIONEXPDT is null
 23               and DI.distributionid=8455) LSGrid
 24          on (RegionGrid.distributionitemseq = LSGrid.distributionitemseq)
 25       group by regionid;

  REGIONID   MAXVALUE
---------- ----------
         0          0
       105          0
       106          0
       107          0
       108          0
       109          0
       110          0
       111          0
       112          0
       113          0

10 rows selected.

If I use left outer join, it changes the values.
mh > select R.regionid, nvl(regionmax.maxvalue,0)
  2         from regions R
  3         left outer join
  4          (select RegionGrid.regionid,
  5                 max(nvl(LSGrid.distributionitemvalue,1)) maxvalue
  6             from
  7            (select DR.regionid, DI.distributionitemseq
  8               from distributions D
  9              inner join distributionregions DR
 10                 on (D.distributionid = DR.distributionid)
 11              inner join distributionitems DI
 12                 on (D.distributionid = DI.distributionid)
 13              where DI.distributionitemexpdt is null
 14                and D.DISTRIBUTIONEXPDT is null
 15                and DR.EXPIRYDT is null
 16                and D.distributiontypeid = 54
 17                and DI.distributionitemvalue = 1) RegionGrid
 18           left outer join
 19           (select DI.distributionitemseq,
 20                   DI.distributionitemvalue
 21              from distributionitems DI
 22             inner join distributions D
 23                on (D.DISTRIBUTIONID = DI.DISTRIBUTIONID)
 24             where DI.distributionitemexpdt is null
 25               and D.DISTRIBUTIONEXPDT is null
 26               and DI.distributionid=8455) LSGrid
 27          on (RegionGrid.distributionitemseq = LSGrid.distributionitemseq)
 28       group by regionid) regionmax
 29       on (R.regionid = regionmax.regionid)
 30       order by regionid;

  REGIONID NVL(REGIONMAX.MAXVALUE,0)
---------- -------------------------
         0                         1
       105                         1
       106                         1
       107                         1
       108                         1
       109                         1
       110                         1
       111                         1
       112                         1
       113                         1

10 rows selected.

But If I use the inner join, it doesn't change value.
mh > select R.regionid, nvl(regionmax.maxvalue,0)
  2         from regions R
  3         Inner join
  4          (select RegionGrid.regionid,
  5                 max(nvl(LSGrid.distributionitemvalue,1)) maxvalue
  6             from
  7            (select DR.regionid, DI.distributionitemseq
  8               from distributions D
  9              inner join distributionregions DR
 10                 on (D.distributionid = DR.distributionid)
 11              inner join distributionitems DI
 12                 on (D.distributionid = DI.distributionid)
 13              where DI.distributionitemexpdt is null
 14                and D.DISTRIBUTIONEXPDT is null
 15                and DR.EXPIRYDT is null
 16                and D.distributiontypeid = 54
 17                and DI.distributionitemvalue = 1) RegionGrid
 18           left outer join
 19           (select DI.distributionitemseq,
 20                   DI.distributionitemvalue
 21              from distributionitems DI
 22             inner join distributions D
 23                on (D.DISTRIBUTIONID = DI.DISTRIBUTIONID)
 24             where DI.distributionitemexpdt is null
 25               and D.DISTRIBUTIONEXPDT is null
 26               and DI.distributionid=8455) LSGrid
 27          on (RegionGrid.distributionitemseq = LSGrid.distributionitemseq)
 28       group by regionid) regionmax
 29       on (R.regionid = regionmax.regionid)
 30       order by regionid;

  REGIONID NVL(REGIONMAX.MAXVALUE,0)
---------- -------------------------
         0                         0
       105                         0
       106                         0
       107                         0
       108                         0
       109                         0
       110                         0
       111                         0
       112                         0
       113                         0

10 rows selected.

Any one know why? Maybe it's an Oracle bug?
As my understanding, the inner join and outer join only affect the rows for output not for values. 

However, if I replace the second part on this left join query, it appears correct.
mh > select R.regionid, regionmax.maxvalue
  2         from regions R
  3         left outer join
  4          (select regionid, 0 as maxvalue from regions) regionmax
  5       on (R.regionid = regionmax.regionid)
  6       order by regionid
  7  ;

  REGIONID   MAXVALUE
---------- ----------
         0          0
       105          0
       106          0
       107          0
       108          0
       109          0
       110          0
       111          0
       112          0
       113          0

10 rows selected.

Tom Kyte
August 24, 2007 - 2:42 pm UTC

I did not even read the queries, but in answer to this:

...
Any one know why? Maybe it's an Oracle bug?
....

It is not a bug, a LEFT OUTER JOIN and an INNER JOIN are as different as NIGHT AND DAY

do you understand the difference between them?

Raj, March 03, 2009 - 12:18 pm UTC

Hi Tom,
I feel, you are God to Oracle learners. At my desk I have put your photo. Thanks for great forum.

I have one question, please help me.

SQL> desc a;
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------

 NAME                                               VARCHAR2(10)
 AGE                                                NUMBER
 SAL                                                NUMBER



SQL> desc b;
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------

 NAME                                               VARCHAR2(10)
 AGE                                                NUMBER
 COMM                                               NUMBER



SQL> select * from a;

NAME              AGE        SAL
---------- ---------- ----------
s                  10       1000
r                  20      10000



SQL> select * from b;

no rows selected


I wanted to get output like below.

a.name    a.age    b.name    b.age
s         10       null      null    

I wrote something like below 

SQL> select * from a,b where a.name(+)='s' and a.age(+)=10 and b.name(+)=a.name
and b.age(+)=a.age;

NAME              AGE        SAL NAME     AGE       COMM
---------- ---------- ---------- ---------- ---------- ----------
s                  10       1000



One more thing, I use the above select query inside a procedure using ref cursor. These 's' and 10 are passed as input parameters to the procedures.

insert into b values('abcd',22,2000);


SQL> select * from b;

NAME              AGE       COMM
---------- ---------- ----------
abcd               22       2000

Now I will pass the values as 'abcd' and 22 to the select query. But this time it is not showing any result. 


SQL> select * from a,b where a.name(+)='abcd' and a.age(+)=22 and b.name(+)=a.na
me and b.age(+)=a.age;

no rows selected

The output should be as,

NAME              AGE        SAL NAME    AGE       COMM
---------- ---------- ---------- ---------- ---------- ----------
                                 abcd     22       2000


Please suggest me a solution.

Regards.

Tom Kyte
March 03, 2009 - 9:07 pm UTC

If only I had a create table and inserts, I could test this - the gist will be:


select * 
  from (select * from a where name = :name and age = :age ) A
       full outer join
       (select * from b where name = :name and age = :age ) B
       on (a.name = b.name and a.age = b.age)


Raj

Raj, March 04, 2009 - 3:58 am UTC

Great..Thanks a lot....
I have very less coding experience (1 yr)..My aim is to become like you (Probably Impossible)....But I got a great inspiration from you..

Could you pls tell how to become master like you (if I dont eat your time)..

Regards,

Outer join Rule

David, March 04, 2009 - 5:24 pm UTC

Hi

A few days ago I was in a SQL Tuning Workshop and I came across this Outer Join Rule:

"For outer joins, the table with the outer-joined table
must come after the other table in the join order for
processing the join."


If we use an example such as:

select a.ename, b.*
from emp a, dept b
where a.deptno(+) = b.deptno
and a.mgr(+) = 7902

According the rule DEPT must be always the first to get resolved and then outer join to EMP. It says that this is to avoid incorrect results but in this precise example I dont see what can go wrong if we resolve a.mgr(+) = 7902 first then join to dept?

Do you have any examples to show this rule?

Thank!

David

Tom Kyte
March 04, 2009 - 8:40 pm UTC

Actually, that is backwards in most releases of Oracle (with hash outer joins anyway).

In current releases - we can drive off of either table in the hash outer join.
In older releases - we drive off of the table being outer joined TO (EMP in your example)

Quote from effective Oracle by design (9i and before)

<quote>

Hash Outer Joins

So, now that we understand how a natural join works using hash joins¿both in-memory and when some disk is required¿we can look at how a hash outer join works. It is a little counterintuitive; at first, I thought the documentation was wrong. The Oracle documentation (the Oracle Performance Guide) states, ¿The outer table (whose rows are being preserved) is used to build the hash table, and the inner table is used to probe the hash table.¿ That seemed backwards.
If I were to sit down and write the hash-join algorithm, I would have gone in the other direction. I would have hashed the table whose rows were not being preserved, and then read the table whose rows were being preserved, probing the hash table to see whether there was a row to join to. Oracle has chosen to do it the other way.

Consider a hash outer join such as this one:

scott@ORA920> select ename, dname
  2  from emp, dept
  3  where emp.deptno(+) = dept.deptno
  4  /

ENAME      DNAME
---------- --------------
SMITH      RESEARCH
ALLEN      SALES
¿
FORD       RESEARCH
MILLER     ACCOUNTING
           OPERATIONS
15 rows selected.

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=1909 Bytes=55361)
   1    0   HASH JOIN (OUTER) (Cost=5 Card=1909 Bytes=55361)
   2    1     TABLE ACCESS (FULL) OF 'DEPT' (Cost=2 Card=409 Bytes=8998)
   3    1     TABLE ACCESS (FULL) OF 'EMP' (Cost=2 Card=14 Bytes=98)


Here is how this is processed.:

1. The DEPT table is scanned and hashed.
2. The EMP table is scanned and for each row.
3. The DEPT hash table is probed for matches. When a match is found, the row is output and the entry in the hash table is marked as matched to a row.
4. After the EMP table is exhausted, the DEPT hash table is gone over once again, and any rows that are not marked as matched are output with NULL values for the EMP columns.

Notice that the output of the previous query has SMITH, ALLEN, ¿, FORD, MILLER in that order. Suppose you perform this query:

scott@ORA920> select ename
  2    from emp;

ENAME
----------
SMITH
ALLEN
¿
FORD
MILLER


You will find that the hash outer join result set presents the rows in order of the EMP table, and then at the end of the result set are all of the unmatched rows from DEPT.

Note: On your system, the rows may be in a different order. The results, however, will be consistent on your machine. The order you see them in the hash join will be the order you see them from a simple full scan.
In general, a hash join is excellent at joining two large sets or a small set with a large set. It is slow at getting the first row, because one of the sources must be hashed into memory (optimally), or memory and disk entirely, before the first row is returned. However, its performance in getting the remaining rows, especially when the hash table fits in memory, is excellent.
</quote>

Outer join Rule

david, March 10, 2009 - 5:37 am UTC

Excellent.

I attended 10gR2 sql tuning Workshop in Oracle University do think they probably need to specify the rule is for backward compatibility?


Tom Kyte
March 11, 2009 - 2:26 am UTC

I do not know what you mean at all here.

Performance drain

Umesh, March 24, 2009 - 5:24 am UTC

This is with respect to the very first posting in the thread. You have indicated that 


from t1, t2
 where t1.x = t2.x(+)
   and t2.any_other_column <some condition>


is a performance drain. I fully agree with you. 
I am facing the same issue, where in the developers have written a similar kind of  "where" clause which does not need a (+) 

The argument is I need to prove ,by  removing the (+) must improve upon the response time COMPARITIVELY. 

I get response times very identical i.e whether I use (+) or NOT  I get the same timings on the query

Please help Attached is the autotrace ( it is 10g) 

SQL>  select *
  2   from flight_seg currentfli0_ left outer join cust_jrny_version custjrnyve1_
  3   on currentfli0_.CUST_JRNY_ID=custjrnyve1_.CUST_JRNY_ID
  4   and currentfli0_.ENTRY_VERSION_NUMBER=custjrnyve1_.VERSION_NUMBER  left outer join cust_jrny customerjo2_
  5   on custjrnyve1_.CUST_JRNY_ID=customerjo2_.CUST_JRNY_ID left outer join subscriber subscriber3_
  6   on customerjo2_.SUBSCRIBER_ID=subscriber3_.SUBSCRIBER_ID
  7   where currentfli0_.CUST_JRNY_ID=13 and currentfli0_.HISTORY_VERSION_NUMBER=0;

Elapsed: 00:00:00.01

Execution Plan
----------------------------------------------------------
Plan hash value: 3981926879

----------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name                     | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |                          |    18E|    15E|    18E  (0)|999:59:59 |       |       |
|   1 |  NESTED LOOPS OUTER                   |                          |    18E|    15E|    18E  (0)|999:59:59 |       |       |
|   2 |   NESTED LOOPS OUTER                  |                          |    18E|    15E|    18E  (0)|999:59:59 |       |       |
|   3 |    NESTED LOOPS OUTER                 |                          |    18E|    15E|    59P  (1)|999:59:59 |       |       |
|   4 |     TABLE ACCESS BY GLOBAL INDEX ROWID| FLIGHT_SEG               |    59P|  4364P|     3   (0)| 00:00:01 | ROWID | ROWID |
|*  5 |      INDEX RANGE SCAN                 | FS_CUST_JRNY_VERSION_FK2 |    59P|       |     1   (0)| 00:00:01 |       |       |
|   6 |     TABLE ACCESS BY GLOBAL INDEX ROWID| CUST_JRNY_VERSION        |   493G|    29T|     1   (0)| 00:00:01 | ROWID | ROWID |
|*  7 |      INDEX UNIQUE SCAN                | CUST_JRNY_VERSION_PK     |     1 |       |     0   (0)| 00:00:01 |       |       |
|   8 |    TABLE ACCESS BY GLOBAL INDEX ROWID | CUST_JRNY                |   825G|    36T|     1   (0)| 00:00:01 | ROWID | ROWID |
|*  9 |     INDEX UNIQUE SCAN                 | CUST_JRNY_PK             |     1 |       |     0   (0)| 00:00:01 |       |       |
|  10 |   TABLE ACCESS BY INDEX ROWID         | SUBSCRIBER               | 33333 |   846K|     1   (0)| 00:00:01 |       |       |
|* 11 |    INDEX UNIQUE SCAN                  | SUBSCRIBER_PK            |     1 |       |     0   (0)| 00:00:01 |       |       |
----------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   5 - access("CURRENTFLI0_"."CUST_JRNY_ID"=13 AND "CURRENTFLI0_"."HISTORY_VERSION_NUMBER"=0)
   7 - access("CUSTJRNYVE1_"."CUST_JRNY_ID"(+)=13 AND "CURRENTFLI0_"."ENTRY_VERSION_NUMBER"="CUSTJRNYVE1_"."VERSION_NUMBER
              "(+))
   9 - access("CUSTJRNYVE1_"."CUST_JRNY_ID"="CUSTOMERJO2_"."CUST_JRNY_ID"(+))
  11 - access("CUSTOMERJO2_"."SUBSCRIBER_ID"="SUBSCRIBER3_"."SUBSCRIBER_ID"(+))


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         12  consistent gets
          0  physical reads
          0  redo size
       4646  bytes sent via SQL*Net to client
        338  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL>
SQL>
SQL>
SQL>
SQL> select *
  2   from flight_seg currentfli0_ inner join cust_jrny_version custjrnyve1_
  3   on currentfli0_.CUST_JRNY_ID=custjrnyve1_.CUST_JRNY_ID
  4   and currentfli0_.ENTRY_VERSION_NUMBER=custjrnyve1_.VERSION_NUMBER  inner join cust_jrny customerjo2_
  5   on custjrnyve1_.CUST_JRNY_ID=customerjo2_.CUST_JRNY_ID inner join subscriber subscriber3_
  6   on customerjo2_.SUBSCRIBER_ID=subscriber3_.SUBSCRIBER_ID
  7   where currentfli0_.CUST_JRNY_ID=13 and currentfli0_.HISTORY_VERSION_NUMBER=0;

Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
Plan hash value: 2918300563

----------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name                     | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |                          |    18E|    15E|    59P  (1)|999:59:59 |       |       |
|   1 |  NESTED LOOPS                         |                          |    18E|    15E|    59P  (1)|999:59:59 |       |       |
|   2 |   NESTED LOOPS                        |                          |    59P|  8309P|     6   (0)| 00:00:01 |       |       |
|   3 |    NESTED LOOPS                       |                          |     1 |    75 |     3   (0)| 00:00:01 |       |       |
|   4 |     TABLE ACCESS BY GLOBAL INDEX ROWID| CUST_JRNY                |     1 |    49 |     2   (0)| 00:00:01 | ROWID | ROWID |
|*  5 |      INDEX UNIQUE SCAN                | CUST_JRNY_PK             |     1 |       |     1   (0)| 00:00:01 |       |       |
|   6 |     TABLE ACCESS BY INDEX ROWID       | SUBSCRIBER               |   200K|  5078K|     1   (0)| 00:00:01 |       |       |
|*  7 |      INDEX UNIQUE SCAN                | SUBSCRIBER_PK            |     1 |       |     0   (0)| 00:00:01 |       |       |
|   8 |    TABLE ACCESS BY GLOBAL INDEX ROWID | FLIGHT_SEG               |    59P|  4364P|     3   (0)| 00:00:01 | ROWID | ROWID |
|*  9 |     INDEX RANGE SCAN                  | FS_CUST_JRNY_VERSION_FK2 |    59P|       |     1   (0)| 00:00:01 |       |       |
|  10 |   TABLE ACCESS BY GLOBAL INDEX ROWID  | CUST_JRNY_VERSION        |   493G|    29T|     1   (0)| 00:00:01 | ROWID | ROWID |
|* 11 |    INDEX UNIQUE SCAN                  | CUST_JRNY_VERSION_PK     |     1 |       |     0   (0)| 00:00:01 |       |       |
----------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   5 - access("CUSTOMERJO2_"."CUST_JRNY_ID"=13)
   7 - access("CUSTOMERJO2_"."SUBSCRIBER_ID"="SUBSCRIBER3_"."SUBSCRIBER_ID")
   9 - access("CURRENTFLI0_"."CUST_JRNY_ID"=13 AND "CURRENTFLI0_"."HISTORY_VERSION_NUMBER"=0)
  11 - access("CUSTJRNYVE1_"."CUST_JRNY_ID"=13 AND "CURRENTFLI0_"."ENTRY_VERSION_NUMBER"="CUSTJRNYVE1_"."VERSION_NUMBER")


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         12  consistent gets
          0  physical reads
          0  redo size
       4646  bytes sent via SQL*Net to client
        338  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed





Tom Kyte
March 29, 2009 - 10:28 am UTC

... is a performance drain. I fully agree with you. ...

is is possibly a performance drain (the optimizer is pretty smart about picking that out). What is primarily indicates is that the person coding the query knew not what they were doing.



... The argument is I need to prove ,by removing the (+) must improve upon the
response time COMPARITIVELY.
....

no you don't, the query:

  from t1, t2
 where t1.x = t2.x(+)
   and t2.any_other_column <some condition>


is 100% equivalent to

  from t1, t2
 where t1.x = t2.x
   and t2.any_other_column <some condition>


that is all you need to "prove", the optimizer will actually do this for us, if you leave the (+) in, all it does is make you look "not smart". People that inherit your code will roll their eyes and make fun of you.



request to look into the above Query of "performance drain"

Umesh, March 25, 2009 - 12:13 am UTC


Tom Kyte
March 29, 2009 - 2:27 pm UTC

sorry, it is very much catch as catch can here, I look when I have time.

Last week - five cities, seven flights, all flights 5pm or later to get to next city (hopefully before midnight but not always) so I could get up and do it all over again.

Sometimes I just don't have any time to look.

Hash Outer Join outperform Hash Join

A Reader, April 20, 2009 - 12:41 am UTC

Hi tom,

Appreciate your response.

I need to understand the way HASH OUTER JOIN performs internally. I was doing some performance tuning to one of the queries and found that the Outer join is meaningless in the business context to solve a problem.

I removed the (+) and to my surprise the equi-join is taking more time (~ 13 more minutes) to get back the results.

How could that be possible even if the driving table choosen can be any of them considering the 10gR2 release as you mentioned in your book Effective Oracle by design.

Any insights would be helpful.
Tom Kyte
April 21, 2009 - 12:43 pm UTC

compare the estimated cardinalities to the actuals and report back to us.

that is, lay the 'explain plan' next to the 'row source (from tkprof)' and see if the estimated (explain plan) row counts are radically different from the real (row source operation in tkprof)

show the tkprofs too.

Question on joins

A reader, May 19, 2009 - 9:02 am UTC

Dear Tom,

One of my friends send me a complex query to optimise. Within this query there is such a kind of join

from
t1 a1
,t1 b1
,t1 c1
, t2
where a1.x (+) = t2.x
and b1.x (+) = t2.x
and c1.x (+) = t2.x
and a1.x (+) = 'A'
and b1.x (+) = 'B'
and c1.x (+) = 'C'

I have asked him to phrase his query in english but he was unable to do it.

My question is

Such a kind of outer join is a waste of time isn't it?

Thanks very much for your answer

Tom Kyte
May 23, 2009 - 12:08 pm UTC

... I have asked him to phrase his query in english but he was unable to do it. ...

send him back "select * from dual" - tell him it answers his question. Since he doesn't have a clue, it really does.


no, such an outer join is not a waste of time, it does answer some question.


ops$tkyte%ORA10GR2> create table t1 ( x varchar2(1) );

Table created.

ops$tkyte%ORA10GR2> create table t2 ( x varchar2(1) );

Table created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> insert into t2 values ( 'x' );

1 row created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> select *
  2  from
  3        t1 a1
  4       ,t1 b1
  5       ,t1 c1
  6      , t2
  7  where   a1.x (+) = t2.x
  8  and     b1.x (+) = t2.x
  9  and     c1.x (+) = t2.x
 10  and     a1.x (+) = 'A'
 11  and     b1.x (+) = 'B'
 12  and     c1.x (+) = 'C'
 13  /

X X X X
- - - -
      x



if T1 and T2 relate to each other 1:1 however when X='A' and 1:1 when x='B' and 1:1 when x='C', the four table join could probably be done in a two table join.


but, we'd need to actually understand the question and the schema.

back to square one.

count(*) on left outer join

A reader, December 09, 2010 - 7:25 am UTC

Hi Tom,

I've a strange situation.

1. A "select *" with two left outer joins gives 46 rows.
2. Changing this to "select count(*)" gives 31.
3. Changing it to "select count(*) from (select * )" gives 46.

The execution plan from 1 + 3 shows two outer join operations while the execution plan from 2 shows only one. I'm on 11.1.0.7 EE with PSU 2.

Strange situation. Am I something missing here? Is there a known phenomenon?

Thanks,
Michael

Tom Kyte
December 09, 2010 - 11:03 am UTC

this sounds like a bug, can you give me an example to reproduce with?

Full Outer Join

Paul, May 23, 2011 - 8:06 am UTC

Your initial response shows a full outer join how I believe it should work however I'm encountering strange behaviour on 11.2.0.2 64bit

with test1 as (select 1 as col from dual union all select 2 from dual),
test2 as (select 2 as col from dual union all select 3 from dual)
select * from test1 t1 full outer join test2 t2 on t1.col = t2.col;

works as expected returning:
COL COL
---------------------- ----------------------
2 2
3
1


However the equivalent (?)

create table test1 as (select 1 as col from dual union all select 2 from dual);
create table test2 as (select 2 as col from dual union all select 3 from dual);
select * from test1 t1 full outer join test2 t2 on t1.col = t2.col;
drop table test1;
drop table test2;

Returns:
COL COL
---------------------- ----------------------
2 2

Is this a bug, or am I missing something subtle here?

Changing the query to:
select * from test1 t1 full outer join test2 t2 on nvl(t1.col,t2.col) = (t2.col,t1.col);

Changes the behaviour to the same as the first
Tom Kyte
May 23, 2011 - 12:04 pm UTC

ops$tkyte%ORA11GR2> select * from v$version;

BANNER
-------------------------------------------------------------------------------
Oracle Database 11g Enterprise 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 Linux: Version 11.2.0.2.0 - Production
NLSRTL Version 11.2.0.2.0 - Production

ops$tkyte%ORA11GR2> 
ops$tkyte%ORA11GR2> 
ops$tkyte%ORA11GR2> with test1 as (select 1 as col from dual union all select 2 from dual),
  2      test2 as (select 2 as col from dual union all select 3 from dual)
  3  select * from test1 t1 full outer join test2 t2 on t1.col = t2.col;

       COL        COL
---------- ----------
         2          2
                    3
         1

ops$tkyte%ORA11GR2> 
ops$tkyte%ORA11GR2> 
ops$tkyte%ORA11GR2> drop table test1;

Table dropped.

ops$tkyte%ORA11GR2> drop table test2;

Table dropped.

ops$tkyte%ORA11GR2> create table test1 as (select 1 as col from dual union all select 2 from dual);

Table created.

ops$tkyte%ORA11GR2> create table test2 as (select 2 as col from dual union all select 3 from dual);

Table created.

ops$tkyte%ORA11GR2> select * from test1 t1 full outer join test2 t2 on t1.col = t2.col;

       COL        COL
---------- ----------
         2          2
                    3
         1




I cannot reproduce yet, what is the output of:

select name || '=' || value
  from v$parameter where isdefault = 'FALSE';

outer join instead of not exists

Art, May 24, 2011 - 2:33 pm UTC

Hi Tom, what is your opinion on using the outer join over a not exists clause to speed up performance? Is there a better way of doing it?

from t1, t2
where t1.x = t2.x(+)
and t2.any_other_column is null

from t1
where not exists (
select null
from t2
where t2.x = t1.x
)


Thanks,

Art

Tom Kyte
May 25, 2011 - 11:13 am UTC

This is closely related:

http://www.oracle.com/technetwork/issue-archive/2006/06-may/o36asktom-099107.html


I say use the query that semantically expresses what you want. Since we typically arrive at the same plan (the optimizer already knows those two things are the same) - it isn't worth your time to obfuscate the code - say what you mean. Where not exists is the right approach in general:

ops$tkyte%ORA11GR2> create table t1 as select * from all_objects where 1=0;

Table created.

ops$tkyte%ORA11GR2> create table t2 as select * from all_objects where 1=0;

Table created.

ops$tkyte%ORA11GR2> 
ops$tkyte%ORA11GR2> exec dbms_stats.set_table_stats( user, 'T1', numrows => 10000000, numblks => 10000 );

PL/SQL procedure successfully completed.

ops$tkyte%ORA11GR2> exec dbms_stats.set_table_stats( user, 'T2', numrows => 10000000, numblks => 10000 );

PL/SQL procedure successfully completed.

ops$tkyte%ORA11GR2> 
ops$tkyte%ORA11GR2> alter table t1 add constraint t1_pk primary key( object_id );

Table altered.

ops$tkyte%ORA11GR2> alter table t2 add constraint t2_pk primary key( object_id );

Table altered.

ops$tkyte%ORA11GR2> 
ops$tkyte%ORA11GR2> set autotrace traceonly explain
ops$tkyte%ORA11GR2> select t1.* from t1, t2 where t1.object_id = t2.object_id (+) and t2.object_id is null;

Execution Plan
----------------------------------------------------------
Plan hash value: 2623930245

--------------------------------------------------------------------------------------
| Id  | Operation            | Name  | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |       |    10M|  1077M|       | 67805   (1)| 00:13:34 |
|*  1 |  HASH JOIN RIGHT ANTI|       |    10M|  1077M|   238M| 67805   (1)| 00:13:34 |
|   2 |   INDEX FULL SCAN    | T2_PK |    10M|   123M|       |     0   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL  | T1    |    10M|   953M|       |  2892   (7)| 00:00:35 |
--------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")

ops$tkyte%ORA11GR2> select * from t1 where not exists (select null from t2 where t2.object_id = t1.object_id );

Execution Plan
----------------------------------------------------------
Plan hash value: 2623930245

--------------------------------------------------------------------------------------
| Id  | Operation            | Name  | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |       |    10M|  1077M|       | 67805   (1)| 00:13:34 |
|*  1 |  HASH JOIN RIGHT ANTI|       |    10M|  1077M|   238M| 67805   (1)| 00:13:34 |
|   2 |   INDEX FULL SCAN    | T2_PK |    10M|   123M|       |     0   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL  | T1    |    10M|   953M|       |  2892   (7)| 00:00:35 |
--------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("T2"."OBJECT_ID"="T1"."OBJECT_ID")

ops$tkyte%ORA11GR2> set autotrace off

see also

Sokrates, May 25, 2011 - 11:20 am UTC

....
http://asktom.oracle.com/pls/apex/f?p=100:11:0::NO::P11_QUESTION_ID:1524631000346778953

since this discussion, I use "not exists" whenever it semantically expresses what I want - with good experiences.

Art, note that I am already on 11.2 - I think they have improved quite a bit in the optimizer here since 9 (almost sure) and 10 (I suspect)

Zvi, July 02, 2011 - 4:58 pm UTC

Hi Tom,

Thanks for answering a previous question of mine plus thanks for your massive effort supporting Oracle people. Your contribution is truly unique.

I'm quoting Database Performance Tuning Guide - 10g Release 2 (10.2) (Part Number B14211-03) as follows:

"13.6.7.3 Sort Merge Outer Joins

When an outer join cannot drive from the outer (preserved) table to the inner (optional) table, it cannot use a hash join...."

seems to be contradicting:

"13.6.7.2 Hash Join Outer Joins

The optimizer uses hash joins for processing an outer join ... if it is not possible to drive from the outer table to inner table."

Could you explain this contradiction?







Tom Kyte
July 05, 2011 - 7:52 am UTC

how do they contradict?

when X cannot do Y
if Y is not possible to do X

they seem to say the same thing to me?

"Sort Merge Outer Join" vs "Hash Join Outer Join"

Zvi, July 05, 2011 - 5:46 pm UTC


Hi Tom,

Furthering the issue of "13.6.7.3 Sort Merge Outer Joins" and "13.6.7.2 Hash Join Outer Joins", in what circumstances (involving outer joins) will the optimizer decide to use the one or the other? Will the optimizer choice be similar as in the case of inner joins?





Tom Kyte
July 06, 2011 - 7:57 am UTC

It costs them out based on your various optimizer settings (which should be set to default) and memory available. Hash joins favor large memory, sorting favors smaller memory in general.

It is unusual to see a sort merge join these days, mostly you would expect hash joins.

A reader, July 07, 2011 - 4:06 pm UTC

Hash joins favor large memory, sorting favors smaller memory in general.

why you think sorting favour small memory as compare to hash join

Can you elobrate on this point?
Tom Kyte
July 08, 2011 - 2:27 pm UTC

our sorting algorithm is optimized for small memory, large disk.

our hashing works best with lots of memory - avoiding the disk.

don't know what else to say?

More to Explore

Performance

Get all the information about database performance in the Database Performance guide.