<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:georss="http://www.georss.org/georss" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:media="http://search.yahoo.com/mrss/"
		>
<channel>
	<title>Comments on: Different Execution Plans when Converting SQL from SELECT to UPDATE &#8230; IN SELECT, ROWNUM Related Bug in 11.2.0.2</title>
	<atom:link href="http://hoopercharles.wordpress.com/2012/06/28/different-execution-plans-when-converting-sql-from-select-to-update-in-select-rownum-related-bug-in-11-2-0-2/feed/" rel="self" type="application/rss+xml" />
	<link>http://hoopercharles.wordpress.com/2012/06/28/different-execution-plans-when-converting-sql-from-select-to-update-in-select-rownum-related-bug-in-11-2-0-2/</link>
	<description>Miscellaneous Random Oracle Topics: Stop, Think, ... Understand</description>
	<lastBuildDate>Mon, 13 May 2013 14:10:06 +0000</lastBuildDate>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.com/</generator>
	<item>
		<title>By: Charles Hooper</title>
		<link>http://hoopercharles.wordpress.com/2012/06/28/different-execution-plans-when-converting-sql-from-select-to-update-in-select-rownum-related-bug-in-11-2-0-2/#comment-4789</link>
		<dc:creator><![CDATA[Charles Hooper]]></dc:creator>
		<pubDate>Tue, 03 Jul 2012 12:09:36 +0000</pubDate>
		<guid isPermaLink="false">http://hoopercharles.wordpress.com/?p=6416#comment-4789</guid>
		<description><![CDATA[Narendra,

The OTN forums crashed as I tried to post the following response (sorry to all of the users of the OTN forums), so I thought that I would post it here.

Your analysis of an increasing number of rows needing to be fetched from the DTL table for each execution of the UPDATE statement is correct (I think that I mentioned this issue early in this thread).  So, what is happening?

First, let&#039;s make it easy for people to create your latest version of the test case tables:
&lt;pre&gt;
drop table dtl purge ;
drop table hdr purge ;
create table hdr (hdrid number(10), hdr_ind number(2) not null, hpad varchar2(100), constraint hdr_pk primary key (hdrid)) ;
create table dtl (dtlid number(10), process_ind varchar2(10) not null, process_date date not null, hdrid number(10), type_id number(2), cat_id number(2), dpad varchar2(100), 

constraint dtl_pk primary key (dtlid)) ;
create index dtl_idx1 on dtl(process_ind, process_date) nologging ;
create index dtl_idx2 on dtl(process_ind, decode(type_id, 1, 99, type_id), process_date) nologging ;
insert into hdr select level, case when mod(level, 3) = 0 then 0 when mod(level, 7) = 0 then 2 else 6 end, dbms_random.string(&#039;a&#039;, 100) from dual connect by level &lt;= 10000 ;
commit ;
drop sequence dtl_seq ;
create sequence dtl_seq cache 1000 ;
insert into dtl select dtl_seq.nextval, &#039;NEW&#039;, sysdate, case mod(rownum, 500) when 0 then null else hdrid end, case mod(rownum, 100) when 0 then 2 else 1 end, 1, 

dbms_random.string(&#039;a&#039;, 10) from (select hdrid from hdr where rownum &lt;= 3000),(select level from dual connect by level &lt;= 2) ;
commit ;
 
begin
for i in (select level rn from dual connect by level &lt;= 20 order by dbms_random.random)
loop
insert into dtl select dtl_seq.nextval, &#039;NEW&#039;, (sysdate + i.rn), case mod(rownum, 500) when 0 then null else hdrid end, case mod(rownum, 100) when 0 then 2 else 1 end, 1, 

dbms_random.string(&#039;a&#039;, 10) from (select hdrid from hdr where rownum &lt;= 5000),(select level from dual connect by level &lt;= 20) ;
commit;
end loop;
end;
/
 
exec dbms_stats.gather_table_stats(user, &#039;DTL&#039;, cascade=&gt;true);
exec dbms_stats.gather_table_stats(user, &#039;HDR&#039;, cascade=&gt;true);
&lt;/pre&gt;

With those tables freshly created, we can start experimenting.  What if we execute the update statement 20 times, outputting the execution plan each time, and executing the following SQL statement before the first update statement, and then after every 5 executions of the update statement:
&lt;pre&gt;
select /*+ gather_plan_statistics */
  V.*,
  COUNT(decode(INCLUDE,&#039;Y&#039;,&#039;Y&#039;,NULL)) OVER (ORDER BY ROWNUM) C,
  ROWNUM RN
from
(    select /*+ NO_EXPAND LEADING(dtl hdr) USE_NL(hdr) INDEX(dtl DTL_IDX2) */
 dtl.dtlid,
  hdr_ind,
  dtl.hdrid,
  dtl.cat_id,
  dtl.type_id,
  DTL.process_date,
  decode(dtl.hdrid,null,&#039;Y&#039;,decode(hdr.hdr_ind,0,&#039;Y&#039;,2,&#039;Y&#039;,&#039;N&#039;)) INCLUDE
    from dtl,
         hdr
    where dtl.hdrid = hdr.hdrid(+)
          and dtl.process_date &lt; sysdate
          and dtl.process_ind = &#039;NEW&#039;
          AND dtl.cat_id = 1
    order by decode(dtl.type_id, 1, 99, dtl.type_id),dtl.process_date) V
where
  rownum&lt;=2800;
&lt;/pre&gt;
The above output will show you the rows that are making it out of the DTL table, which are then used to probe the primary key index on the HDR table.  The C column indicates the number of rows that match the WHERE clause in your UPDATE statement, after those rows are pulled from the DTL table (once the C column reaches 100, the Oracle runtime engine is able to short-circuit the execution plan).  The RN column provides a running counter of the number of rows from the DTL table.

Before the first update, notice that the first row that will be returned (based on your WHERE clause) is the first row printed, and that the 100th row that will be returned is the 211th row that is printed:
&lt;pre&gt;
     DTLID    HDR_IND      HDRID     CAT_ID    TYPE_ID PROCESS_D I          C         RN
---------- ---------- ---------- ---------- ---------- --------- - ---------- ----------
       600          0        600          1          2 03-JUL-12 Y          1          1
       700          2        700          1          2 03-JUL-12 Y          2          2
       800          6        800          1          2 03-JUL-12 N          2          3
       100          6        100          1          2 03-JUL-12 N          2          4
       200          6        200          1          2 03-JUL-12 N          2          5
       300          0        300          1          2 03-JUL-12 Y          3          6
       400          6        400          1          2 03-JUL-12 N          3          7
...
       669          0        669          1          1 03-JUL-12 Y         98        205
       670          6        670          1          1 03-JUL-12 N         98        206
       671          6        671          1          1 03-JUL-12 N         98        207
       672          0        672          1          1 03-JUL-12 Y         99        208
       673          6        673          1          1 03-JUL-12 N         99        209
       674          6        674          1          1 03-JUL-12 N         99        210
       675          0        675          1          1 03-JUL-12 Y        100        211
&lt;/pre&gt;

The execution plan, notice the A-Rows value for the DTL table is 211, the same value as the RN value above.
&lt;pre&gt;
SQL_ID  a2g3h48p54pa2, child number 0
-------------------------------------
update /*+ gather_plan_statistics */ dtl set process_ind = &#039;PROCESSED&#039;
where dtlid in (   select dtlid   from (     select /*+ NO_EXPAND
LEADING(dtl hdr) USE_NL(hdr) INDEX(dtl DTL_IDX2) */ dtl.dtlid     from
dtl,          hdr     where dtl.hdrid = hdr.hdrid(+)           and
dtl.process_date &lt; sysdate           and dtl.process_ind = &#039;NEW&#039;                        
   and dtl.cat_id = 1           and (                (hdr_ind in (0, 2)                 
and dtl.hdrid IS NOT NULL) OR (dtl.hdrid IS NULL)               )                       
order by decode(dtl.type_id, 1, 99, dtl.type_id),dtl.process_date                       
      )     where rownum &lt;= 100)    
 
Plan hash value: 3052733514         
 
-------------------------------------------------------------------------------------------------------------------------------------       
&#124; Id  &#124; Operation                           &#124; Name     &#124; Starts &#124; E-Rows &#124; A-Rows &#124;   A-Time   &#124; Buffers &#124;  OMem &#124;  1Mem &#124; Used-Mem &#124;       
-------------------------------------------------------------------------------------------------------------------------------------       
&#124;   0 &#124; UPDATE STATEMENT                    &#124;          &#124;      1 &#124;        &#124;      0 &#124;00:00:00.01 &#124;    1863 &#124;       &#124;       &#124;          &#124;       
&#124;   1 &#124;  UPDATE                             &#124; DTL      &#124;      1 &#124;        &#124;      0 &#124;00:00:00.01 &#124;    1863 &#124;       &#124;       &#124;          &#124;       
&#124;   2 &#124;   NESTED LOOPS                      &#124;          &#124;      1 &#124;    100 &#124;    100 &#124;00:00:00.01 &#124;     502 &#124;       &#124;       &#124;          &#124;       
&#124;   3 &#124;    VIEW                             &#124; VW_NSO_1 &#124;      1 &#124;    100 &#124;    100 &#124;00:00:00.01 &#124;     415 &#124;       &#124;       &#124;          &#124;       
&#124;   4 &#124;     SORT UNIQUE                     &#124;          &#124;      1 &#124;    100 &#124;    100 &#124;00:00:00.01 &#124;     415 &#124; 73728 &#124; 73728 &#124;          &#124;       
&#124;*  5 &#124;      COUNT STOPKEY                  &#124;          &#124;      1 &#124;        &#124;    100 &#124;00:00:00.01 &#124;     415 &#124;       &#124;       &#124;          &#124;       
&#124;   6 &#124;       VIEW                          &#124;          &#124;      1 &#124;  63619 &#124;    100 &#124;00:00:00.01 &#124;     415 &#124;       &#124;       &#124;          &#124;       
&#124;*  7 &#124;        FILTER                       &#124;          &#124;      1 &#124;        &#124;    100 &#124;00:00:00.01 &#124;     415 &#124;       &#124;       &#124;          &#124;       
&#124;   8 &#124;         NESTED LOOPS OUTER          &#124;          &#124;      1 &#124;  63619 &#124;    211 &#124;00:00:00.01 &#124;     415 &#124;       &#124;       &#124;          &#124;       
&#124;*  9 &#124;          TABLE ACCESS BY INDEX ROWID&#124; DTL      &#124;      1 &#124;  95524 &#124;    211 &#124;00:00:00.01 &#124;     152 &#124;       &#124;       &#124;          &#124;       
&#124;* 10 &#124;           INDEX RANGE SCAN          &#124; DTL_IDX2 &#124;      1 &#124;  95524 &#124;    211 &#124;00:00:00.01 &#124;     116 &#124;       &#124;       &#124;          &#124;       
&#124;  11 &#124;          TABLE ACCESS BY INDEX ROWID&#124; HDR      &#124;    211 &#124;      1 &#124;    199 &#124;00:00:00.01 &#124;     263 &#124;       &#124;       &#124;          &#124;       
&#124;* 12 &#124;           INDEX UNIQUE SCAN         &#124; HDR_PK   &#124;    211 &#124;      1 &#124;    199 &#124;00:00:00.01 &#124;      64 &#124;       &#124;       &#124;          &#124;       
&#124;* 13 &#124;    INDEX UNIQUE SCAN                &#124; DTL_PK   &#124;    100 &#124;      1 &#124;    100 &#124;00:00:00.01 &#124;      87 &#124;       &#124;       &#124;          &#124;       
-------------------------------------------------------------------------------------------------------------------------------------       
 
Predicate Information (identified by operation id):
---------------------------------------------------
   5 - filter(ROWNUM&lt;=100)          
   7 - filter(((INTERNAL_FUNCTION(&quot;HDR_IND&quot;) AND &quot;DTL&quot;.&quot;HDRID&quot; IS NOT NULL) OR &quot;DTL&quot;.&quot;HDRID&quot; IS NULL)) 
   9 - filter(&quot;DTL&quot;.&quot;CAT_ID&quot;=1)     
  10 - access(&quot;DTL&quot;.&quot;PROCESS_IND&quot;=&#039;NEW&#039; AND &quot;DTL&quot;.&quot;PROCESS_DATE&quot;&lt;SYSDATE@!)             
       filter(&quot;DTL&quot;.&quot;PROCESS_DATE&quot;&lt;SYSDATE@!)      
  12 - access(&quot;DTL&quot;.&quot;HDRID&quot;=&quot;HDR&quot;.&quot;HDRID&quot;)         
  13 - access(&quot;DTLID&quot;=&quot;DTLID&quot;)      
&lt;/pre&gt;

Before the sixth execution - notice that the C column does not advance from 0 until the 645th row, and then runs for about 230 rows until hitting the 100th row that will be returned:
&lt;pre&gt;
     DTLID    HDR_IND      HDRID     CAT_ID    TYPE_ID PROCESS_D I          C         RN
---------- ---------- ---------- ---------- ---------- --------- - ---------- ----------
      1093          6       1093          1          1 03-JUL-12 N          0        643
      1094          6       1094          1          1 03-JUL-12 N          0        644
      1095          0       1095          1          1 03-JUL-12 Y          1        645
      1096          6       1096          1          1 03-JUL-12 N          1        646
      1097          6       1097          1          1 03-JUL-12 N          1        647
      1098          0       1098          1          1 03-JUL-12 Y          2        648
      1099          2       1099          1          1 03-JUL-12 Y          3        649
      1101          0       1101          1          1 03-JUL-12 Y          4        650
...
      1840          0       1269          1          1 03-JUL-12 Y         98        868
      1841          6       1270          1          1 03-JUL-12 N         98        869
      1842          6       1271          1          1 03-JUL-12 N         98        870
      1843          0       1272          1          1 03-JUL-12 Y         99        871
      1844          6       1273          1          1 03-JUL-12 N         99        872
      1845          2       1274          1          1 03-JUL-12 Y        100        873
&lt;/pre&gt;

The execution plan for the 6th execution of the update statement, notice that the DTL table is returning 873 rows, just as the above predicted:
&lt;pre&gt;
-------------------------------------------------------------------------------------------------------------------------------------       
&#124; Id  &#124; Operation                           &#124; Name     &#124; Starts &#124; E-Rows &#124; A-Rows &#124;   A-Time   &#124; Buffers &#124;  OMem &#124;  1Mem &#124; Used-Mem &#124;       
-------------------------------------------------------------------------------------------------------------------------------------       
&#124;   0 &#124; UPDATE STATEMENT                    &#124;          &#124;      1 &#124;        &#124;      0 &#124;00:00:00.01 &#124;    2482 &#124;       &#124;       &#124;          &#124;       
&#124;   1 &#124;  UPDATE                             &#124; DTL      &#124;      1 &#124;        &#124;      0 &#124;00:00:00.01 &#124;    2482 &#124;       &#124;       &#124;          &#124;       
&#124;   2 &#124;   NESTED LOOPS                      &#124;          &#124;      1 &#124;    100 &#124;    100 &#124;00:00:00.01 &#124;    1134 &#124;       &#124;       &#124;          &#124;       
&#124;   3 &#124;    VIEW                             &#124; VW_NSO_1 &#124;      1 &#124;    100 &#124;    100 &#124;00:00:00.01 &#124;    1096 &#124;       &#124;       &#124;          &#124;       
&#124;   4 &#124;     SORT UNIQUE                     &#124;          &#124;      1 &#124;    100 &#124;    100 &#124;00:00:00.01 &#124;    1096 &#124; 73728 &#124; 73728 &#124;          &#124;       
&#124;*  5 &#124;      COUNT STOPKEY                  &#124;          &#124;      1 &#124;        &#124;    100 &#124;00:00:00.01 &#124;    1096 &#124;       &#124;       &#124;          &#124;       
&#124;   6 &#124;       VIEW                          &#124;          &#124;      1 &#124;  63619 &#124;    100 &#124;00:00:00.01 &#124;    1096 &#124;       &#124;       &#124;          &#124;       
&#124;*  7 &#124;        FILTER                       &#124;          &#124;      1 &#124;        &#124;    100 &#124;00:00:00.01 &#124;    1096 &#124;       &#124;       &#124;          &#124;       
&#124;   8 &#124;         NESTED LOOPS OUTER          &#124;          &#124;      1 &#124;  63619 &#124;    873 &#124;00:00:00.01 &#124;    1096 &#124;       &#124;       &#124;          &#124;       
&#124;*  9 &#124;          TABLE ACCESS BY INDEX ROWID&#124; DTL      &#124;      1 &#124;  95524 &#124;    873 &#124;00:00:00.01 &#124;     155 &#124;       &#124;       &#124;          &#124;       
&#124;* 10 &#124;           INDEX RANGE SCAN          &#124; DTL_IDX2 &#124;      1 &#124;  95524 &#124;    873 &#124;00:00:00.01 &#124;     123 &#124;       &#124;       &#124;          &#124;       
&#124;  11 &#124;          TABLE ACCESS BY INDEX ROWID&#124; HDR      &#124;    873 &#124;      1 &#124;    873 &#124;00:00:00.01 &#124;     941 &#124;       &#124;       &#124;          &#124;       
&#124;* 12 &#124;           INDEX UNIQUE SCAN         &#124; HDR_PK   &#124;    873 &#124;      1 &#124;    873 &#124;00:00:00.01 &#124;      68 &#124;       &#124;       &#124;          &#124;       
&#124;* 13 &#124;    INDEX UNIQUE SCAN                &#124; DTL_PK   &#124;    100 &#124;      1 &#124;    100 &#124;00:00:00.01 &#124;      38 &#124;       &#124;       &#124;          &#124;       
-------------------------------------------------------------------------------------------------------------------------------------       
 
Predicate Information (identified by operation id):
---------------------------------------------------
   5 - filter(ROWNUM&lt;=100)          
   7 - filter(((INTERNAL_FUNCTION(&quot;HDR_IND&quot;) AND &quot;DTL&quot;.&quot;HDRID&quot; IS NOT NULL) OR &quot;DTL&quot;.&quot;HDRID&quot; IS NULL)) 
   9 - filter(&quot;DTL&quot;.&quot;CAT_ID&quot;=1)     
  10 - access(&quot;DTL&quot;.&quot;PROCESS_IND&quot;=&#039;NEW&#039; AND &quot;DTL&quot;.&quot;PROCESS_DATE&quot;&lt;SYSDATE@!)             
       filter(&quot;DTL&quot;.&quot;PROCESS_DATE&quot;&lt;SYSDATE@!)      
  12 - access(&quot;DTL&quot;.&quot;HDRID&quot;=&quot;HDR&quot;.&quot;HDRID&quot;)         
  13 - access(&quot;DTLID&quot;=&quot;DTLID&quot;)      
&lt;/pre&gt;

Just before the 11th update, the first row returned will begin with the 1312th row printed, and end with the 1544th row printed (again running for about 230 rows once the first usable row is found):
&lt;pre&gt;
     DTLID    HDR_IND      HDRID     CAT_ID    TYPE_ID PROCESS_D I          C         RN
---------- ---------- ---------- ---------- ---------- --------- - ---------- ----------
      2791          6       2791          1          1 03-JUL-12 N          0       1310
      2792          6       2792          1          1 03-JUL-12 N          0       1311
      2793          0       2793          1          1 03-JUL-12 Y          1       1312
      2794          6       2794          1          1 03-JUL-12 N          1       1313
      2795          6       2795          1          1 03-JUL-12 N          1       1314
      2796          0       2796          1          1 03-JUL-12 Y          2       1315
      2797          6       2797          1          1 03-JUL-12 N          2       1316
      2798          6       2798          1          1 03-JUL-12 N          2       1317
...
      3024          0         24          1          1 03-JUL-12 Y         98       1540
      3025          6         25          1          1 03-JUL-12 N         98       1541
      3026          6         26          1          1 03-JUL-12 N         98       1542
      3027          0         27          1          1 03-JUL-12 Y         99       1543
      3028          2         28          1          1 03-JUL-12 Y        100       1544
&lt;/pre&gt;

You can probably guess what the execution plan looks like at this point:
&lt;pre&gt;
SQL_ID  a2g3h48p54pa2, child number 0              
-------------------------------------              
update /*+ gather_plan_statistics */ dtl set process_ind = &#039;PROCESSED&#039;                  
where dtlid in (   select dtlid   from (     select /*+ NO_EXPAND                       
LEADING(dtl hdr) USE_NL(hdr) INDEX(dtl DTL_IDX2) */ dtl.dtlid     from                  
dtl,          hdr     where dtl.hdrid = hdr.hdrid(+)           and                      
dtl.process_date &lt; sysdate           and dtl.process_ind = &#039;NEW&#039;                        
   and dtl.cat_id = 1           and (                (hdr_ind in (0, 2)                 
and dtl.hdrid IS NOT NULL) OR (dtl.hdrid IS NULL)               )                       
order by decode(dtl.type_id, 1, 99, dtl.type_id),dtl.process_date                       
      )     where rownum &lt;= 100)    
 
Plan hash value: 3052733514         
 
-------------------------------------------------------------------------------------------------------------------------------------       
&#124; Id  &#124; Operation                           &#124; Name     &#124; Starts &#124; E-Rows &#124; A-Rows &#124;   A-Time   &#124; Buffers &#124;  OMem &#124;  1Mem &#124; Used-Mem &#124;       
-------------------------------------------------------------------------------------------------------------------------------------       
&#124;   0 &#124; UPDATE STATEMENT                    &#124;          &#124;      1 &#124;        &#124;      0 &#124;00:00:00.01 &#124;    3172 &#124;       &#124;       &#124;          &#124;       
&#124;   1 &#124;  UPDATE                             &#124; DTL      &#124;      1 &#124;        &#124;      0 &#124;00:00:00.01 &#124;    3172 &#124;       &#124;       &#124;          &#124;       
&#124;   2 &#124;   NESTED LOOPS                      &#124;          &#124;      1 &#124;    100 &#124;    100 &#124;00:00:00.01 &#124;    1841 &#124;       &#124;       &#124;          &#124;       
&#124;   3 &#124;    VIEW                             &#124; VW_NSO_1 &#124;      1 &#124;    100 &#124;    100 &#124;00:00:00.01 &#124;    1815 &#124;       &#124;       &#124;          &#124;       
&#124;   4 &#124;     SORT UNIQUE                     &#124;          &#124;      1 &#124;    100 &#124;    100 &#124;00:00:00.01 &#124;    1815 &#124; 73728 &#124; 73728 &#124;          &#124;       
&#124;*  5 &#124;      COUNT STOPKEY                  &#124;          &#124;      1 &#124;        &#124;    100 &#124;00:00:00.01 &#124;    1815 &#124;       &#124;       &#124;          &#124;       
&#124;   6 &#124;       VIEW                          &#124;          &#124;      1 &#124;  63619 &#124;    100 &#124;00:00:00.01 &#124;    1815 &#124;       &#124;       &#124;          &#124;       
&#124;*  7 &#124;        FILTER                       &#124;          &#124;      1 &#124;        &#124;    100 &#124;00:00:00.01 &#124;    1815 &#124;       &#124;       &#124;          &#124;       
&#124;   8 &#124;         NESTED LOOPS OUTER          &#124;          &#124;      1 &#124;  63619 &#124;   1544 &#124;00:00:00.01 &#124;    1815 &#124;       &#124;       &#124;          &#124;       
&#124;*  9 &#124;          TABLE ACCESS BY INDEX ROWID&#124; DTL      &#124;      1 &#124;  95524 &#124;   1544 &#124;00:00:00.01 &#124;     168 &#124;       &#124;       &#124;          &#124;       
&#124;* 10 &#124;           INDEX RANGE SCAN          &#124; DTL_IDX2 &#124;      1 &#124;  95524 &#124;   1544 &#124;00:00:00.01 &#124;     129 &#124;       &#124;       &#124;          &#124;       
&#124;  11 &#124;          TABLE ACCESS BY INDEX ROWID&#124; HDR      &#124;   1544 &#124;      1 &#124;   1544 &#124;00:00:00.01 &#124;    1647 &#124;       &#124;       &#124;          &#124;       
&#124;* 12 &#124;           INDEX UNIQUE SCAN         &#124; HDR_PK   &#124;   1544 &#124;      1 &#124;   1544 &#124;00:00:00.01 &#124;     103 &#124;       &#124;       &#124;          &#124;       
&#124;* 13 &#124;    INDEX UNIQUE SCAN                &#124; DTL_PK   &#124;    100 &#124;      1 &#124;    100 &#124;00:00:00.01 &#124;      26 &#124;       &#124;       &#124;          &#124;       
-------------------------------------------------------------------------------------------------------------------------------------       
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   5 - filter(ROWNUM&lt;=100)          
   7 - filter(((INTERNAL_FUNCTION(&quot;HDR_IND&quot;) AND &quot;DTL&quot;.&quot;HDRID&quot; IS NOT NULL) OR &quot;DTL&quot;.&quot;HDRID&quot; IS NULL)) 
   9 - filter(&quot;DTL&quot;.&quot;CAT_ID&quot;=1)     
  10 - access(&quot;DTL&quot;.&quot;PROCESS_IND&quot;=&#039;NEW&#039; AND &quot;DTL&quot;.&quot;PROCESS_DATE&quot;&lt;SYSDATE@!)             
       filter(&quot;DTL&quot;.&quot;PROCESS_DATE&quot;&lt;SYSDATE@!)      
  12 - access(&quot;DTL&quot;.&quot;HDRID&quot;=&quot;HDR&quot;.&quot;HDRID&quot;)         
  13 - access(&quot;DTLID&quot;=&quot;DTLID&quot;)      
&lt;/pre&gt;

The execution plan for the 20th update:
&lt;pre&gt;
-------------------------------------------------------------------------------------------------------------------------------------       
&#124; Id  &#124; Operation                           &#124; Name     &#124; Starts &#124; E-Rows &#124; A-Rows &#124;   A-Time   &#124; Buffers &#124;  OMem &#124;  1Mem &#124; Used-Mem &#124;       
-------------------------------------------------------------------------------------------------------------------------------------       
&#124;   0 &#124; UPDATE STATEMENT                    &#124;          &#124;      1 &#124;        &#124;      0 &#124;00:00:00.01 &#124;    4468 &#124;       &#124;       &#124;          &#124;       
&#124;   1 &#124;  UPDATE                             &#124; DTL      &#124;      1 &#124;        &#124;      0 &#124;00:00:00.01 &#124;    4468 &#124;       &#124;       &#124;          &#124;       
&#124;   2 &#124;   NESTED LOOPS                      &#124;          &#124;      1 &#124;    100 &#124;    100 &#124;00:00:00.01 &#124;    3120 &#124;       &#124;       &#124;          &#124;       
&#124;   3 &#124;    VIEW                             &#124; VW_NSO_1 &#124;      1 &#124;    100 &#124;    100 &#124;00:00:00.01 &#124;    3094 &#124;       &#124;       &#124;          &#124;       
&#124;   4 &#124;     SORT UNIQUE                     &#124;          &#124;      1 &#124;    100 &#124;    100 &#124;00:00:00.01 &#124;    3094 &#124; 73728 &#124; 73728 &#124;          &#124;       
&#124;*  5 &#124;      COUNT STOPKEY                  &#124;          &#124;      1 &#124;        &#124;    100 &#124;00:00:00.01 &#124;    3094 &#124;       &#124;       &#124;          &#124;       
&#124;   6 &#124;       VIEW                          &#124;          &#124;      1 &#124;  63619 &#124;    100 &#124;00:00:00.01 &#124;    3094 &#124;       &#124;       &#124;          &#124;       
&#124;*  7 &#124;        FILTER                       &#124;          &#124;      1 &#124;        &#124;    100 &#124;00:00:00.01 &#124;    3094 &#124;       &#124;       &#124;          &#124;       
&#124;   8 &#124;         NESTED LOOPS OUTER          &#124;          &#124;      1 &#124;  63619 &#124;   2743 &#124;00:00:00.01 &#124;    3094 &#124;       &#124;       &#124;          &#124;       
&#124;*  9 &#124;          TABLE ACCESS BY INDEX ROWID&#124; DTL      &#124;      1 &#124;  95524 &#124;   2743 &#124;00:00:00.01 &#124;     192 &#124;       &#124;       &#124;          &#124;       
&#124;* 10 &#124;           INDEX RANGE SCAN          &#124; DTL_IDX2 &#124;      1 &#124;  95524 &#124;   2743 &#124;00:00:00.01 &#124;     141 &#124;       &#124;       &#124;          &#124;       
&#124;  11 &#124;          TABLE ACCESS BY INDEX ROWID&#124; HDR      &#124;   2743 &#124;      1 &#124;   2743 &#124;00:00:00.01 &#124;    2902 &#124;       &#124;       &#124;          &#124;       
&#124;* 12 &#124;           INDEX UNIQUE SCAN         &#124; HDR_PK   &#124;   2743 &#124;      1 &#124;   2743 &#124;00:00:00.01 &#124;     159 &#124;       &#124;       &#124;          &#124;       
&#124;* 13 &#124;    INDEX UNIQUE SCAN                &#124; DTL_PK   &#124;    100 &#124;      1 &#124;    100 &#124;00:00:00.01 &#124;      26 &#124;       &#124;       &#124;          &#124;       
-------------------------------------------------------------------------------------------------------------------------------------       
 
Predicate Information (identified by operation id):
---------------------------------------------------
   5 - filter(ROWNUM&lt;=100)          
   7 - filter(((INTERNAL_FUNCTION(&quot;HDR_IND&quot;) AND &quot;DTL&quot;.&quot;HDRID&quot; IS NOT NULL) OR &quot;DTL&quot;.&quot;HDRID&quot; IS NULL)) 
   9 - filter(&quot;DTL&quot;.&quot;CAT_ID&quot;=1)     
  10 - access(&quot;DTL&quot;.&quot;PROCESS_IND&quot;=&#039;NEW&#039; AND &quot;DTL&quot;.&quot;PROCESS_DATE&quot;&lt;SYSDATE@!)             
       filter(&quot;DTL&quot;.&quot;PROCESS_DATE&quot;&lt;SYSDATE@!)      
  12 - access(&quot;DTL&quot;.&quot;HDRID&quot;=&quot;HDR&quot;.&quot;HDRID&quot;)         
  13 - access(&quot;DTLID&quot;=&quot;DTLID&quot;) 
&lt;/pre&gt;

After the 20th update, the 21st update would need to go well beyond the 2800th row printed (probably close to the 2900th row):
&lt;pre&gt;
     DTLID    HDR_IND      HDRID     CAT_ID    TYPE_ID PROCESS_D I          C         RN
---------- ---------- ---------- ---------- ---------- --------- - ---------- ----------
      4109          6       1109          1          1 03-JUL-12 N          0       2643
      4111          6       1111          1          1 03-JUL-12 N          0       2644
      4112          6       1112          1          1 03-JUL-12 N          0       2645
      4113          0       1113          1          1 03-JUL-12 Y          1       2646
      4114          6       1114          1          1 03-JUL-12 N          1       2647
      4115          6       1115          1          1 03-JUL-12 N          1       2648
      4116          0       1116          1          1 03-JUL-12 Y          2       2649
      4117          6       1117          1          1 03-JUL-12 N          2       2650
      4118          6       1118          1          1 03-JUL-12 N          2       2651
...
      4266          6       1837          1          1 03-JUL-12 N         66       2798
      4267          6       1838          1          1 03-JUL-12 N         66       2799
      4268          0       1839          1          1 03-JUL-12 Y         67       2800
...
&lt;/pre&gt;

Now, I think you know what you need to do to optimize this SQL statement, if your test case correctly replicates the product environment (if it is not obvious yet, look at the output of the SELECT SQL statement that returns 2800 rows, after the 20th update).]]></description>
		<content:encoded><![CDATA[<p>Narendra,</p>
<p>The OTN forums crashed as I tried to post the following response (sorry to all of the users of the OTN forums), so I thought that I would post it here.</p>
<p>Your analysis of an increasing number of rows needing to be fetched from the DTL table for each execution of the UPDATE statement is correct (I think that I mentioned this issue early in this thread).  So, what is happening?</p>
<p>First, let&#8217;s make it easy for people to create your latest version of the test case tables:</p>
<pre>
drop table dtl purge ;
drop table hdr purge ;
create table hdr (hdrid number(10), hdr_ind number(2) not null, hpad varchar2(100), constraint hdr_pk primary key (hdrid)) ;
create table dtl (dtlid number(10), process_ind varchar2(10) not null, process_date date not null, hdrid number(10), type_id number(2), cat_id number(2), dpad varchar2(100), 

constraint dtl_pk primary key (dtlid)) ;
create index dtl_idx1 on dtl(process_ind, process_date) nologging ;
create index dtl_idx2 on dtl(process_ind, decode(type_id, 1, 99, type_id), process_date) nologging ;
insert into hdr select level, case when mod(level, 3) = 0 then 0 when mod(level, 7) = 0 then 2 else 6 end, dbms_random.string('a', 100) from dual connect by level &lt;= 10000 ;
commit ;
drop sequence dtl_seq ;
create sequence dtl_seq cache 1000 ;
insert into dtl select dtl_seq.nextval, 'NEW', sysdate, case mod(rownum, 500) when 0 then null else hdrid end, case mod(rownum, 100) when 0 then 2 else 1 end, 1, 

dbms_random.string('a', 10) from (select hdrid from hdr where rownum &lt;= 3000),(select level from dual connect by level &lt;= 2) ;
commit ;
 
begin
for i in (select level rn from dual connect by level &lt;= 20 order by dbms_random.random)
loop
insert into dtl select dtl_seq.nextval, 'NEW', (sysdate + i.rn), case mod(rownum, 500) when 0 then null else hdrid end, case mod(rownum, 100) when 0 then 2 else 1 end, 1, 

dbms_random.string('a', 10) from (select hdrid from hdr where rownum &lt;= 5000),(select level from dual connect by level &lt;= 20) ;
commit;
end loop;
end;
/
 
exec dbms_stats.gather_table_stats(user, 'DTL', cascade=&gt;true);
exec dbms_stats.gather_table_stats(user, 'HDR', cascade=&gt;true);
</pre>
<p>With those tables freshly created, we can start experimenting.  What if we execute the update statement 20 times, outputting the execution plan each time, and executing the following SQL statement before the first update statement, and then after every 5 executions of the update statement:</p>
<pre>
select /*+ gather_plan_statistics */
  V.*,
  COUNT(decode(INCLUDE,'Y','Y',NULL)) OVER (ORDER BY ROWNUM) C,
  ROWNUM RN
from
(    select /*+ NO_EXPAND LEADING(dtl hdr) USE_NL(hdr) INDEX(dtl DTL_IDX2) */
 dtl.dtlid,
  hdr_ind,
  dtl.hdrid,
  dtl.cat_id,
  dtl.type_id,
  DTL.process_date,
  decode(dtl.hdrid,null,'Y',decode(hdr.hdr_ind,0,'Y',2,'Y','N')) INCLUDE
    from dtl,
         hdr
    where dtl.hdrid = hdr.hdrid(+)
          and dtl.process_date &lt; sysdate
          and dtl.process_ind = 'NEW'
          AND dtl.cat_id = 1
    order by decode(dtl.type_id, 1, 99, dtl.type_id),dtl.process_date) V
where
  rownum&lt;=2800;
</pre>
<p>The above output will show you the rows that are making it out of the DTL table, which are then used to probe the primary key index on the HDR table.  The C column indicates the number of rows that match the WHERE clause in your UPDATE statement, after those rows are pulled from the DTL table (once the C column reaches 100, the Oracle runtime engine is able to short-circuit the execution plan).  The RN column provides a running counter of the number of rows from the DTL table.</p>
<p>Before the first update, notice that the first row that will be returned (based on your WHERE clause) is the first row printed, and that the 100th row that will be returned is the 211th row that is printed:</p>
<pre>
     DTLID    HDR_IND      HDRID     CAT_ID    TYPE_ID PROCESS_D I          C         RN
---------- ---------- ---------- ---------- ---------- --------- - ---------- ----------
       600          0        600          1          2 03-JUL-12 Y          1          1
       700          2        700          1          2 03-JUL-12 Y          2          2
       800          6        800          1          2 03-JUL-12 N          2          3
       100          6        100          1          2 03-JUL-12 N          2          4
       200          6        200          1          2 03-JUL-12 N          2          5
       300          0        300          1          2 03-JUL-12 Y          3          6
       400          6        400          1          2 03-JUL-12 N          3          7
...
       669          0        669          1          1 03-JUL-12 Y         98        205
       670          6        670          1          1 03-JUL-12 N         98        206
       671          6        671          1          1 03-JUL-12 N         98        207
       672          0        672          1          1 03-JUL-12 Y         99        208
       673          6        673          1          1 03-JUL-12 N         99        209
       674          6        674          1          1 03-JUL-12 N         99        210
       675          0        675          1          1 03-JUL-12 Y        100        211
</pre>
<p>The execution plan, notice the A-Rows value for the DTL table is 211, the same value as the RN value above.</p>
<pre>
SQL_ID  a2g3h48p54pa2, child number 0
-------------------------------------
update /*+ gather_plan_statistics */ dtl set process_ind = 'PROCESSED'
where dtlid in (   select dtlid   from (     select /*+ NO_EXPAND
LEADING(dtl hdr) USE_NL(hdr) INDEX(dtl DTL_IDX2) */ dtl.dtlid     from
dtl,          hdr     where dtl.hdrid = hdr.hdrid(+)           and
dtl.process_date &lt; sysdate           and dtl.process_ind = 'NEW'                        
   and dtl.cat_id = 1           and (                (hdr_ind in (0, 2)                 
and dtl.hdrid IS NOT NULL) OR (dtl.hdrid IS NULL)               )                       
order by decode(dtl.type_id, 1, 99, dtl.type_id),dtl.process_date                       
      )     where rownum &lt;= 100)    
 
Plan hash value: 3052733514         
 
-------------------------------------------------------------------------------------------------------------------------------------       
| Id  | Operation                           | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |       
-------------------------------------------------------------------------------------------------------------------------------------       
|   0 | UPDATE STATEMENT                    |          |      1 |        |      0 |00:00:00.01 |    1863 |       |       |          |       
|   1 |  UPDATE                             | DTL      |      1 |        |      0 |00:00:00.01 |    1863 |       |       |          |       
|   2 |   NESTED LOOPS                      |          |      1 |    100 |    100 |00:00:00.01 |     502 |       |       |          |       
|   3 |    VIEW                             | VW_NSO_1 |      1 |    100 |    100 |00:00:00.01 |     415 |       |       |          |       
|   4 |     SORT UNIQUE                     |          |      1 |    100 |    100 |00:00:00.01 |     415 | 73728 | 73728 |          |       
|*  5 |      COUNT STOPKEY                  |          |      1 |        |    100 |00:00:00.01 |     415 |       |       |          |       
|   6 |       VIEW                          |          |      1 |  63619 |    100 |00:00:00.01 |     415 |       |       |          |       
|*  7 |        FILTER                       |          |      1 |        |    100 |00:00:00.01 |     415 |       |       |          |       
|   8 |         NESTED LOOPS OUTER          |          |      1 |  63619 |    211 |00:00:00.01 |     415 |       |       |          |       
|*  9 |          TABLE ACCESS BY INDEX ROWID| DTL      |      1 |  95524 |    211 |00:00:00.01 |     152 |       |       |          |       
|* 10 |           INDEX RANGE SCAN          | DTL_IDX2 |      1 |  95524 |    211 |00:00:00.01 |     116 |       |       |          |       
|  11 |          TABLE ACCESS BY INDEX ROWID| HDR      |    211 |      1 |    199 |00:00:00.01 |     263 |       |       |          |       
|* 12 |           INDEX UNIQUE SCAN         | HDR_PK   |    211 |      1 |    199 |00:00:00.01 |      64 |       |       |          |       
|* 13 |    INDEX UNIQUE SCAN                | DTL_PK   |    100 |      1 |    100 |00:00:00.01 |      87 |       |       |          |       
-------------------------------------------------------------------------------------------------------------------------------------       
 
Predicate Information (identified by operation id):
---------------------------------------------------
   5 - filter(ROWNUM&lt;=100)          
   7 - filter(((INTERNAL_FUNCTION("HDR_IND") AND "DTL"."HDRID" IS NOT NULL) OR "DTL"."HDRID" IS NULL)) 
   9 - filter("DTL"."CAT_ID"=1)     
  10 - access("DTL"."PROCESS_IND"='NEW' AND "DTL"."PROCESS_DATE"&lt;SYSDATE@!)             
       filter("DTL"."PROCESS_DATE"&lt;SYSDATE@!)      
  12 - access("DTL"."HDRID"="HDR"."HDRID")         
  13 - access("DTLID"="DTLID")      
</pre>
<p>Before the sixth execution &#8211; notice that the C column does not advance from 0 until the 645th row, and then runs for about 230 rows until hitting the 100th row that will be returned:</p>
<pre>
     DTLID    HDR_IND      HDRID     CAT_ID    TYPE_ID PROCESS_D I          C         RN
---------- ---------- ---------- ---------- ---------- --------- - ---------- ----------
      1093          6       1093          1          1 03-JUL-12 N          0        643
      1094          6       1094          1          1 03-JUL-12 N          0        644
      1095          0       1095          1          1 03-JUL-12 Y          1        645
      1096          6       1096          1          1 03-JUL-12 N          1        646
      1097          6       1097          1          1 03-JUL-12 N          1        647
      1098          0       1098          1          1 03-JUL-12 Y          2        648
      1099          2       1099          1          1 03-JUL-12 Y          3        649
      1101          0       1101          1          1 03-JUL-12 Y          4        650
...
      1840          0       1269          1          1 03-JUL-12 Y         98        868
      1841          6       1270          1          1 03-JUL-12 N         98        869
      1842          6       1271          1          1 03-JUL-12 N         98        870
      1843          0       1272          1          1 03-JUL-12 Y         99        871
      1844          6       1273          1          1 03-JUL-12 N         99        872
      1845          2       1274          1          1 03-JUL-12 Y        100        873
</pre>
<p>The execution plan for the 6th execution of the update statement, notice that the DTL table is returning 873 rows, just as the above predicted:</p>
<pre>
-------------------------------------------------------------------------------------------------------------------------------------       
| Id  | Operation                           | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |       
-------------------------------------------------------------------------------------------------------------------------------------       
|   0 | UPDATE STATEMENT                    |          |      1 |        |      0 |00:00:00.01 |    2482 |       |       |          |       
|   1 |  UPDATE                             | DTL      |      1 |        |      0 |00:00:00.01 |    2482 |       |       |          |       
|   2 |   NESTED LOOPS                      |          |      1 |    100 |    100 |00:00:00.01 |    1134 |       |       |          |       
|   3 |    VIEW                             | VW_NSO_1 |      1 |    100 |    100 |00:00:00.01 |    1096 |       |       |          |       
|   4 |     SORT UNIQUE                     |          |      1 |    100 |    100 |00:00:00.01 |    1096 | 73728 | 73728 |          |       
|*  5 |      COUNT STOPKEY                  |          |      1 |        |    100 |00:00:00.01 |    1096 |       |       |          |       
|   6 |       VIEW                          |          |      1 |  63619 |    100 |00:00:00.01 |    1096 |       |       |          |       
|*  7 |        FILTER                       |          |      1 |        |    100 |00:00:00.01 |    1096 |       |       |          |       
|   8 |         NESTED LOOPS OUTER          |          |      1 |  63619 |    873 |00:00:00.01 |    1096 |       |       |          |       
|*  9 |          TABLE ACCESS BY INDEX ROWID| DTL      |      1 |  95524 |    873 |00:00:00.01 |     155 |       |       |          |       
|* 10 |           INDEX RANGE SCAN          | DTL_IDX2 |      1 |  95524 |    873 |00:00:00.01 |     123 |       |       |          |       
|  11 |          TABLE ACCESS BY INDEX ROWID| HDR      |    873 |      1 |    873 |00:00:00.01 |     941 |       |       |          |       
|* 12 |           INDEX UNIQUE SCAN         | HDR_PK   |    873 |      1 |    873 |00:00:00.01 |      68 |       |       |          |       
|* 13 |    INDEX UNIQUE SCAN                | DTL_PK   |    100 |      1 |    100 |00:00:00.01 |      38 |       |       |          |       
-------------------------------------------------------------------------------------------------------------------------------------       
 
Predicate Information (identified by operation id):
---------------------------------------------------
   5 - filter(ROWNUM&lt;=100)          
   7 - filter(((INTERNAL_FUNCTION("HDR_IND") AND "DTL"."HDRID" IS NOT NULL) OR "DTL"."HDRID" IS NULL)) 
   9 - filter("DTL"."CAT_ID"=1)     
  10 - access("DTL"."PROCESS_IND"='NEW' AND "DTL"."PROCESS_DATE"&lt;SYSDATE@!)             
       filter("DTL"."PROCESS_DATE"&lt;SYSDATE@!)      
  12 - access("DTL"."HDRID"="HDR"."HDRID")         
  13 - access("DTLID"="DTLID")      
</pre>
<p>Just before the 11th update, the first row returned will begin with the 1312th row printed, and end with the 1544th row printed (again running for about 230 rows once the first usable row is found):</p>
<pre>
     DTLID    HDR_IND      HDRID     CAT_ID    TYPE_ID PROCESS_D I          C         RN
---------- ---------- ---------- ---------- ---------- --------- - ---------- ----------
      2791          6       2791          1          1 03-JUL-12 N          0       1310
      2792          6       2792          1          1 03-JUL-12 N          0       1311
      2793          0       2793          1          1 03-JUL-12 Y          1       1312
      2794          6       2794          1          1 03-JUL-12 N          1       1313
      2795          6       2795          1          1 03-JUL-12 N          1       1314
      2796          0       2796          1          1 03-JUL-12 Y          2       1315
      2797          6       2797          1          1 03-JUL-12 N          2       1316
      2798          6       2798          1          1 03-JUL-12 N          2       1317
...
      3024          0         24          1          1 03-JUL-12 Y         98       1540
      3025          6         25          1          1 03-JUL-12 N         98       1541
      3026          6         26          1          1 03-JUL-12 N         98       1542
      3027          0         27          1          1 03-JUL-12 Y         99       1543
      3028          2         28          1          1 03-JUL-12 Y        100       1544
</pre>
<p>You can probably guess what the execution plan looks like at this point:</p>
<pre>
SQL_ID  a2g3h48p54pa2, child number 0              
-------------------------------------              
update /*+ gather_plan_statistics */ dtl set process_ind = 'PROCESSED'                  
where dtlid in (   select dtlid   from (     select /*+ NO_EXPAND                       
LEADING(dtl hdr) USE_NL(hdr) INDEX(dtl DTL_IDX2) */ dtl.dtlid     from                  
dtl,          hdr     where dtl.hdrid = hdr.hdrid(+)           and                      
dtl.process_date &lt; sysdate           and dtl.process_ind = 'NEW'                        
   and dtl.cat_id = 1           and (                (hdr_ind in (0, 2)                 
and dtl.hdrid IS NOT NULL) OR (dtl.hdrid IS NULL)               )                       
order by decode(dtl.type_id, 1, 99, dtl.type_id),dtl.process_date                       
      )     where rownum &lt;= 100)    
 
Plan hash value: 3052733514         
 
-------------------------------------------------------------------------------------------------------------------------------------       
| Id  | Operation                           | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |       
-------------------------------------------------------------------------------------------------------------------------------------       
|   0 | UPDATE STATEMENT                    |          |      1 |        |      0 |00:00:00.01 |    3172 |       |       |          |       
|   1 |  UPDATE                             | DTL      |      1 |        |      0 |00:00:00.01 |    3172 |       |       |          |       
|   2 |   NESTED LOOPS                      |          |      1 |    100 |    100 |00:00:00.01 |    1841 |       |       |          |       
|   3 |    VIEW                             | VW_NSO_1 |      1 |    100 |    100 |00:00:00.01 |    1815 |       |       |          |       
|   4 |     SORT UNIQUE                     |          |      1 |    100 |    100 |00:00:00.01 |    1815 | 73728 | 73728 |          |       
|*  5 |      COUNT STOPKEY                  |          |      1 |        |    100 |00:00:00.01 |    1815 |       |       |          |       
|   6 |       VIEW                          |          |      1 |  63619 |    100 |00:00:00.01 |    1815 |       |       |          |       
|*  7 |        FILTER                       |          |      1 |        |    100 |00:00:00.01 |    1815 |       |       |          |       
|   8 |         NESTED LOOPS OUTER          |          |      1 |  63619 |   1544 |00:00:00.01 |    1815 |       |       |          |       
|*  9 |          TABLE ACCESS BY INDEX ROWID| DTL      |      1 |  95524 |   1544 |00:00:00.01 |     168 |       |       |          |       
|* 10 |           INDEX RANGE SCAN          | DTL_IDX2 |      1 |  95524 |   1544 |00:00:00.01 |     129 |       |       |          |       
|  11 |          TABLE ACCESS BY INDEX ROWID| HDR      |   1544 |      1 |   1544 |00:00:00.01 |    1647 |       |       |          |       
|* 12 |           INDEX UNIQUE SCAN         | HDR_PK   |   1544 |      1 |   1544 |00:00:00.01 |     103 |       |       |          |       
|* 13 |    INDEX UNIQUE SCAN                | DTL_PK   |    100 |      1 |    100 |00:00:00.01 |      26 |       |       |          |       
-------------------------------------------------------------------------------------------------------------------------------------       
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   5 - filter(ROWNUM&lt;=100)          
   7 - filter(((INTERNAL_FUNCTION("HDR_IND") AND "DTL"."HDRID" IS NOT NULL) OR "DTL"."HDRID" IS NULL)) 
   9 - filter("DTL"."CAT_ID"=1)     
  10 - access("DTL"."PROCESS_IND"='NEW' AND "DTL"."PROCESS_DATE"&lt;SYSDATE@!)             
       filter("DTL"."PROCESS_DATE"&lt;SYSDATE@!)      
  12 - access("DTL"."HDRID"="HDR"."HDRID")         
  13 - access("DTLID"="DTLID")      
</pre>
<p>The execution plan for the 20th update:</p>
<pre>
-------------------------------------------------------------------------------------------------------------------------------------       
| Id  | Operation                           | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |       
-------------------------------------------------------------------------------------------------------------------------------------       
|   0 | UPDATE STATEMENT                    |          |      1 |        |      0 |00:00:00.01 |    4468 |       |       |          |       
|   1 |  UPDATE                             | DTL      |      1 |        |      0 |00:00:00.01 |    4468 |       |       |          |       
|   2 |   NESTED LOOPS                      |          |      1 |    100 |    100 |00:00:00.01 |    3120 |       |       |          |       
|   3 |    VIEW                             | VW_NSO_1 |      1 |    100 |    100 |00:00:00.01 |    3094 |       |       |          |       
|   4 |     SORT UNIQUE                     |          |      1 |    100 |    100 |00:00:00.01 |    3094 | 73728 | 73728 |          |       
|*  5 |      COUNT STOPKEY                  |          |      1 |        |    100 |00:00:00.01 |    3094 |       |       |          |       
|   6 |       VIEW                          |          |      1 |  63619 |    100 |00:00:00.01 |    3094 |       |       |          |       
|*  7 |        FILTER                       |          |      1 |        |    100 |00:00:00.01 |    3094 |       |       |          |       
|   8 |         NESTED LOOPS OUTER          |          |      1 |  63619 |   2743 |00:00:00.01 |    3094 |       |       |          |       
|*  9 |          TABLE ACCESS BY INDEX ROWID| DTL      |      1 |  95524 |   2743 |00:00:00.01 |     192 |       |       |          |       
|* 10 |           INDEX RANGE SCAN          | DTL_IDX2 |      1 |  95524 |   2743 |00:00:00.01 |     141 |       |       |          |       
|  11 |          TABLE ACCESS BY INDEX ROWID| HDR      |   2743 |      1 |   2743 |00:00:00.01 |    2902 |       |       |          |       
|* 12 |           INDEX UNIQUE SCAN         | HDR_PK   |   2743 |      1 |   2743 |00:00:00.01 |     159 |       |       |          |       
|* 13 |    INDEX UNIQUE SCAN                | DTL_PK   |    100 |      1 |    100 |00:00:00.01 |      26 |       |       |          |       
-------------------------------------------------------------------------------------------------------------------------------------       
 
Predicate Information (identified by operation id):
---------------------------------------------------
   5 - filter(ROWNUM&lt;=100)          
   7 - filter(((INTERNAL_FUNCTION("HDR_IND") AND "DTL"."HDRID" IS NOT NULL) OR "DTL"."HDRID" IS NULL)) 
   9 - filter("DTL"."CAT_ID"=1)     
  10 - access("DTL"."PROCESS_IND"='NEW' AND "DTL"."PROCESS_DATE"&lt;SYSDATE@!)             
       filter("DTL"."PROCESS_DATE"&lt;SYSDATE@!)      
  12 - access("DTL"."HDRID"="HDR"."HDRID")         
  13 - access("DTLID"="DTLID") 
</pre>
<p>After the 20th update, the 21st update would need to go well beyond the 2800th row printed (probably close to the 2900th row):</p>
<pre>
     DTLID    HDR_IND      HDRID     CAT_ID    TYPE_ID PROCESS_D I          C         RN
---------- ---------- ---------- ---------- ---------- --------- - ---------- ----------
      4109          6       1109          1          1 03-JUL-12 N          0       2643
      4111          6       1111          1          1 03-JUL-12 N          0       2644
      4112          6       1112          1          1 03-JUL-12 N          0       2645
      4113          0       1113          1          1 03-JUL-12 Y          1       2646
      4114          6       1114          1          1 03-JUL-12 N          1       2647
      4115          6       1115          1          1 03-JUL-12 N          1       2648
      4116          0       1116          1          1 03-JUL-12 Y          2       2649
      4117          6       1117          1          1 03-JUL-12 N          2       2650
      4118          6       1118          1          1 03-JUL-12 N          2       2651
...
      4266          6       1837          1          1 03-JUL-12 N         66       2798
      4267          6       1838          1          1 03-JUL-12 N         66       2799
      4268          0       1839          1          1 03-JUL-12 Y         67       2800
...
</pre>
<p>Now, I think you know what you need to do to optimize this SQL statement, if your test case correctly replicates the product environment (if it is not obvious yet, look at the output of the SELECT SQL statement that returns 2800 rows, after the 20th update).</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Charles Hooper</title>
		<link>http://hoopercharles.wordpress.com/2012/06/28/different-execution-plans-when-converting-sql-from-select-to-update-in-select-rownum-related-bug-in-11-2-0-2/#comment-4787</link>
		<dc:creator><![CDATA[Charles Hooper]]></dc:creator>
		<pubDate>Mon, 02 Jul 2012 00:39:30 +0000</pubDate>
		<guid isPermaLink="false">http://hoopercharles.wordpress.com/?p=6416#comment-4787</guid>
		<description><![CDATA[Narendra,

You may be able to control the access path by using hints (keep in mind the comment made by Dominic).

This is an example that I put in that OTN thread where hints were used in the SELECT portion of the UPDATE statement:
&lt;pre&gt;
update dtl set process_ind = &#039;PROCESSED&#039; where dtlid in (
select
  dtlid
from
  (select
     dtlid
   from
     (select /*+ INDEX(DTL) INDEX(HRD) NO_USE_HASH(HDR) NO_USE_MERGE(HDR) */
        dtl.dtlid,
        dtl.process_date
      from
        dtl,
        hdr
      where
        dtl.hdrid = hdr.hdrid(+)
        and dtl.process_date &lt; (sysdate + 30)
        and dtl.process_ind = &#039;NEW&#039;
        and (
            (hdr_ind in (0, 2) and dtl.hdrid IS NOT NULL)
            OR (dtl.hdrid IS NULL)))
   order by
     dtlid,
     process_date)
where
  rownum &lt;= 10);
&lt;/pre&gt;

I think that if the DTL table is not selected as the first table in the join, the NO_USE_HASH and NO_USE_MERGE hints might not have any effect.  So, we probably need to use a LEADING hint to make certain that the optimizer starts with the DTL table:
&lt;pre&gt;
update dtl set process_ind = &#039;PROCESSED&#039; where dtlid in (
select
  dtlid
from
  (select
     dtlid
   from
     (select /*+ LEADING(DTL) INDEX(DTL) INDEX(HRD) NO_USE_HASH(HDR) NO_USE_MERGE(HDR) */
        dtl.dtlid,
        dtl.process_date
      from
        dtl,
        hdr
      where
        dtl.hdrid = hdr.hdrid(+)
        and dtl.process_date &lt; (sysdate + 30)
        and dtl.process_ind = &#039;NEW&#039;
        and (
            (hdr_ind in (0, 2) and dtl.hdrid IS NOT NULL)
            OR (dtl.hdrid IS NULL)))
   order by
     dtlid,
     process_date)
where
  rownum &lt;= 10);
&lt;/pre&gt;

You could probably replace the NO_USE_HASH and NO_USE_MERGE hints with just a USE_NL(HDR) hint:
&lt;pre&gt;
update dtl set process_ind = &#039;PROCESSED&#039; where dtlid in (
select
  dtlid
from
  (select
     dtlid
   from
     (select /*+ LEADING(DTL) INDEX(DTL) INDEX(HRD) USE_NL(HDR) */
        dtl.dtlid,
        dtl.process_date
      from
        dtl,
        hdr
      where
        dtl.hdrid = hdr.hdrid(+)
        and dtl.process_date &lt; (sysdate + 30)
        and dtl.process_ind = &#039;NEW&#039;
        and (
            (hdr_ind in (0, 2) and dtl.hdrid IS NOT NULL)
            OR (dtl.hdrid IS NULL)))
   order by
     dtlid,
     process_date)
where
  rownum &lt;= 10);
&lt;/pre&gt;


Since the SELECT statement is optimized with a FIRST_ROWS(n) optimizer mode, there is a chance (probably a small chance) that a FIRST_ROWS(n) hint may override the automatic ALL_ROWS optimization:
&lt;pre&gt;
update dtl set process_ind = &#039;PROCESSED&#039; where dtlid in (
select
  dtlid
from
  (select
     dtlid
   from
     (select /*+ FIRST_ROWS(10) */
        dtl.dtlid,
        dtl.process_date
      from
        dtl,
        hdr
      where
        dtl.hdrid = hdr.hdrid(+)
        and dtl.process_date &lt; (sysdate + 30)
        and dtl.process_ind = &#039;NEW&#039;
        and (
            (hdr_ind in (0, 2) and dtl.hdrid IS NOT NULL)
            OR (dtl.hdrid IS NULL)))
   order by
     dtlid,
     process_date)
where
  rownum &lt;= 10);
&lt;/pre&gt;

If you are able to solve the problem, I would like to see what worked.  Definitely update the OTN thread when you find a solution.  If the hint does not work out, you could post the updated test case so that people are able to better reproduce the problem that you are experiencing.]]></description>
		<content:encoded><![CDATA[<p>Narendra,</p>
<p>You may be able to control the access path by using hints (keep in mind the comment made by Dominic).</p>
<p>This is an example that I put in that OTN thread where hints were used in the SELECT portion of the UPDATE statement:</p>
<pre>
update dtl set process_ind = 'PROCESSED' where dtlid in (
select
  dtlid
from
  (select
     dtlid
   from
     (select /*+ INDEX(DTL) INDEX(HRD) NO_USE_HASH(HDR) NO_USE_MERGE(HDR) */
        dtl.dtlid,
        dtl.process_date
      from
        dtl,
        hdr
      where
        dtl.hdrid = hdr.hdrid(+)
        and dtl.process_date &lt; (sysdate + 30)
        and dtl.process_ind = &#039;NEW&#039;
        and (
            (hdr_ind in (0, 2) and dtl.hdrid IS NOT NULL)
            OR (dtl.hdrid IS NULL)))
   order by
     dtlid,
     process_date)
where
  rownum &lt;= 10);
</pre>
<p>I think that if the DTL table is not selected as the first table in the join, the NO_USE_HASH and NO_USE_MERGE hints might not have any effect.  So, we probably need to use a LEADING hint to make certain that the optimizer starts with the DTL table:</p>
<pre>
update dtl set process_ind = 'PROCESSED' where dtlid in (
select
  dtlid
from
  (select
     dtlid
   from
     (select /*+ LEADING(DTL) INDEX(DTL) INDEX(HRD) NO_USE_HASH(HDR) NO_USE_MERGE(HDR) */
        dtl.dtlid,
        dtl.process_date
      from
        dtl,
        hdr
      where
        dtl.hdrid = hdr.hdrid(+)
        and dtl.process_date &lt; (sysdate + 30)
        and dtl.process_ind = &#039;NEW&#039;
        and (
            (hdr_ind in (0, 2) and dtl.hdrid IS NOT NULL)
            OR (dtl.hdrid IS NULL)))
   order by
     dtlid,
     process_date)
where
  rownum &lt;= 10);
</pre>
<p>You could probably replace the NO_USE_HASH and NO_USE_MERGE hints with just a USE_NL(HDR) hint:</p>
<pre>
update dtl set process_ind = 'PROCESSED' where dtlid in (
select
  dtlid
from
  (select
     dtlid
   from
     (select /*+ LEADING(DTL) INDEX(DTL) INDEX(HRD) USE_NL(HDR) */
        dtl.dtlid,
        dtl.process_date
      from
        dtl,
        hdr
      where
        dtl.hdrid = hdr.hdrid(+)
        and dtl.process_date &lt; (sysdate + 30)
        and dtl.process_ind = &#039;NEW&#039;
        and (
            (hdr_ind in (0, 2) and dtl.hdrid IS NOT NULL)
            OR (dtl.hdrid IS NULL)))
   order by
     dtlid,
     process_date)
where
  rownum &lt;= 10);
</pre>
<p>Since the SELECT statement is optimized with a FIRST_ROWS(n) optimizer mode, there is a chance (probably a small chance) that a FIRST_ROWS(n) hint may override the automatic ALL_ROWS optimization:</p>
<pre>
update dtl set process_ind = 'PROCESSED' where dtlid in (
select
  dtlid
from
  (select
     dtlid
   from
     (select /*+ FIRST_ROWS(10) */
        dtl.dtlid,
        dtl.process_date
      from
        dtl,
        hdr
      where
        dtl.hdrid = hdr.hdrid(+)
        and dtl.process_date &lt; (sysdate + 30)
        and dtl.process_ind = &#039;NEW&#039;
        and (
            (hdr_ind in (0, 2) and dtl.hdrid IS NOT NULL)
            OR (dtl.hdrid IS NULL)))
   order by
     dtlid,
     process_date)
where
  rownum &lt;= 10);
</pre>
<p>If you are able to solve the problem, I would like to see what worked.  Definitely update the OTN thread when you find a solution.  If the hint does not work out, you could post the updated test case so that people are able to better reproduce the problem that you are experiencing.</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Narendra</title>
		<link>http://hoopercharles.wordpress.com/2012/06/28/different-execution-plans-when-converting-sql-from-select-to-update-in-select-rownum-related-bug-in-11-2-0-2/#comment-4786</link>
		<dc:creator><![CDATA[Narendra]]></dc:creator>
		<pubDate>Sun, 01 Jul 2012 23:59:27 +0000</pubDate>
		<guid isPermaLink="false">http://hoopercharles.wordpress.com/?p=6416#comment-4786</guid>
		<description><![CDATA[Charles,

I believe I have managed to simulate the data pattern more closer to the data in original tables (BTW, I am the OP of that OTN thread).
And I am again stuck at the stage where the SELECT part of the UPDATE, when executed on its own, uses the nice NESTED LOOP join with short-circuit. But, as expected, the UPDATE does not use the particular index and the join changes to HASH JOIN. I strongly suspect that the UPDATE could benefit from using the access path used by the standalone SELECT but can&#039;t figure out how to make it happen.
If I have not already managed to annoy you, I can post the complete test case, either here or on OTN thread.]]></description>
		<content:encoded><![CDATA[<p>Charles,</p>
<p>I believe I have managed to simulate the data pattern more closer to the data in original tables (BTW, I am the OP of that OTN thread).<br />
And I am again stuck at the stage where the SELECT part of the UPDATE, when executed on its own, uses the nice NESTED LOOP join with short-circuit. But, as expected, the UPDATE does not use the particular index and the join changes to HASH JOIN. I strongly suspect that the UPDATE could benefit from using the access path used by the standalone SELECT but can&#8217;t figure out how to make it happen.<br />
If I have not already managed to annoy you, I can post the complete test case, either here or on OTN thread.</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Log Buffer #276, A Carnival of the Vanities for DBAs &#124; The Pythian Blog</title>
		<link>http://hoopercharles.wordpress.com/2012/06/28/different-execution-plans-when-converting-sql-from-select-to-update-in-select-rownum-related-bug-in-11-2-0-2/#comment-4781</link>
		<dc:creator><![CDATA[Log Buffer #276, A Carnival of the Vanities for DBAs &#124; The Pythian Blog]]></dc:creator>
		<pubDate>Fri, 29 Jun 2012 06:01:31 +0000</pubDate>
		<guid isPermaLink="false">http://hoopercharles.wordpress.com/?p=6416#comment-4781</guid>
		<description><![CDATA[[...] test case to reproduce a particular problem.  Sometimes the test case drifts a bit offtrack. Charles Hooper shares his unique [...]]]></description>
		<content:encoded><![CDATA[<p>[...] test case to reproduce a particular problem.  Sometimes the test case drifts a bit offtrack. Charles Hooper shares his unique [...]</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Charles Hooper</title>
		<link>http://hoopercharles.wordpress.com/2012/06/28/different-execution-plans-when-converting-sql-from-select-to-update-in-select-rownum-related-bug-in-11-2-0-2/#comment-4779</link>
		<dc:creator><![CDATA[Charles Hooper]]></dc:creator>
		<pubDate>Thu, 28 Jun 2012 17:46:27 +0000</pubDate>
		<guid isPermaLink="false">http://hoopercharles.wordpress.com/?p=6416#comment-4779</guid>
		<description><![CDATA[Dominic,

Thanks for the comment and for supplying a link to one of your articles.  You make a good point.  Rational disagreement is of course encouraged when necessary - there are of course a lot of extremely knowledgeable people out there, and when I am wrong, I hope that those people will offer kind assistance.

&lt;u&gt;The first of the above bullet points has (at least) two completely different interpretations:&lt;/u&gt;

&lt;ul&gt;
* If you as the developer know that table T1 will expand from 1,000 rows in your test environment to 10,000,000,000 rows once the application is in service for a couple of years (with index clustering factor values expected to become closer to the number or rows in the table than it is to the number of blocks in the table), while the other tables accessed by the SQL statement will remain at roughly 1,000 rows, it might make sense to specify the access path for one of the tables, while allowing the optimizer to dynamically adjust the access path, join method, and automatic query transformation for the remainder of the query.  I believe that this is the interpretation envisioned when you read the first bullet point, and might very well be the same interpretation that many other people have also.

* When you as the developer are developing an application, the sample data that is used for development may be much smaller than what your customers will see - that 1,000 row table that has beautifully built rows that are inserted in a perfect order for the indexes built on the table, may in fact be a 10,000,000,000 row table with rows inserted in a nearly random order into the table&#039;s blocks, with the table having 10,000 partitions in an ASSM tablespace, and with access to parallel query.  The statement &quot;without boxing the SQL statement into a corner as the data volume increases in the various tables accessed by the SQL statement&quot; in this case means - think about the big picture, what will the table&#039;s data volume look like in a month, year, 10 years, etc.  If the developer &lt;i&gt;boxes&lt;/i&gt; the optimizer into a corner with hints that produce efficient execution plans in the small sample database with OPTIMIZER_INDEX_COST_ADJ set to 1, for instance, that same SQL statement and execution plan could cause horrendous performance problems when the code is released into production environments with several years&#039; worth of data.  In short, if you do not know what you are doing with hints, and do not understand how the data will transform over time, maybe it is best to leave the hints out of the SQL statement all together.

* Consider how the data and supporting structures might mature over time.  If as a developer, you fully hint an execution plan to take advantage of specific indexes, access paths, join methods, etc. and the query executes efficiently - great.  But, what happens if the definition of &quot;efficient&quot; changes at some point in the future?  What if the developer learns that normal B*tree indexes can contain NULLs (http://hoopercharles.wordpress.com/2012/02/28/repeat-after-me-null-values-are-not-stored-in-indexes ) and determines that a new composite index would generate a better plan, or simply adding a NOT NULL contraint to a table definition provides the optimizer with enough information that it can not make use of an index access path that was previously unavailable.  What if a hint specifies a specific index, and that index is no longer accessible to the optimizer (maybe the index is marked as UNUSABLE, maybe it was altered to be hidden, maybe it was just a redundant index and dropped, maybe it was an index with a system generated SYS_ name that does not survive an export-import) - the optimizer in an Oracle Database is quite clever at times, and may arrive at a completely stupid execution plan when trying to follow the demands specified by the remaining valid hints.  If the developer had boxed in the optimizer with very specific hints, it could be difficult to fix the application code to take advantage of the &lt;i&gt;more&lt;/i&gt; efficient access paths (I understand that there are extensive procedures that must be followed in some industries, for instance drug manufacturing, any time the source code of an application changes).&lt;/ul&gt;

The different possible interpretations, in this case, was somewhat intentional - the second of the above bullet points was the original target of the bullet point statement, but the other interpretations are present to encourage discussion with readers who have a lot of technical experience in this area.

Anyone else have an opinion on this matter, or any of the other bullet pointed items in the article?]]></description>
		<content:encoded><![CDATA[<p>Dominic,</p>
<p>Thanks for the comment and for supplying a link to one of your articles.  You make a good point.  Rational disagreement is of course encouraged when necessary &#8211; there are of course a lot of extremely knowledgeable people out there, and when I am wrong, I hope that those people will offer kind assistance.</p>
<p><u>The first of the above bullet points has (at least) two completely different interpretations:</u></p>
<ul>
* If you as the developer know that table T1 will expand from 1,000 rows in your test environment to 10,000,000,000 rows once the application is in service for a couple of years (with index clustering factor values expected to become closer to the number or rows in the table than it is to the number of blocks in the table), while the other tables accessed by the SQL statement will remain at roughly 1,000 rows, it might make sense to specify the access path for one of the tables, while allowing the optimizer to dynamically adjust the access path, join method, and automatic query transformation for the remainder of the query.  I believe that this is the interpretation envisioned when you read the first bullet point, and might very well be the same interpretation that many other people have also.</p>
<p>* When you as the developer are developing an application, the sample data that is used for development may be much smaller than what your customers will see &#8211; that 1,000 row table that has beautifully built rows that are inserted in a perfect order for the indexes built on the table, may in fact be a 10,000,000,000 row table with rows inserted in a nearly random order into the table&#8217;s blocks, with the table having 10,000 partitions in an ASSM tablespace, and with access to parallel query.  The statement &#8220;without boxing the SQL statement into a corner as the data volume increases in the various tables accessed by the SQL statement&#8221; in this case means &#8211; think about the big picture, what will the table&#8217;s data volume look like in a month, year, 10 years, etc.  If the developer <i>boxes</i> the optimizer into a corner with hints that produce efficient execution plans in the small sample database with OPTIMIZER_INDEX_COST_ADJ set to 1, for instance, that same SQL statement and execution plan could cause horrendous performance problems when the code is released into production environments with several years&#8217; worth of data.  In short, if you do not know what you are doing with hints, and do not understand how the data will transform over time, maybe it is best to leave the hints out of the SQL statement all together.</p>
<p>* Consider how the data and supporting structures might mature over time.  If as a developer, you fully hint an execution plan to take advantage of specific indexes, access paths, join methods, etc. and the query executes efficiently &#8211; great.  But, what happens if the definition of &#8220;efficient&#8221; changes at some point in the future?  What if the developer learns that normal B*tree indexes can contain NULLs (<a href="http://hoopercharles.wordpress.com/2012/02/28/repeat-after-me-null-values-are-not-stored-in-indexes" rel="nofollow">http://hoopercharles.wordpress.com/2012/02/28/repeat-after-me-null-values-are-not-stored-in-indexes</a> ) and determines that a new composite index would generate a better plan, or simply adding a NOT NULL contraint to a table definition provides the optimizer with enough information that it can not make use of an index access path that was previously unavailable.  What if a hint specifies a specific index, and that index is no longer accessible to the optimizer (maybe the index is marked as UNUSABLE, maybe it was altered to be hidden, maybe it was just a redundant index and dropped, maybe it was an index with a system generated SYS_ name that does not survive an export-import) &#8211; the optimizer in an Oracle Database is quite clever at times, and may arrive at a completely stupid execution plan when trying to follow the demands specified by the remaining valid hints.  If the developer had boxed in the optimizer with very specific hints, it could be difficult to fix the application code to take advantage of the <i>more</i> efficient access paths (I understand that there are extensive procedures that must be followed in some industries, for instance drug manufacturing, any time the source code of an application changes).</ul>
<p>The different possible interpretations, in this case, was somewhat intentional &#8211; the second of the above bullet points was the original target of the bullet point statement, but the other interpretations are present to encourage discussion with readers who have a lot of technical experience in this area.</p>
<p>Anyone else have an opinion on this matter, or any of the other bullet pointed items in the article?</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Dom Brooks</title>
		<link>http://hoopercharles.wordpress.com/2012/06/28/different-execution-plans-when-converting-sql-from-select-to-update-in-select-rownum-related-bug-in-11-2-0-2/#comment-4777</link>
		<dc:creator><![CDATA[Dom Brooks]]></dc:creator>
		<pubDate>Thu, 28 Jun 2012 14:09:35 +0000</pubDate>
		<guid isPermaLink="false">http://hoopercharles.wordpress.com/?p=6416#comment-4777</guid>
		<description><![CDATA[&gt; When hinting a SQL statement to generate an expected execution plan, it is important to be as specific as possible with the hints, without boxing the SQL statement into a corner as the data volume increases in the various tables accessed by the SQL statement

For once, I&#039;m not sure I agree with you, Charles. I don&#039;t want to take the thread off topic but ...

If we were just talking about test cases and trying to reproduce a particular behaviour then I&#039;d have nothing further to say. 

But you mention giving the optimizer options as volumes change, so we must be talking about permanently embedded hints in application code, right?

Now, it must depend on what sort of hints we&#039;re talking about and I&#039;ve had &lt;a href=&quot;http://orastory.wordpress.com/2011/12/12/hints-of-acceptability/&quot; rel=&quot;nofollow&quot;&gt;some thoughts before about &quot;acceptable&quot; hints&lt;/a&gt;.

But, certainly in any recent version of Oracle, if we&#039;re not talking about this small subset of statistical adjustments and other such hints as per above but we&#039;re talking about hinting join mechanisms and access paths then I think you have to  make a pretty much black and white choice.

You either have to provide a full set of hints for a specific plan or you don&#039;t hint.

Particularly as the optimizer gets more complex and query transformation gets more sophisticated, not boxing the SQL statement into a corner is a performance threat and I see the threat in action time and time again, especially related to upgrades.

If you can fix an execution plan issue via statistical adjustments using cardinality, dynamic_sampling or even opt_estimate then great, otherwise I think you&#039;ve a tough choice.

Alternatively, if you want to best of both worlds - specific plans now with the flexibility to evolve then we&#039;re talking baselines.

Cheers,
Dominic]]></description>
		<content:encoded><![CDATA[<p>&gt; When hinting a SQL statement to generate an expected execution plan, it is important to be as specific as possible with the hints, without boxing the SQL statement into a corner as the data volume increases in the various tables accessed by the SQL statement</p>
<p>For once, I&#8217;m not sure I agree with you, Charles. I don&#8217;t want to take the thread off topic but &#8230;</p>
<p>If we were just talking about test cases and trying to reproduce a particular behaviour then I&#8217;d have nothing further to say. </p>
<p>But you mention giving the optimizer options as volumes change, so we must be talking about permanently embedded hints in application code, right?</p>
<p>Now, it must depend on what sort of hints we&#8217;re talking about and I&#8217;ve had <a href="http://orastory.wordpress.com/2011/12/12/hints-of-acceptability/" rel="nofollow">some thoughts before about &#8220;acceptable&#8221; hints</a>.</p>
<p>But, certainly in any recent version of Oracle, if we&#8217;re not talking about this small subset of statistical adjustments and other such hints as per above but we&#8217;re talking about hinting join mechanisms and access paths then I think you have to  make a pretty much black and white choice.</p>
<p>You either have to provide a full set of hints for a specific plan or you don&#8217;t hint.</p>
<p>Particularly as the optimizer gets more complex and query transformation gets more sophisticated, not boxing the SQL statement into a corner is a performance threat and I see the threat in action time and time again, especially related to upgrades.</p>
<p>If you can fix an execution plan issue via statistical adjustments using cardinality, dynamic_sampling or even opt_estimate then great, otherwise I think you&#8217;ve a tough choice.</p>
<p>Alternatively, if you want to best of both worlds &#8211; specific plans now with the flexibility to evolve then we&#8217;re talking baselines.</p>
<p>Cheers,<br />
Dominic</p>
]]></content:encoded>
	</item>
</channel>
</rss>
