Skip to Main Content

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, gabby.

Asked: March 05, 2001 - 1:34 am UTC

Last updated: January 14, 2013 - 11:21 am UTC

Version: 8.0.6

Viewed 10K+ times! This question is

You Asked

Hi

I have select with sub query
like
select e.id,e.name
from employee e
where
e.dept_no=(select dept_no
from dept
where dept_no=:bind)
my question is
how many time the sub query will run?
one time at the first time or for each row in the first select even that the answer will be the same

thanks
gabby


and Tom said...

IF dept_no is a column in DEPT, it will run once per query. This is probably what you mean.

If dept_no is *not* a column in DEPT (not having the definition of DEPT I cannot be sure) it will run once per row per query.

Thats a trick with subqueries -- dept_no does not have to be a column in dept. In dept, the dept_no might be named "dept#" or something else. The subquery will assume you mean dept_no from the outer query and will run with that.

Rating

  (46 ratings)

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

Comments

subqueries II

J?rund Vier Skriubakken, April 24, 2002 - 10:31 am UTC

Hi.

It's a little difficult to understand when it will no run only once per query. Therefore I give you an another example:

delete from mytable
where id = ( select min(id)
from mytable
where id >= :bind )

Will the subquery only be executed once per execution, or for each row in mytable?

Tom Kyte
April 24, 2002 - 3:19 pm UTC

That one will run once per delete statement, not per row since the subquery is NOT correlated (eg: it can be evaluated to a constant value and the delete is really "delete from mytable where id = CONSTANT"

correlated subqueries -- once per row.

On a similar subject

Martin, April 25, 2002 - 3:09 am UTC

Hiya Tom, 

This is on a similar subject. Consider this :

SQL> create table a (a  VARCHAR2(20) );

Table created.

SQL> create table b (a  VARCHAR2(20) );

Table created.

SQL> INSERT INTO a VALUES ( 'X' );

1 row created.

SQL> INSERT INTO b VALUES ( 'X' );

1 row created.

SQL> set autotrace traceonly explain

SQL> select (select a from a) from b
  2  /

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (FULL) OF 'B'


Can you explain why the explain plan doesn't show an access
path through table A? This is on 9.0.1.1.1 by the way.

Thanks in advance
 

Tom Kyte
April 25, 2002 - 7:18 am UTC

The explain plan capability currently does not have that ability, it just doesn't do it is all (unfortunately).

tune sub-queries with analytic functions

A reader, August 19, 2002 - 5:51 am UTC

hi tom

I have noticed that to find an employee´s name with highest salary we normally have to use sub-queries

like

select ename
from emp
where sal = (select max(sal) from emp)

which implies two index/table scan in emp.

from 8.1.6 we have these new analytic functions, is there any way to write above query without scanning twice the table?



Tom Kyte
August 19, 2002 - 7:51 am UTC

Well, in the above, you were not "scanning" the table twice, especially if SAL was indexed.

scott@ORA817DEV.US.ORACLE.COM> select *
  2    from ( select ename, sal,
  3                  dense_rank() over ( order by sal desc ) dr
  4             from emp )
  5   where dr = 1
  6  /

ENAME             SAL         DR
---------- ---------- ----------
KING             5000          1


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=14 Bytes=462)
   1    0   VIEW (Cost=3 Card=14 Bytes=462)
   2    1     WINDOW (SORT PUSHED RANK)
   3    2       TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=14 Bytes=252)



<b>Now, while analytic functions are in fact the coolest thing since sliced bread -- they are also not a silver bullet</b>

Here, the subquery is by far the best approach.  You are not really doing things by "set", you are doing things over the entire result set and then keeping a teeny tiny piece of it.  Consider:

ops$tkyte@ORA817DEV.US.ORACLE.COM> drop table emp;

Table dropped.

ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table emp as select * from scott.emp;

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> exec gen_data( 'EMP', 10000 )

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> create index sal_idx on emp(sal);

Index created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table emp compute statistics
  2  for table
  3  for all indexes
  4  for all indexed columns;

Table analyzed

ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace  on
ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> select ename, sal
  2    from emp
  3   where sal = ( select max(sal) from emp )
  4  /

ENAME             SAL
---------- ----------
LP^YXiTKRn   99996.61


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=2 Bytes=24)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=2 Card=2 Bytes=24)
   2    1     INDEX (RANGE SCAN) OF 'SAL_IDX' (NON-UNIQUE) (Cost=1 Card=2)
   3    2       SORT (AGGREGATE)
   4    3         INDEX (FULL SCAN (MIN/MAX)) OF 'SAL_IDX' (NON-UNIQUE) (Cost=3 Card=10014 Bytes=50070)




Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          5  consistent gets
          0  physical reads
          0  redo size
        429  bytes sent via SQL*Net to client
        425  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM> 
ops$tkyte@ORA817DEV.US.ORACLE.COM> select *
  2    from ( select ename, sal,
  3                                  dense_rank() over ( order by sal desc ) dr
  4                     from emp )
  5   where dr = 1
  6  /

ENAME             SAL         DR
---------- ---------- ----------
LP^YXiTKRn   99996.61          1


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=36 Card=10014 Bytes=330462)
   1    0   VIEW (Cost=36 Card=10014 Bytes=330462)
   2    1     WINDOW (SORT PUSHED RANK)
   3    2       TABLE ACCESS (FULL) OF 'EMP' (Cost=3 Card=10014 Bytes=120168)




Statistics
----------------------------------------------------------
          0  recursive calls
         12  db block gets
         84  consistent gets
          0  physical reads
          0  redo size
        482  bytes sent via SQL*Net to client
        425  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          1  rows processed

ops$tkyte@ORA817DEV.US.ORACLE.COM> 

<b>here the old fashioned way is the best way -- this is because the old fashioned way gets the max sal very very fast and then just does an index pickup to get the matching row(s).

The analytic function however needs to build the entire answer and then find the row(s) with dense_rank of 1</b>


So, fear not the old ways, they are still very good in many cases. 

pls share the procedure - exec gen_data( 'EMP', 10000 )

A reader, August 19, 2002 - 10:20 am UTC

show us the gen_data( 'EMP', 10000 ) procedure, we want to see how it produces data


Tom Kyte
August 19, 2002 - 11:19 am UTC

create or replace procedure gen_data( p_tname in varchar2, p_records in number )
authid current_user
as
l_insert long;
l_rows number default 0;
begin

dbms_application_info.set_client_info( 'gen_data ' || p_tname );
l_insert := 'insert /*+ append */ into ' || p_tname ||
' select ';

for x in ( select data_type, data_length,
nvl(rpad( '9',data_precision,'9')/power(10,data_scale),9999999999) maxval
from user_tab_columns
where table_name = upper(p_tname)
order by column_id )
loop
if ( x.data_type in ('NUMBER', 'FLOAT' ))
then
l_insert := l_insert || 'dbms_random.value(1,' || x.maxval || '),';
elsif ( x.data_type = 'DATE' )
then
l_insert := l_insert ||
'sysdate+dbms_random.value+dbms_random.value(1,1000),';
else
l_insert := l_insert || 'dbms_random.string(''A'',' ||
x.data_length || '),';
end if;
end loop;
l_insert := rtrim(l_insert,',') ||
' from all_objects where rownum <= :n';

loop
execute immediate l_insert using p_records - l_rows;
l_rows := l_rows + sql%rowcount;
commit;
dbms_application_info.set_module
( l_rows || ' rows of ' || p_records, '' );
exit when ( l_rows >= p_records );
end loop;
end;
/



difference between these two queries

A reader, September 25, 2003 - 8:34 am UTC

Hi

I am trying to convert a query so I am doing some tests with some test data using EMP table

I came up with these two queries

select * from emp x
where sal = (select max(sal) from emp where x.deptno = deptno and emp.empno not between 7600 and 7800 )
order by empno

select * from emp x
where sal = (select max(sal) from emp where x.deptno = deptno)
and empno not between 7600 and 7800
order by empno

They return different results but I cannot see the difference.... Can you explain why they are different please

many many thanks

Tom Kyte
September 25, 2003 - 8:48 am UTC

the first gets the max(sal) for that deptno as long as the empno is not between A and B. so the subquery will never return a value for those empnos (it'll return NULL) or skip them. so say empno = 7600 in deptno 10 makes the MOST, this subquery will ignore them. their salary will not be returned.

the second returns the max(sal) in that deptno. it then returns the emp that makes that much AS LONG AS that empno is not between A and B


so, the first query will probably return someone from deptno=10 -- the guy who works in deptno=10 whose sal is the max(sal) in deptpno 10 for all records except when empno between A and B

the second query will not return anyone from deptno=10 if the person in deptno=10 making the most has an empno between A and B.

Good

Peter, September 25, 2003 - 9:24 am UTC

Dear Tom,
Fine and expect the same from you.What is the use of the
"with" keyword?Does it relate to query possiblities like
"Merge" statement?Please provide a sample explanation.


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

Sam, September 25, 2003 - 9:57 am UTC

Hi Tom,
I am trying to rewrite the following query with correlated subqueries.

SELECT
A.ID
FROM
A,
B
WHERE
A.ID=B.ID AND
(B.CONTACT_DATE > (SELECT MAX(CONTACT_DATE) FROM C WHERE RESPONSE_CODE ='RV_BOUNCE' AND
A.ID = C.ID) OR
(SELECT MAX(CONTACT_DATE) FROM CS_CASE_DETAILS
WHERE A.ID = C.ID AND RESPONSE_CODE ='RV_BOUNCE') IS NULL);

Tables A,B,C have indexes on respective ID columns. Table A is the master table and Table B and C are transaction tables. B and C may or may not have corresponding records in Table A.

I rewrote the above query as follows to avoid correlated subqueries

SELECT ID
FROM
(
SELECT MAX_CNT_DATE,A.CS_CASE_INFO_ID
FROM
(
SELECT A.ID,C.CONTACT_DATE,
C.RESPONSE_CODE,
MAX(C.CONTACT_DATE) OVER (PARTITION BY A.ID) MAX_CNT_DATE
FROM A,C
WHERE A.ID=C.ID AND C.RESPONSE_CODE ='RV_BOUNCE'
) C ,
A,
B
WHERE A.ID=C.ID(+) AND A.ID=B.ID
)
WHERE (CONTACT_DATE > max_cnt_date OR max_cnt_date IS NULL)

Though, this query is running in approximately the same time as the original one, I feel there are better ways to do it.

Could you Please, suggest a better way to rewrite such a subquery where a third (i.e table C is not involved in outer queries) table is involved?

Since, this is a trivial query, may be someone else can suggest a solution ,if not Tom. Though I would prefer him).

Thanks & Regards
Sam


Sam, September 26, 2003 - 2:12 pm UTC

Hi Tom,
Apropos my previous post, could you Please, suggest a better alternative to the following query?
SELECT
A.ID
FROM
A,
B
WHERE
A.ID=B.ID AND
(B.CONTACT_DATE > (SELECT MAX(CONTACT_DATE) FROM C WHERE RESPONSE_CODE
='RV_BOUNCE' AND
A.ID = C.ID) OR
(SELECT MAX(CONTACT_DATE) FROM CS_CASE_DETAILS
WHERE A.ID = C.ID AND RESPONSE_CODE ='RV_BOUNCE') IS NULL);

Thanks & Regards
Sam

OK

Kumar, December 10, 2004 - 12:32 pm UTC

Hi Tom,
How does the following correlated subquery proceed,whether
from subquery to parent query or viceversa?The query is

SQL>select deptno,ename,sal from emp e where
    sal = (select max(sal) from emp where deptno =   e.deptno);


 

Tom Kyte
December 10, 2004 - 7:50 pm UTC

scope is always "nearest"

that is the same as:

select E.deptno, E.ename, E.sal from emp e where
E.sal = (select max(XX.sal) from emp XX where XX.deptno = e.deptno);



Please help

Mohammed, December 12, 2004 - 12:39 pm UTC

Hello Tom,
We are new to Oracle and We don't have access to New Oracle
Books.We are not clear with "How a correlated subquery
works?".Could you please explain it with a simple example?Do correlated subqueries refer to same tables just as Parent query does?
Expecting your reply,
K.Mohammed



Tom Kyte
December 12, 2004 - 7:32 pm UTC

first correlated subqueries are as old as dirt.

second all oracle doc is online at otn.oracle.com! you have access to ALL oracle documentation.


select *
from dept
where exists ( select null
from emp
where emp.deptno = DEPT.DEPTNO ) <<<=== correlated subquery

is a lot like:

for x in ( select * from dept )
loop
select count(*) into n from emp where emp.deptno = X.DEPTNO;
if ( n > 0 )
then
output X;
end if;
end loop;



Efficiency?

Bob B, December 12, 2004 - 10:50 pm UTC

I've noticed, though never tested, that adding "and rownum <= 1" to the predicate of an exists subquery, the processing time can go down.

If
<QUOTE>
select *
from dept
where exists ( select null
from emp
where emp.deptno = DEPT.DEPTNO ) <<<=== correlated subquery

is a lot like:

for x in ( select * from dept )
loop
select count(*) into n from emp where emp.deptno = X.DEPTNO;
if ( n > 0 )
then
output X;
end if;
end loop;
</QUOTE>

Wouldn't a "and rownum <= 1" predicate on the inner query speed things along? I know what you wrote was conceptual in nature, but it seems quite close to reality. I had a query that was

SELECT *
FROM MASTER_TABLE t1
WHERE EXISTS(
SELECT NULL
FROM DETAIL_TABLE t2
WHERE t1.ID = t2.ID
)

It took about 30 seconds to run. Adding "AND ROWNUM <= 1" to the inner subquery predicate cut the time down to 3 - 4 seconds. I was thinking that if the optimizer knew it had to evaluate the subquery once for each row evaluated, then it would implicitly hint the subquery with a /*+ FIRST_ROWS */ and potentially add the rownum <= 1 (i.e. get me any row as quick as possible). The only reasons I could come up with for not doing it this way would be performance issues when no rows are returned and the possibility of being able to fully materialize and load the subquery into memory.



Tom Kyte
December 13, 2004 - 10:11 am UTC


where exists is a "where rownum=1" type of query. Unless the plan changed (you may well have prevented merging with the rownum) -- they would be "the same"

did your plans change?

OK

Carolyn, December 18, 2004 - 11:46 am UTC

Hi Tom,

SQL> select deptno from dept d where not exists(
       select null from emp e where e.deptno = d.deptno);

I tried to transform the above query using a subquery as
follows :
 
SQL> select d.deptno from dept d,(select deptno from emp)e
      where d.deptno <> e.deptno;

But it is not working properly as expected.It results 
in a cross product.How to correct this?
  

Tom Kyte
December 18, 2004 - 12:57 pm UTC

that is not a subquery, that is an inline view....

subquery would be:


select deptno
from dept
where deptno NOT IN ( select deptno from emp <where deptno is not null>)
^^^^^^^^^^^^^^^^^^^^^^^^^^^

that where is only needed if deptno is NULLABLE in EMP

OK

Carolyn, December 19, 2004 - 1:06 am UTC

Hi Tom,
I asked why this query with inline view is not
working properly?

SQL> select d.deptno from dept d,(select deptno from emp)e
      where d.deptno <> e.deptno;

Please reply for that. 

Tom Kyte
December 19, 2004 - 11:03 am UTC

think about what happens conceptually.


When you have a query like:


select ...
from t1, t2
where <condition>


conceptually what happens is:

a) t1 and t2 are cartesianed producted together. EVERY row in t1 is joined to EVERY row in t2. So, if t1 had 5 rows and t2 had 10 rows -- there would be 50 rows as a result of T1xT2

b) the where clause would be applied.


Now, say t1 has:

DEPTNO
-------
10
20

and t2 has:

DEPTNO
------
20
30

t1xt2 would result in:

10 20 <<<=== deptno <> deptno
10 30 <<<=== deptno <> deptno
20 20
20 30 <<<=== deptno <> deptno

so you would get 3 rows back from your query -- all of the rows such that deptno <> deptno after performing a cartesian product.

Sub Query

Vithal, December 19, 2004 - 7:55 am UTC

Hi Tom,

i want to know the flow of this query. can you please help me in this.

select empno,ename,sal from emp a
where 3 >= (select count(distinct sal) from emp b
where a.sal <= b.sal);

Thanks

Tom Kyte
December 19, 2004 - 11:40 am UTC

conceptually


for x in ( select * from emp )
loop
select count(distinct sal) into l_sal_cnt from emp where X.SAL <= sal;
if ( 3 >= l_sal_cnt )
then
OUTPUT RECORD;
end if;
end loop;


PL/SQL

Vithal, March 20, 2005 - 6:29 am UTC

Tom,
I want to create a procedure which will insert some 5000000 records to empdup from different EMP table which contains same number of records.

But in EMP there are some duplicate records for empno and in empdup there unique constraint for empno.
I don’t want to use distinct clause in cursor rather then I want like when ever there the same employee information is inserting in empdup the program can raise an exception and go to next record and insert in empdup and so on keep inserting all the records except the duplicate

Can we do this if yes then how?


Tom Kyte
March 20, 2005 - 7:46 am UTC

NO cursor, NO procedural code

this is a solution that demands only SQL

insert into t2
select * from t1 where rowid in (select min(rowid) from t1 group by empno);

would be one way...


insert into t2
select ....
from (select t1.*, row_number() over (partition by empno order by rowid)rn
from t1)
where rn = 1;

would be another.

Nice

Dominique, March 22, 2005 - 9:04 pm UTC

Hello Tom,
Is it possible to get Nth max or min sal
with out using subquery?


Tom Kyte
March 23, 2005 - 1:13 am UTC

yes, with analytics.


but you'll be using an inline view........ unless salary is UNIQUE

Thanks

A reader, March 23, 2005 - 11:59 am UTC

Hi Tom,
Actually the question asked in an interview was
"Can you get the second maximum sal from emp table
with out using subquery?"
Any solution you have for this?



Tom Kyte
March 23, 2005 - 6:13 pm UTC

select *
from (select sal, rownum r
from (select distinct sal from emp order by 1 desc)
)
where r = 2;

no subqueries, but inline views.....

So, now Mr. Interviewer -- you cannot call that wrong (if you do you are wrong) but I can say it is sort of a silly question.....




PROBLEM WITH ROWNUM

Bhavesh Ghodasara, March 24, 2005 - 5:55 am UTC

hi tom,
I try urs example and it works great...
but if i write it like :
select ename,sal
from(select *
from emp
order by sal desc)
where rownum=&n
..
the result is :::
Enter value for n: 1
old 5: where rownum=&n
new 5: where rownum=1

ENAME SAL
---------- ----------
bhavesh 12000
which is the highest..

BUT if we enter the value like :
Enter value for n: 2
old 5: where rownum=&n
new 5: where rownum=2

no rows selected.

although there are more records..and i want to know second highest or what ever..
What is the secret behind that rownum function??
it will not work some times with '='
some times i also find that when i want to retrive the record between some ranges:

select *
from emp
where rownum>&min and rownum<&max;
it will not work

what is the reason..
pls reply..


Tom Kyte
March 24, 2005 - 8:53 am UTC

r = 2

NOT rownum = 2



Is that something wrong in my query??

BHAVESH PATEL, March 25, 2005 - 12:00 am UTC

hi tom,
you are right yours query runs fine...
but i want to know what is wrong with my query..??




Tom Kyte
March 25, 2005 - 7:44 am UTC

you said "where rownum = 2"

rownum will never be equal to two, unless it was at sometime "1". It never gets to be 1 since 1 <> 2

think of rownum being assigned like this:


rownum = 1
for x in ( select * from your_query minus any predicate on rownum )
loop
if (predicate on rownum "where rownum = 2" is true )
then
output record
rownum = rownum+1
end if
end loop


well, rownum never equals two because rownum starts at one and never ever gets incremented.



SQL

Mallikarjun Rao, July 15, 2005 - 5:41 pm UTC

excellent answers, more useful, needs some graphical representations with answers

Tom Kyte
July 15, 2005 - 8:41 pm UTC

hmm, wonder what a rownum looks like as a picture ;)

Remove Correlated Subquery?

S.P., July 28, 2005 - 4:01 pm UTC

Is there a way to rewrite this without using correlated subqueries/or cursors/loops? Using temp tables/batch based sql?

SELECT E.department_id, last_name, salary
FROM employee E
WHERE salary = (SELECT MAX (salary)
FROM employee E2
WHERE E.department_id = E2.department_id);

Thanks.

Tom Kyte
July 29, 2005 - 7:30 am UTC

select *
from (select ..., max(sal) over (partition by deptartment_id) max_sal
from employee )
where salary = max_sal;



max sal of employee

Ranjana Ahuja, February 20, 2006 - 1:10 am UTC

All the q's asked in ur site are good enough, they are sometimes very useful when one know the solution but don't get on the moment. The best software that i choose after experience is Oracle. Its not only user friendly to users but also for DBA's and developers, its really good.
i will get accurate ans to my q's in ur site. Users who are working in ur site have also good experience and its really nice when someone ask q and users use their mind to give the perfect solution. This job is very nice.

Thanks for such a wonderful site
Ranjana Ahuja

group by

jas, June 14, 2006 - 1:14 pm UTC

hi Tom

I have to write a query which has output of both these queries

select c,d,e from a,b where where date_id=v_date
group by c,d,e
or
select c,d,e from a,b where where date_id=trunc(to_date(v_date)-10)
group by c,d,e

how can i write it as subquries don't use group by clause


Tom Kyte
June 15, 2006 - 8:07 am UTC

this does not "make sense"

and subqueries may certainly use group by, but I don't see any subqueries???

In fact, I don't see any aggregates either so the group by doesn't make sense either.

addition to group by

A reader, June 14, 2006 - 1:17 pm UTC

select c,d,e from a,b where where date_id=v_date
group by c,d,e
having count(*)>1
or
select c,d,e from a,b where where date_id=trunc(to_date(v_date)-10)
group by c,d,e
having count(*)>4


Tom Kyte
June 15, 2006 - 8:12 am UTC

select c, d, e,
count( case when date_id = v_date then 1 end ) cnt1,
count( case when date_id = trunc(v_date)-10) then 1 end ) cnt2
from a, b
where ...
and (date_id = v_date or date_id = trunc(v_date)-10)
group by c, d, e
having count( case when date_id = v_date then 1 end ) > 1
or count( case when date_id = trunc(v_date)-10) then 1 end ) > 4;

might be it, hard to say without AN EXAMPLE ;)

I'm assuming v_date is a DATE in the first place, please do not to_date a date, that is "really a bad idea"

subquery

Girish, June 15, 2006 - 8:31 am UTC

It was good explaination

Tom,

If I have a query like

1)
select ename,job,sal
from emp
where exists ( select 'X' from dept
where a.deptno = b.deptno);

then whether first inner query will executed then out query or for each of the row returned by outer query inner query will be executed

2)
select ename,job,sal
from emp
where exists ( select 'X' from dept
where deptno = :dno);

In case 2 is it that first inner query is executed and then based on true/false outer query will be executes ?

regds
Girish




Tom Kyte
June 15, 2006 - 8:49 am UTC

1) yes, no, maybe

the optimizer is free to do many things there.


2) in this case is it LIKELY that the subquery would be evaluated and used as sort of an "if statement" to see if the outer query should be run in its entirety.

any thoughts on this query

A reader, April 04, 2007 - 6:43 pm UTC

Tom,

any suggestions on improving a code like this. please keep in mind that is part of a big query, but if I cut this out it run in seconds else > 4 min.

Thank you

here is the piece of code...

--- AND party_status_cd IN ('STAFF', 'VERIF')
--- AND effective_dt =
--- (SELECT MAX (effective_dt)
-- FROM MAIN_TABLE T1
-- WHERE T1.party_id = P2.party_id
-- GROUP BY T1.party_id)

Tom Kyte
April 05, 2007 - 10:47 am UTC

impossible to say - likely you can use analytics for this, but who knows.... given that a snippet of a large query taken out of context means almost nothing...

the whole query

A reader, April 05, 2007 - 10:54 am UTC

SELECT DISTINCT pty_mina_party_id,
max_search_mbe_dbe_date
(pty_mina_party_id,
pty_party_effective_dt
) AS pty_party_effective_dt,
cdt_party_type_cd, cdt_party_status_cd, bus_name,
name_ovrride, physical_location_id,
DECODE (cdt_party_type_cd,
'MINA_PTY_TYP_BR', physical_nm,
bus_name
) AS physical_location_nm,
city_nm, country_nm, cdt_iso2_country_cd, state_province_cd,
state_province_nm, postal_zip_cd, cdt_address_type_cd,
cubicle, mailstop, FLOOR, address_line_1, address_line_2,
address_line_3, address_line_4, suite, duns_nr, status_cd,
subsidiary_in, domestic_ultimate_ind, global_ultimate_in,
dnb_business_nm, tradestyle_primary_nm, tradestyle_second_nm,
tradestyle_third_nm, tradestyle_fourth_nm,
tradestyle_fifth_nm,
(SELECT (SELECT cdt_business_classification_cd
FROM business_record_brc brc
WHERE brc.pty_mina_party_id(+) =
temp_table.pty_mina_party_id
AND brc.pty_party_effective_dt(+) =
temp_table.pty_party_effective_dt)
|| (SELECT cdt_business_classification_cd
FROM mina_business_entity_mbe mbe
WHERE mbe.pty_mina_party_id(+) =
temp_table.pty_mina_party_id
AND mbe.pty_party_effective_dt(+) =
temp_table.pty_party_effective_dt)
FROM DUAL) AS cdt_business_classification_cd
FROM (SELECT pty.pty_mina_party_id, pty.pty_party_effective_dt,
pty.cdt_party_status_cd,
(SELECT name_value_tx
FROM business_name_bnm bnm
WHERE bnm.cdt_business_name_type_cd = 'BUS_NM_TYP_BUS'
AND bnm.pty_mina_party_id = pty.pty_mina_party_id
AND bnm.pty_party_effective_dt =
pty.pty_party_effective_dt)
AS bus_name,
(SELECT name_value_tx
FROM business_name_bnm bnm_ovr
WHERE bnm_ovr.cdt_business_name_type_cd ='BUS_NM_TYP_OVR'
AND bnm_ovr.cdt_status_cd = 'BUS_NM_STAT_CD_ACTIV'
AND bnm_ovr.pty_mina_party_id = pty.pty_mina_party_id
AND bnm_ovr.pty_party_effective_dt = pty.pty_party_effective_dt)
AS name_ovrride,
DECODE
(pty.cdt_party_type_cd,
'MINA_PTY_TYP_BR', (SELECT ptp_inn.pty_mina_party_one_id
FROM party_to_party_assoc_ptp ptp_inn
WHERE ptp_inn.pty_mina_party_two_id =
pty.pty_mina_party_id
AND ptp_inn.pty_party_effective_two_dt =
pty.pty_party_effective_dt
AND ptp_inn.cdt_party_association_type_cd =
'ASSOC_BE_TO_BR'
AND ptp_inn.cdt_status_cd =
'ASSOC_ACTIV'
AND ptp_inn.ptp_party_party_assoc_eff_dt =
(SELECT MAX
(ptp_inn2.ptp_party_party_assoc_eff_dt
)
FROM party_to_party_assoc_ptp ptp_inn2
WHERE ptp_inn.pty_mina_party_two_id =
ptp_inn2.pty_mina_party_two_id
AND ptp_inn.pty_party_effective_two_dt =
ptp_inn2.pty_party_effective_two_dt
AND ptp_inn2.cdt_party_association_type_cd =
'ASSOC_BE_TO_BR'
GROUP BY ptp_inn2.pty_mina_party_two_id,
ptp_inn2.pty_party_effective_two_dt)),
pty.pty_mina_party_id
) AS physical_location_id,
(SELECT name_value_tx
FROM business_name_bnm bnm,
party_to_party_assoc_ptp ptp_inn
WHERE ptp_inn.pty_mina_party_two_id =
pty.pty_mina_party_id
AND ptp_inn.pty_party_effective_two_dt =
pty.pty_party_effective_dt
AND ptp_inn.cdt_party_association_type_cd =
'ASSOC_BE_TO_BR'
AND ptp_inn.cdt_status_cd = 'ASSOC_ACTIV'
AND ptp_inn.ptp_party_party_assoc_eff_dt =
(SELECT MAX
(ptp_inn2.ptp_party_party_assoc_eff_dt
)
FROM party_to_party_assoc_ptp ptp_inn2
WHERE ptp_inn.pty_mina_party_two_id =
ptp_inn2.pty_mina_party_two_id
AND ptp_inn.pty_party_effective_two_dt =
ptp_inn2.pty_party_effective_two_dt
AND ptp_inn2.cdt_party_association_type_cd =
'ASSOC_BE_TO_BR'
GROUP BY ptp_inn2.pty_mina_party_two_id,
ptp_inn2.pty_party_effective_two_dt)
AND ptp_inn.pty_mina_party_one_id =
bnm.pty_mina_party_id
AND ptp_inn.pty_party_effective_one_dt =
bnm.pty_party_effective_dt
AND bnm.cdt_business_name_type_cd =
'BUS_NM_TYP_BUS')
AS physical_nm,
loc.city_nm, loc.country_nm, loc.cdt_iso2_country_cd,
loc.state_province_cd, loc.state_province_nm,
loc.postal_zip_cd, pad.cdt_address_type_cd,
pad.cubicle, pad.mailstop, pad.FLOOR,
loc.address_line_1, pad.address_line_2,
pad.address_line_3, pad.address_line_4, pad.suite,
dbe.duns_nr, dbe.status_cd, dbe.dnb_business_nm,
dbe.tradestyle_primary_nm, dbe.tradestyle_second_nm,
dbe.tradestyle_third_nm, dbe.tradestyle_fourth_nm,
dbe.tradestyle_fifth_nm, pty.cdt_party_type_cd,
dbe.subsidiary_in, dbe.domestic_ultimate_ind,
dbe.global_ultimate_in,
ptp.cdt_party_association_type_cd
FROM party_address_pad pad,
business_record_brc brc,
(SELECT /*+ INDEX(bnm TX_ID)*/
pty_mina_party_id, pty_party_effective_dt
FROM business_name_bnm bnm
WHERE bnm.cdt_business_name_type_cd IN
('BUS_NM_TYP_BUS',
'BUS_NM_TYP_DNB_LGL',
'BUS_NM_TYP_OVR',
'BUS_NM_TYP_TRD_STY_1',
'BUS_NM_TYP_TRD_STY_2',
'BUS_NM_TYP_TRD_STY_3',
'BUS_NM_TYP_TRD_STY_4',
'BUS_NM_TYP_TRD_STY_5'
)
AND contains (name_value_tx, 'IBM%') > 0) bnm,
mina_business_entity_mbe mbe,
party_to_party_assoc_ptp ptp,
location_loc loc,
dnb_business_entity_dbe dbe,
mina_party_pty pty
WHERE pad.pty_mina_party_id = pty.pty_mina_party_id
AND pad.pty_party_effective_dt = pty.pty_party_effective_dt
AND bnm.pty_mina_party_id = pty.pty_mina_party_id
AND bnm.pty_party_effective_dt = pty.pty_party_effective_dt
AND loc.loc_location_key = pad.loc_location_key
AND cdt_party_type_cd IN ('MINA_PTY_TYP_BE', 'MINA_PTY_TYP_BR')
AND brc.pty_mina_party_id(+) = pty.pty_mina_party_id
AND brc.pty_party_effective_dt(+) = pty.pty_party_effective_dt
AND mbe.pty_mina_party_id(+) = pty.pty_mina_party_id
AND mbe.pty_party_effective_dt(+) = pty.pty_party_effective_dt
AND ptp.pty_mina_party_one_id(+) = pty.pty_mina_party_id
AND ptp.pty_party_effective_one_dt(+) = pty.pty_party_effective_dt
AND ptp.cdt_party_association_type_cd = 'ASSOC_BE_TO_DNB' ---do not need the outer join
AND ptp.cdt_status_cd = 'ASSOC_ACTIV' ---do not need the outer join
AND dbe.pty_mina_party_id(+) = ptp.pty_mina_party_two_id
AND dbe.pty_party_effective_dt(+) = ptp.pty_party_effective_two_dt
AND pty.cdt_party_status_cd IN ('BL_STAT_UNVERIF', 'BL_STAT_VERIF')----PROBLEM
AND pty.pty_party_effective_dt =
(SELECT MAX (pty1.pty_party_effective_dt)
FROM mina_party_pty pty1
WHERE pty1.pty_mina_party_id = pty.pty_mina_party_id
GROUP BY pty1.pty_mina_party_id )
AND ( ptp.ptp_party_party_assoc_eff_dt =
(SELECT MAX (ptp_party_party_assoc_eff_dt)
FROM party_to_party_assoc_ptp
WHERE pty_mina_party_one_id =
ptp.pty_mina_party_one_id
AND pty_party_effective_one_dt =
ptp.pty_party_effective_one_dt
AND cdt_party_association_type_cd = 'ASSOC_BE_TO_DNB' ---do not need the outer join
AND cdt_status_cd = 'ASSOC_ACTIV' ---do not need the outer join
GROUP BY pty_mina_party_one_id,
pty_party_effective_one_dt)
OR NOT EXISTS (
SELECT 1
FROM party_to_party_assoc_ptp
WHERE pty_mina_party_one_id = ptp.pty_mina_party_one_id
AND pty_party_effective_one_dt = ptp.pty_party_effective_one_dt
AND cdt_party_association_type_cd = 'ASSOC_BE_TO_DNB'
AND cdt_status_cd = 'ASSOC_ACTIV'
GROUP BY pty_mina_party_one_id, pty_party_effective_one_dt)
)
AND '0' = '0'
AND ( brc.cdt_business_classification_cd NOT IN ('BL_CLASS_INTER')
OR mbe.cdt_business_classification_cd NOT IN ('BL_CLASS_INTER')
)) temp_table
WHERE ROWNUM < 102


Tom Kyte
April 05, 2007 - 11:36 am UTC

yup, not going to touch that one.

in general, if you have:

select *
from t t1
where dt = (select max(dt) from t t2 where t1.something=t2.something);


You can


select *
from (select t.*, max(dt) over (partition by something) max_dt
from t
)
where dt = max_dt;


instead - you can try that logic and see if it works for you after you understand what it does/how it works...


scott%ORA10GR2> select deptno, ename, hiredate
  2  from emp e1
  3  where hiredate = (select max(hiredate) from emp e2 where e2.deptno = e1.deptno);

    DEPTNO ENAME      HIREDATE
---------- ---------- ---------
        20 ADAMS      23-MAY-87
        30 JAMES      03-DEC-81
        10 MILLER     23-JAN-82

scott%ORA10GR2> select *
  2  from (select deptno, ename, hiredate, max(hiredate) over (partition by deptno) max_dt from emp)
  3  where hiredate = max_dt;

    DEPTNO ENAME      HIREDATE  MAX_DT
---------- ---------- --------- ---------
        10 MILLER     23-JAN-82 23-JAN-82
        20 ADAMS      23-MAY-87 23-MAY-87
        30 JAMES      03-DEC-81 03-DEC-81



thanks

A reader, April 05, 2007 - 11:59 am UTC

I was thinking along those lines from a previous post. I knew also that you won't touch that one because of how big it is. Thanks for looking at it anyway.


I am back

A reader, April 08, 2007 - 6:57 pm UTC

Tom,

how can someone replace(max)the following with analystical as you have suggested? Please advice.


AND pty.pty_party_effective_dt =
(SELECT MAX (pty1.pty_party_effective_dt)
FROM mina_party_pty pty1
WHERE pty1.pty_mina_party_id = pty.pty_mina_party_id
GROUP BY pty1.pty_mina_party_id )

Tom Kyte
April 09, 2007 - 10:42 am UTC

do you understand the approach I outlined right above? I gave you an example with scott/tiger data???!!?!

???!!?!

A reader, April 09, 2007 - 12:04 pm UTC

too much traveling maybe???!!?!
Tom Kyte
April 09, 2007 - 1:08 pm UTC

I don't understand what you mean by that.

I gave an example of turning:


AND pty.pty_party_effective_dt =
(SELECT MAX (pty1.pty_party_effective_dt)
FROM mina_party_pty pty1
WHERE pty1.pty_mina_party_id = pty.pty_mina_party_id
GROUP BY pty1.pty_mina_party_id )


into analytical functions - and someone then asked me:

how can someone replace(max)the following with analystical as you have suggested? Please advice.

I'm just displaying my surprise at getting a question that was "pre-answered".

have things changed in 10g

reader, August 26, 2008 - 7:27 am UTC

Dear Tom,

good day to you, I was going through the post and tried the two queries one using subquery and another with analytical function. We are using 10g ver 10.2.0.1.0 on Windows. Please see below the output from autotrace.

SCOTT@perf>select * from emp where sal= (select max(sal) from emp);

EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
7839 KING PRESIDENT 17-NOV-81 5000 10


Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=6 Card=1 Bytes=37)
1 0 TABLE ACCESS (FULL) OF 'EMP' (TABLE) (Cost=3 Card=1 Bytes=
37)

2 1 SORT (AGGREGATE)
3 2 TABLE ACCESS (FULL) OF 'EMP' (TABLE) (Cost=3 Card=14 B
ytes=56)





Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
15 consistent gets
0 physical reads
0 redo size
837 bytes sent via SQL*Net to client
508 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

SCOTT@perf>select * from ( select empno,ename,sal, dense_rank() over(order by sal desc) dr from emp) where dr=1;

EMPNO ENAME SAL DR
---------- ---------- ---------- ----------
7839 KING 5000 1


Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=4 Card=14 Bytes=64
4)

1 0 VIEW (Cost=4 Card=14 Bytes=644)
2 1 WINDOW (SORT PUSHED RANK) (Cost=4 Card=14 Bytes=196)
3 2 TABLE ACCESS (FULL) OF 'EMP' (TABLE) (Cost=3 Card=14 B
ytes=196)





Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
7 consistent gets
0 physical reads
0 redo size
595 bytes sent via SQL*Net to client
508 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1 rows processed

The consistent gets using Analytical function is less but there's 1 sort in memory, I have two questions here.

1. any changes made in 10g compared to 8i since the earlier stats posted by you show consistent more when using Analytical functions.

2. since there's one sort in memory when using Analytical function compared to none when using subquery, should we still use subquery or analytical function is better.

Please shed some light on this.

Regards.
Tom Kyte
August 26, 2008 - 9:25 pm UTC

please use a real sized table. I stopped reading when I say you used "EMP" as is, with one block and 14 rows. It is not relevant in real life.

after using gen_data procedure

reader, August 28, 2008 - 2:43 am UTC

Dear Tom,

good day to you and thank you for taking up my query. I used your procedure gen_data to populate the table and after following all the steps you listed I found that the stats at my end are similar to the ones posted by you.

I dropped the index on salary column and executed the two queries, I found that the consistent gets by subquery is more compared to the query using analytical function, but the query using analytical function performs a sort in memory.

In a case when there's no index on the column being used for max(),how should this be evaluated, consistent gets are less but sort is being performed vs. consistent gets are more.

Thanks for your time and efforts on this and all the other questions you take, there are lot of learnings from your site.

Regards,
your fan.

I am going in wrong direction

reader, September 12, 2008 - 11:13 am UTC

Dear Tom,

good day to you as always, just wanted to know if I am going in a wrong direction or is my question in above post invalid/incorrect.


Regards,
your fan.
Tom Kyte
September 16, 2008 - 1:38 pm UTC

i didn't see an example, I didn't really follow the textual description you gave.

for loop and sub query

rose, December 02, 2009 - 4:45 pm UTC

Hi Tom,
I want to ask that whether using for loop is more efficient or using sub queries
For example consider the following two cases...
1)
DELETE FROM COMP_SPEC_CHAR_VALUE WHERE COMP_SPEC_CHAR_ID IN
(SELECT COMP_SPEC_CHAR_ID FROM COMP_SPEC_CHAR WHERE COMP_SPEC_ID = compSpecId);

2)
FOR comp_spec_char_record IN (SELECT * FROM COMP_SPEC_CHAR WHERE COMP_SPEC_ID = compSpecId)
LOOP
DELETE FROM COMP_SPEC_CHAR_VALUE WHERE COMP_SPEC_CHAR_ID = comp_spec_char_record.COMP_SPEC_CHAR_ID;
END LOOP;

In the above case which is more efficient and which we should use
Tom Kyte
December 04, 2009 - 9:49 am UTC

#1 is almost always - 99.99999999999% of the time - the right approach.

search for "my mantra" on this site, it starts with:

if you can do it in a single sql statement - do it.



what if "delete from t where c in (select x from y where z = :x)" was to find 1,000 rows in the subquery and in turn each row from the subquery would find 10 rows in T (so we delete 10,000 records). Further, suppose the table itself has 20,000 records. Would you want do to that in a loop whereby you would almost surely use an inefficient index access path to find 10 rows 1,000 times? I'd rather do a nice juicy full scan.

The more information you give the optimizer (like the FULL QUERY TO DO), the better off you'll be.


subquery using delete

satin, February 01, 2010 - 9:55 am UTC


I have the following below issue need your help



select count(*) from table_1 t1 , table_2 t2
where t1.col1 = t2.col1 and t1.col2 = t2.col2 and t1.col3= t2.col3

The above query returning 100000 rows and these rows which i have delete from "table_1"


So I am writing the delete statement as follows

delete from table_1 where exists (

select 1 from table_1 t1 , table_2 t2
where t1.col1 = t2.col1 and t1.col2 = t2.col2 and t1.col3= t2.col3 ), This query is returing the 5,000,000 rows which are nothing but now rows in table_1

can you tell me how can write the above delete statement

thanks for you help


Tom Kyte
February 01, 2010 - 10:49 am UTC

delete from t1
where (col1,col2,col3) in (select t2.col1, t2.col2, t2.col3 from t2);



subquery

satin satin, February 01, 2010 - 12:09 pm UTC

I have tired this way but the count is not matching
Tom Kyte
February 01, 2010 - 12:16 pm UTC

no kidding they won't match, why should they?

Unless col1,col2,col3 is a primary/unique key in T1 - why should they match??!?!?!?!?


You cannot compare the count of a JOIN with the count of a single table with a where clause in general.


ops$tkyte%ORA10GR2> create table t1 ( col1 int, col2 int, col3 int );

Table created.

ops$tkyte%ORA10GR2> create table t2 ( col1 int, col2 int, col3 int );

Table created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> insert into t1 values ( 1,1,1 );

1 row created.

ops$tkyte%ORA10GR2> insert into t2 values ( 1,1,1 );

1 row created.

ops$tkyte%ORA10GR2> insert into t2 values ( 1,1,1 );

1 row created.

ops$tkyte%ORA10GR2> insert into t2 values ( 1,1,1 );

1 row created.

ops$tkyte%ORA10GR2> insert into t2 values ( 1,1,1 );

1 row created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> select count(*) from t1 , t2
  2  where t1.col1 = t2.col1 and t1.col2 = t2.col2 and t1.col3= t2.col3
  3  /

  COUNT(*)
----------
         4

ops$tkyte%ORA10GR2> delete from t1
  2  where (col1,col2,col3) in (select t2.col1, t2.col2, t2.col3 from t2);

1 row deleted.



make sense now????


phrase your requirment in text:


delete every row in T1 such that (c1,c2,c3) exists in T2(c1,c2,c3)

then, count (if you must, I suggest DO NOT count)

select count(*) from t1
where (c1,c2,c3) in (select c1,c2,c3 from t2);

that is semantically what you are trying to do - do not JOIN to T2, that is wrong, use T2 in the where clause to filter T1 - fine, but don't JOIN TO IT, you didn't mean to join at all.

why correlated subqueries are as old as dirt

Khalid, March 30, 2010 - 12:13 pm UTC

Dear Tom,

In your replay for this question you said that "correlated subqueries are as old as dirt" could you please explain to us why did you say that?

Regards,
Khalid.
Tom Kyte
April 05, 2010 - 10:56 am UTC

because I was responding to :

We are new to Oracle and We don't have access to New Oracle
Books.We are not clear with "How a correlated subquery
works?"


it isn't as if correlated subqueries were "new" or even remotely "fancy or specific to Oracle". They are just sql and are as old as dirt as far as sql goes.

subquery -

Troy Zeng, March 30, 2010 - 8:34 pm UTC

This is regarding execution path of SQL involving subquery. 

Are there any init parameters (or hints) that can enable Oracle to take t3 out of the sub-query in the 1st SQL and do the join in the right order like the 2nd SQL? 

Something like query rewrite. The following parameters are set (if relevant): 

query_rewrite_enabled=TRUE             
query_rewrite_integrity=enforced    

We have a SQL like the 1st SQL below. Tests have been done to show that the cartesian join generates too much data (10,000 times more than the final result). We've tried and the 2nd SQL ran 5,000 times faster than the 1st SQL. 
If possible, we don't want code change, which requires functional validation from the developer, who has already left the job. 

CREATE TABLE t1(c1 NUMBER, c2 NUMBER);
CREATE TABLE t2(c1 NUMBER);
CREATE TABLE t3(c2 NUMBER, c3 NUMBER PRIMARY KEY);
CREATE TABLE t4(c3 NUMBER);

SQL> -- 1st SQL (with subquery)
SQL> SELECT *
  2    FROM t1,
  3         t2,
  4         t4
  5   WHERE t1.c1 = t2.c1
  6     AND t1.c2 = (SELECT c2
  7                    FROM t3
  8*                  WHERE t3.c3 = t4.c3);

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 3383608335

---------------------------------------------------------------------------------------------
| Id  | Operation                    | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |              |     1 |    52 |     8  (13)| 00:00:01 |
|*  1 |  FILTER                      |              |       |       |            |          |
|   2 |   MERGE JOIN CARTESIAN       |              |     1 |    52 |     7  (15)| 00:00:01 |
|*  3 |    HASH JOIN                 |              |     1 |    39 |     5  (20)| 00:00:01 |
|   4 |     TABLE ACCESS FULL        | T1           |     1 |    26 |     2   (0)| 00:00:01 |
|   5 |     TABLE ACCESS FULL        | T2           |     1 |    13 |     2   (0)| 00:00:01 |
|   6 |    BUFFER SORT               |              |     1 |    13 |     5  (20)| 00:00:01 |
|   7 |     TABLE ACCESS FULL        | T4           |     1 |    13 |     2   (0)| 00:00:01 |
|   8 |   TABLE ACCESS BY INDEX ROWID| T3           |     1 |    26 |     1   (0)| 00:00:01 |
|*  9 |    INDEX UNIQUE SCAN         | SYS_C0011014 |     1 |       |     1   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("T1"."C2"= (SELECT "C2" FROM "T3" "T3" WHERE "T3"."C3"=:B1))
   3 - access("T1"."C1"="T2"."C1")
   9 - access("T3"."C3"=:B1)

Note
-----
   - dynamic sampling used for this statement
   - SQL plan baseline "SYS_SQL_PLAN_11c15db694987734" used for this statement


Statistics
----------------------------------------------------------
         15  recursive calls
         15  db block gets
         23  consistent gets
          0  physical reads
       9792  redo size
        434  bytes sent via SQL*Net to client
        409  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

SQL> -- 2nd SQL (without subquery, with hints)
SQL> SELECT /*+ ordered */ *
  2    FROM t1,
  3         t2,
  4         t3,
  5         t4
  6   WHERE t1.c1 = t2.c1
  7     AND t1.c2 = t3.c2
  8     AND t3.c3 = t4.c3;

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 894925296

-----------------------------------------------------------------------------
| Id  | Operation            | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |      |     1 |    78 |    10  (20)| 00:00:01 |
|*  1 |  HASH JOIN           |      |     1 |    78 |    10  (20)| 00:00:01 |
|*  2 |   HASH JOIN          |      |     1 |    65 |     7  (15)| 00:00:01 |
|*  3 |    HASH JOIN         |      |     1 |    39 |     5  (20)| 00:00:01 |
|   4 |     TABLE ACCESS FULL| T1   |     1 |    26 |     2   (0)| 00:00:01 |
|   5 |     TABLE ACCESS FULL| T2   |     1 |    13 |     2   (0)| 00:00:01 |
|   6 |    TABLE ACCESS FULL | T3   |     1 |    26 |     2   (0)| 00:00:01 |
|   7 |   TABLE ACCESS FULL  | T4   |     1 |    13 |     2   (0)| 00:00:01 |
-----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("T3"."C3"="T4"."C3")
   2 - access("T1"."C2"="T3"."C2")
   3 - access("T1"."C1"="T2"."C1")

Note
-----
   - dynamic sampling used for this statement


Statistics
----------------------------------------------------------
          6  recursive calls
          4  db block gets
          9  consistent gets
          0  physical reads
        672  redo size
        538  bytes sent via SQL*Net to client
        409  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

subquery

Troy Zeng, April 05, 2010 - 7:29 pm UTC

Hi Tom,

Thanks for the reply.

I would still consider dbms_advanced_rewrite.declare_rewrite_equivalence() as _manual_ code change because I need to decide what the new code is. It still requires functional validation. I think it would be helpful when code change is simple but troublesome, e.g., when that piece of code is everywhere. In my case, I'd rather changing the code itself rather than using declare_rewrite_equivalence(). I think the 1st and 2nd SQL are equivalent, but our process does not allow the new code to go in without reviewed by the developer. I tried comparing the result sets generated by the original and new code, but the original code does not finish running.

I am looking for automatic execution path optimization (via hints, init parameter, or automatic query rewrite - it does not require functional validation whatsoever). Is your answer No?

Thanks.
Tom Kyte
April 05, 2010 - 10:29 pm UTC

My answer is no.

And I would fail to see the difference between hinting and this query rewrite personally. The query plan is the thing that determines the answer. If you were able to hint the query to come up with the same plan as the other query - you'd need to do the same functional testing in my opinion. You've done the same thing as query rewrite did.


one more question on correlated sub query

Srini Hari, July 30, 2010 - 5:10 am UTC

Hi Tom,

I've a question on the correlated sub query.

We have the table data as below

SQL> select * from emp;

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

14 rows selected.

SQL> SELECT empno, mgr,ename, sal
  2  FROM emp outer
  3  WHERE sal >
  4    (SELECT AVG(sal)
  5     FROM emp inner
  6     WHERE inner.empno = outer.mgr);

     EMPNO        MGR ENAME             SAL
---------- ---------- ---------- ----------
      7788       7566 SCOTT            3000
      7902       7566 FORD             3000

Please let me know how the correlated subquery logic works and what would be the average salary that is produced as output in the sub query. Also how many records are eligible for selection for the output of subquery before the AVERAGE function is applied and what are they. Thanks for your time.

TIA

Srini

Tom Kyte
August 02, 2010 - 8:02 am UTC

what plan did you get for that query, that would answer your question "Please let me know how the correlated subquery logic works" physically.

Logically, it works this way:


for outer in ( select * from emp)
loop
select avg(sal) into l_avg
from emp
where empno = OUTER.MGR;

if (outer.sal > l_avg) then OUTPUT RECORD;
end loop


basically, your query says "show me everyone whose salary is greater than their managers salary" in a very obscure way.


We know that empno is a primary key for emp, so

4 (SELECT AVG(sal)
5 FROM emp inner
6 WHERE inner.empno = outer.mgr);


is the same as:

4 nvl((SELECT sal
5 FROM emp inner
6 WHERE inner.empno = outer.mgr), 0 );


Also how many
records are eligible for selection for the output of subquery before the
AVERAGE function is applied and what are they.



ALL of them are eligible and used.

correlated subquery

Srini Hari, August 03, 2010 - 3:19 am UTC

Thanks v much Tom. Even i felt obscure when i looked at the query the first time as i took it from one of the online examples for correlated subquery.

Now i understood the logic part of it.

Srini

Explaing Plan Doubt

Sarath, August 11, 2011 - 1:58 am UTC

Hi Tom,

I have a doubt which is from long time. This is a trivial example where I want to select multiple columns from the sub table using similar join condition.

select
(select prod_name from products where prod_id = sales.prod_id) names,
(select prod_subcategory from products where prod_id = sales.prod_id) descr
from sales;

When I checked the explain plan I found out that it is accessing the table products twice.

Can oracle also have an explain plan in which it will be understanding that both the subquery refers to same row and the table join has to be executed only once.

Basically I want to know whether this kind of sub-query uses some kind of caching to achieve the result set.

I checked in forums and no one have seem to be ask this question

Thanks,
Sarath

Tom Kyte
August 13, 2011 - 4:36 pm UTC

yes, we call it a JOIN.


why are you not joining?????


select x.prod_name, x.prod_subcategory from sales, products x
where x.prod_id = sales.prod_id;


??????????????????????

In this case - using your subquery, it would read products twice. There is a thing called scalar subquery caching (search this site for that) - but in general, it is going to hit it twice.

Just join.

RAJESH, December 25, 2011 - 11:41 pm UTC

1. Display the details of the manufacturers along with the certification and quality
certification type details.
1
CLM_MFTR_ID
CLM_MFTR_NME
CLM_MFTR_ABVN
CLM_MFTR_LIC_NR
CLM_MFTR_LIC_DT
CLM_MFTR_DESC
2
CLM_CRFN_ID
CLM_MFTR_ID
CLM_CRFN_TYP_ID
CLM_CRFN_DT
CLM_CRFN_DESC
CLM_CRFN_ADTR_ID


Tom Kyte
December 26, 2011 - 10:39 am UTC

ok, go ahead, I don't have any problem with you doing that.

I don't know what else to say given what you've given to me here.

I mean, seriously - no explanation (I have no idea what these details are/mean/come from. I have no idea what a certification is/means/does/comes from. I have no clue what this quality measure is/means/comes from/etc).

I see no create tables

I see no inserts into

I see nothing that explains anything here.

rowid matters

javier, January 05, 2012 - 2:29 pm UTC

Hi Tom ; 
Thanks a lot for your very useful site. I'm just trying to split a table in a recordset, for a web application, using the rowid of the table. The idea is to return "n" records every time this procedure is called. I'm performing the test and when I perform the following: 

SQL> select rownum, a.id_bitacora
  2  from ssi_bitacora a
  3  where rownum > 1;

ninguna fila seleccionada

SQL> select rownum, a.id_bitacora
  2  from ssi_bitacora a
  3  where rownum > 0;

    ROWNUM ID_BITACORA
---------- -----------
         1           1
         2           2
         3           3
         4           4
         5           5

Obiously, I have records in this table, as you can see in the second query. 

Tom Kyte
January 05, 2012 - 2:41 pm UTC

you say "rowid" in the subject

you query with "rownum" in the predicate.


rownum is a magic psuedo column (it is NOT stored with the data! it is not persistent).

Rownum is assigned to rows as they flow out of the predicate (before the order by/group by).


Here is a fact about rownum: If there isn't a first row, there cannot be a second row. If there is no "rownum=1", there cannot be a "rownum=2".

that is why "rownum > 1" can never be true - if there isn't a rownum = 1, there cannot be a rownum > 1.


Think of it this way:

select rownum, t.* from t where rownum > 1;

is processed like this (in concept)

rownum = 1
for x in ( select * from t )
loop
   if ( rownum = 1 )
   then
      output row
      rownum = rownum + 1;
   end if;
end loop;


that logic would never produce a row.



If you want to get rows "M through N" from a result set, you can use rownum - or more easily ROW_NUMBER().


select * 
  from (select t.*, row_number() over (order by something) rn
          from t
        )
where rn between :m and :n;


Using rownum it would look like this


select * 
  from (select x.*, rownum rnum
          from (select * from t order by something) X
         where rownum <= :n)
 where rnum >= :m;




You have to order by something deterministic (unique) - otherwise - every time you call that query it could return a different set of rows for the same M-N range! (think about why this is true!!)

See:

http://www.oracle.com/technetwork/issue-archive/2007/07-jan/o17asktom-093877.html

for the answer to that puzzle and a lot more information on this type of query

thanks a lot

javier, January 05, 2012 - 3:11 pm UTC

thanks a lot for your quick and useful response; I' reading the article provided and it is exactly what I need.

Extract queries having subqueries

Ahmad Al-Sallal, January 07, 2013 - 12:00 am UTC

Hi Tom,
I need to review all SPs that have a (correlated subquery) to switch them to JOIN --if i could--
My Question: how can i extract all queries that have a correlated subqueries from USER_SOURCE table?

Tom Kyte
January 14, 2013 - 11:21 am UTC

there is no magic for this, sorry. You'd have to write a piece of code to do this.


I would say to you

DO NOT DO THIS.


we already do that rewrite when it makes sense.

ops$tkyte%ORA11GR2> create table t1 ( x int primary key, y int, z int );

Table created.

ops$tkyte%ORA11GR2> create table t2 ( x int primary key, y int, z int );

Table created.

ops$tkyte%ORA11GR2> 
ops$tkyte%ORA11GR2> 
ops$tkyte%ORA11GR2> exec dbms_stats.set_table_stats( user, 'T1', numrows => 1000000, numblks => 100000 );

PL/SQL procedure successfully completed.

ops$tkyte%ORA11GR2> exec dbms_stats.set_table_stats( user, 'T2', numrows => 10, numblks => 1 );

PL/SQL procedure successfully completed.

ops$tkyte%ORA11GR2> 
ops$tkyte%ORA11GR2> set autotrace traceonly explain
ops$tkyte%ORA11GR2> select * from t2 where exists ( select null from t1 where t1.x = t2.x );

Execution Plan
----------------------------------------------------------
Plan hash value: 3586655764

-----------------------------------------------------------------------------------
| Id  | Operation          | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |              |    10 |   520 |     2   (0)| 00:00:01 |
|   1 |  NESTED LOOPS SEMI |              |    10 |   520 |     2   (0)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| T2           |    10 |   390 |     2   (0)| 00:00:01 |
|*  3 |   INDEX UNIQUE SCAN| SYS_C0045878 |  1000K|    12M|     0   (0)| 00:00:01 |
-----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - access("T1"."X"="T2"."X")

ops$tkyte%ORA11GR2> select * from t1 where exists ( select null from t2 where t1.x = t2.x );

Execution Plan
----------------------------------------------------------
Plan hash value: 2782716045

---------------------------------------------------------------------------------------------
| Id  | Operation                    | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |              |   100K|  5078K|     4  (25)| 00:00:01 |
|   1 |  NESTED LOOPS                |              |       |       |            |          |
|   2 |   NESTED LOOPS               |              |   100K|  5078K|     4  (25)| 00:00:01 |
|   3 |    SORT UNIQUE               |              |    10 |   130 |     2   (0)| 00:00:01 |
|   4 |     TABLE ACCESS FULL        | T2           |    10 |   130 |     2   (0)| 00:00:01 |
|*  5 |    INDEX UNIQUE SCAN         | SYS_C0045878 |     1 |       |     0   (0)| 00:00:01 |
|   6 |   TABLE ACCESS BY INDEX ROWID| T1           |   100K|  3808K|     1   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   5 - access("T1"."X"="T2"."X")

ops$tkyte%ORA11GR2> set autotrace off



joins - viola - already done for you.

the optimizer does this, you do not have to.