<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	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:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Archives des Cost Based Optimizer - dbi Blog</title>
	<atom:link href="https://www.dbi-services.com/blog/tag/cost-based-optimizer/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.dbi-services.com/blog/tag/cost-based-optimizer/</link>
	<description></description>
	<lastBuildDate>Sun, 16 Nov 2014 12:38:52 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/cropped-favicon_512x512px-min-32x32.png</url>
	<title>Archives des Cost Based Optimizer - dbi Blog</title>
	<link>https://www.dbi-services.com/blog/tag/cost-based-optimizer/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>12c SQL Plan Directive: state has changed between 12.1.0.1 and 12.1.0.2</title>
		<link>https://www.dbi-services.com/blog/12c-sql-plan-directive-state-has-changed-between-12101-and-12102/</link>
					<comments>https://www.dbi-services.com/blog/12c-sql-plan-directive-state-has-changed-between-12101-and-12102/#respond</comments>
		
		<dc:creator><![CDATA[Oracle Team]]></dc:creator>
		<pubDate>Sun, 16 Nov 2014 12:38:52 +0000</pubDate>
				<category><![CDATA[Database management]]></category>
		<category><![CDATA[Oracle]]></category>
		<category><![CDATA[Cost Based Optimizer]]></category>
		<category><![CDATA[Oracle 12c]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/12c-sql-plan-directive-state-has-changed-between-12101-and-12102/</guid>

					<description><![CDATA[<p>By Franck Pachot . SQL plan Directives have been introduced in 12c. If you have scripts that check their states (and I have that as I prefer to monitor closely the features that are new) you probably have seen a difference when going from 12.1.0.1 &#8211; the first release of 12c and the only one [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/12c-sql-plan-directive-state-has-changed-between-12101-and-12102/">12c SQL Plan Directive: state has changed between 12.1.0.1 and 12.1.0.2</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h2>By Franck Pachot</h2>
<p>.<br />
SQL plan Directives have been introduced in 12c. If you have scripts that check their states (and I have that as I prefer to monitor closely the features that are new) you probably have seen a difference when going from 12.1.0.1 &#8211; the first release of 12c and the only one avilable yet in Standard Edition &#8211; and 12.1.0.2 &#8211; the first patchest. I&#8217;ll explain here what are the SQL Plan Directive states and how they changed.</p>
<p>When a SQL Plan Directive is created, it&#8217;s state is NEW. It means that a misestimate has been encountered but the reason has not been yet determined. Remember that the directive is created only at execution time, so very limited information is available, except the fact that A-Rows is different that E-Rows.</p>
<p>On a subsequent optimization (same or different statement that uses the same table and same column predicates) the optimizer sees the SQL Plan Directive and can update it with more information. If the reason of misestimation is that some statistics are missing then the state of the directive is changed from NEW to MISSING_STATS.</p>
<p>MISSING_STATS directives lead to short term and long term solutions:</p>
<ul>
<li>each new query will solve missing stats by gathering more statistics with Dynamic Sampling</li>
<li>the next dbms_stats gathering will gather extended statistics to definitly fix the issue</li>
</ul>
<p>Once the latter is done, we can expect to have good estimations without Dynamic Sampling anymore, thanks to the extended statistics. But that has to be checked. So the next query optimization will check it and update the SQL plan Directive accordingly:</p>
<ul>
<li>HAS_STATS to show that statistics (extended statistics here) are now sufficient to get correct estimations</li>
<li>PERMANENT to show that the misestimation is still there and extended statistics have to be ignored because they were calculated for an issue that they do not solve.</li>
</ul>
<div>Finally, the HAS_STATS SQL Plan Directives are purged after some weeks as they are not needed anymore &#8211; the issue being solved definitely.</div>
<div></div>
<div>So what has changed in 12.1.0.1 ?</div>
<div></div>
<div>There are only two states now:</div>
<div>
<ul>
<li>&#8216;USABLE&#8217; that covers the &#8216;NEW&#8217;, &#8216;MISSING_STATS&#8217; and &#8216;PERMANENT&#8217; which means that a directive is there to be evaluated by the optimizer, but the issue is not solved yet.</li>
<li>&#8216;SUPERSEDED&#8217; when it has been solved (the &#8216;HAS_STATS&#8217;) or it is redundant with another directive, which means that the issue is solved somewhere else.</li>
</ul>
</div>
<div>This is a simplification, but if you want to have the same level of detail that you had in 12.1.0.2 then you can get it from the &#8216;internal state&#8217; which is exposed in XML in the NOTES column.</div>
<div></div>
<div>Here is an example of two USABLE state:</div>
<div></div>
<pre class="brush: sql; gutter: true; first-line: 1">SQL&gt; select directive_id,type,state,reason,notes from dba_sql_plan_directives where directive_id in(select directive_id from dba_sql_plan_dir_objects where owner='DEMO' );

        DIRECTIVE_ID TYPE              STATE         REASON
-------------------- ---------------- ---------- ------------------------------------
NOTES
--------------------------------------------------------------------------------
 1350980939868665098 DYNAMIC_SAMPLING USABLE         SINGLE TABLE CARDINALITY MISESTIMATE

  NEW
  NO
  {EC(DEMO.DEMO_TABLE)[A, B, C, D]}

</pre>
<div>which is &#8216;NEW&#8217; and</div>
<div></div>
<pre class="brush: sql; gutter: true; first-line: 1">        DIRECTIVE_ID TYPE              STATE         REASON
-------------------- ---------------- ---------- ------------------------------------
NOTES
--------------------------------------------------------------------------------
 1350980939868665098 DYNAMIC_SAMPLING USABLE         SINGLE TABLE CARDINALITY MISESTIMATE

  MISSING_STATS
  NO
  {EC(DEMO.DEMO_TABLE)[A, B, C, D]}

</pre>
<div>which is &#8216;MISSING_STATS&#8217;.</div>
<div></div>
<div>And a &#8216;SUPERSEDED&#8217; once dbms_stats has run:</div>
<div></div>
<pre class="brush: sql; gutter: true; first-line: 1">        DIRECTIVE_ID TYPE              STATE         REASON
-------------------- ---------------- ---------- ------------------------------------
NOTES
--------------------------------------------------------------------------------
 1350980939868665098 DYNAMIC_SAMPLING SUPERSEDED SINGLE TABLE CARDINALITY MISESTIMATE

  HAS_STATS
  NO
  {EC(DEMO.DEMO_TABLE)[A, B, C, D]}

</pre>
<div>which is &#8216;HAS_STATS&#8217;</div>
<div>Note that the xml tags were eaten by my blog editor. They are: internal_state,redundant,spd_text</div>
<div></div>
<div>We do full demos of SQL Plan Directives in our <a href="index.php/products/dbi-insite-workshops/oracle-12c-new-features-workshop">&#8216;Oracle 12c new features workshop&#8217;</a> and <a href="index.php/products/dbi-insite-workshops/oracle-performance-tuning-training">&#8216;Oracle performance tuning workshop&#8217;</a>. It&#8217;s a great feature that brings the CBO to another level of intelligence. And there are some misconceptions about them. Some people think that they store statistics. But that&#8217;s wrong. Statistics come from cardinality feedback, dynamic sampling, or object statistics. There is no need for another component to store them. The only thing that is stored by SQL Plan Directives are their state. Which makes the state a very important information &#8211; and the reason for that blog post.</div>
<div></div>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>L’article <a href="https://www.dbi-services.com/blog/12c-sql-plan-directive-state-has-changed-between-12101-and-12102/">12c SQL Plan Directive: state has changed between 12.1.0.1 and 12.1.0.2</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/12c-sql-plan-directive-state-has-changed-between-12101-and-12102/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Oracle 12.1.0.2.1 Set to Join Conversion</title>
		<link>https://www.dbi-services.com/blog/oracle-121021-set-to-join-conversion/</link>
					<comments>https://www.dbi-services.com/blog/oracle-121021-set-to-join-conversion/#respond</comments>
		
		<dc:creator><![CDATA[Oracle Team]]></dc:creator>
		<pubDate>Thu, 21 Aug 2014 03:17:54 +0000</pubDate>
				<category><![CDATA[Database management]]></category>
		<category><![CDATA[Oracle]]></category>
		<category><![CDATA[Cost Based Optimizer]]></category>
		<category><![CDATA[Oracle 12c]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[SQL]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/oracle-121021-set-to-join-conversion/</guid>

					<description><![CDATA[<p>By Franck Pachot . Recently, I described the Partial Join Evaluation transformation that appeared last year in Oracle 12c. I did it as an introduction for another transformation that appeared long time ago in 10.1.0.3 but was not used by default. And even in the latest Oracle 12c patchset 1 (aka 12.1.0.2.0) it is still [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/oracle-121021-set-to-join-conversion/">Oracle 12.1.0.2.1 Set to Join Conversion</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h2>By Franck Pachot</h2>
<p>.<br />
Recently, I described the <a href="/partial-join-evaluation-in-oracle-12c">Partial Join Evaluation</a> transformation that appeared last year in Oracle 12c. I did it as an introduction for another transformation that appeared long time ago in 10.1.0.3 but was not used by default. And even in the latest Oracle 12c patchset 1 (aka 12.1.0.2.0) it is still not enabled. But it&#8217;s there and you can use it if you set optimizer_features_enabled to 12.1.0.2.1 (that&#8217;s not a typo!).</p>
<p>Yes, that number looks like the future PSU for the 12c Release 1 Patchset 1 that was available recently and has no PSU yet. Lost in the release numbers? No problem. This is only default values for the _convert_set_to_join paramter but you can also use the hint to get that transformation, which is available in previous versions as well.</p>
<p>So what does this transformation? It transforms an INTERSECT or MINUS into a join. When the tables are large but the result is small, that transformation can bring new access path avoiding full table scans and deduplication for each branch. And thanks to the Partial Join Evaluation the performance is even better in 12c. Let&#8217;s look at an example.</p>
<pre><code>SQL*Plus: Release 12.1.0.2.0 Production on Sun Jul 27 22:10:57 2014</code></pre>
<pre><code>Copyright (c) 1982, 2014, Oracle.  All rights reserved.
Connected to:
 Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
 With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
SQL&gt; create table DEMO1(n constraint DEMO1_N primary key) as select rownum n from (select * from dual connect by level  Table created.
SQL&gt; create table DEMO2(n constraint DEMO2_N primary key) as select rownum n from dual connect by level  Table created.
SQL&gt; alter session set statistics_level=all;
 Session altered.</code></pre>
<pre><code><samp></samp></code></pre>
<p>So I have two tables, one with 100000 rows and one with only 10. And I want the rows from DEMO1 which are not in DEMO2:</p>
<pre><code>SQL&gt; alter session set optimizer_features_enable='12.1.0.2.1';
 Session altered.
SQL&gt; select * from DEMO1 intersect select * from DEMO2;
         N
 ----------
          1
          2
          3
          4
          5
          6
          7
          8
          9
         10
10 rows selected.</code></pre>
<p>Let&#8217;s have a look at the plan:</p>
<pre><code>SQL&gt; select * from table(dbms_xplan.display_cursor(format=&gt;'allstats last'));
PLAN_TABLE_OUTPUT
 -----------------------------------------------------------------------------
 SQL_ID 9fpg8nyjaqb5f, child number 0
 -------------------------------------
 select * from DEMO1 intersect select * from DEMO2
Plan hash value: 4278239763
------------------------------------------------------------------------------
 | Id  | Operation           | Name    | Starts | E-Rows | A-Rows || Used-Mem |
 ------------------------------------------------------------------------------
 |   0 | SELECT STATEMENT    |         |      1 |        |     10 ||          |
 |   1 |  INTERSECTION       |         |      1 |        |     10 ||          |
 |   2 |   SORT UNIQUE       |         |      1 |    100K|    100K|| 4078K (0)|
 |   3 |    TABLE ACCESS FULL| DEMO1   |      1 |    100K|    100K||          |
 |   4 |   SORT UNIQUE NOSORT|         |      1 |     10 |     10 ||          |
 |   5 |    INDEX FULL SCAN  | DEMO2_N |      1 |     10 |     10 ||          |
 ------------------------------------------------------------------------------
Query Block Name / Object Alias (identified by operation id):
 -------------------------------------------------------------
   1 - SET$1
    3 - SEL$1 / DEMO1@SEL$1
    5 - SEL$2 / DEMO2@SEL$2</code></pre>
<p>This is the expected plan. There is an INTERSECTION operation that implements our INTERSECT. But look: each branch had to be deduplicated (SORT UNIQUE). Note that the SORT UNIQUE NOSORT has a funny name &#8211; it&#8217;s just a SORT UNIQUE that doesn&#8217;t have to sort because its input comes from an index. Each branch had to read all the rows. Look at the big table: we read 100000 rows and use 4MB of memory to sort them in order to deduplicate them. But it&#8217;s an intersection and we have a small table that has only 10 rows. We know that the result cannot be large. Then a more efficient way would be to read the small table and for each row check if they are in the big one &#8211; through an index access. We still have to deduplicate, but we do that at the end, on the small rowset.</p>
<p>And this is exactly what the Set to Join Conversion is doing. Let&#8217;s force it with a hint:</p>
<pre><code>SQL&gt; select /*+ SET_TO_JOIN(@"SET$1") */ * from DEMO1 intersect select * from DEMO2;
         N
 ----------
          1
          2
          3
          4
          5
          6
          7
          8
          9
         10
10 rows selected.
SQL&gt; select * from table(dbms_xplan.display_cursor(format=&gt;'allstats last'));
PLAN_TABLE_OUTPUT
 -----------------------------------------------------------------
 SQL_ID        01z69x8w7fmu0, child number 0
 -------------------------------------
 select /*+ SET_TO_JOIN(@"SET$1") */ * from DEMO1 intersect select *
 from DEMO2
Plan hash value: 169945296
------------------------------------------------------------------
 | Id  | Operation           | Name    | Starts | E-Rows | A-Rows |
 ------------------------------------------------------------------
 |   0 | SELECT STATEMENT    |         |      1 |        |     10 |
 |   1 |  SORT UNIQUE NOSORT |         |      1 |     10 |     10 |
 |   2 |   NESTED LOOPS SEMI |         |      1 |     10 |     10 |
 |   3 |    INDEX FULL SCAN  | DEMO2_N |      1 |     10 |     10 |
 |*  4 |    INDEX UNIQUE SCAN| DEMO1_N |     10 |    100K|     10 |
 ------------------------------------------------------------------
Predicate Information (identified by operation id):
 ---------------------------------------------------
   4 - access("DEMO1"."N"="DEMO2"."N")</code></pre>
<p>&nbsp;</p>
<p>The intersect has been transformed to a join thanks to the Set to Join transformation, and the join has been transformed to a semi-join thanks to the Partial Join Evaluation transformation. The result is clear here:</p>
<ul>
<li>No full table scan on the big table because the join is able to access with an index</li>
<li>No deduplication which needs a large workarea</li>
<li>The join can stop as soon as one row matches thanks to the semi-join</li>
<li>Deduplication occurs only on result, which is small. And here it does not even require a workarea because the rows comes sorted from the index.</li>
</ul>
<p>We can see the SET_TO_JOIN and PARTIAL_JOIN hints in the outline:</p>
<pre><code>Outline Data
 -------------
  /*+
       BEGIN_OUTLINE_DATA
       IGNORE_OPTIM_EMBEDDED_HINTS
       OPTIMIZER_FEATURES_ENABLE('12.1.0.2')
       DB_VERSION('12.1.0.2')
       ALL_ROWS
       OUTLINE_LEAF(@"SEL$02B15F54")
       MERGE(@"SEL$1")
       MERGE(@"SEL$2")
       OUTLINE(@"SET$09AAA538")
       SET_TO_JOIN(@"SET$1")
       OUTLINE(@"SEL$1")
       OUTLINE(@"SEL$2")
       OUTLINE(@"SET$1")
       INDEX(@"SEL$02B15F54" "DEMO2"@"SEL$2" ("DEMO2"."N"))
       INDEX(@"SEL$02B15F54" "DEMO1"@"SEL$1" ("DEMO1"."N"))
       LEADING(@"SEL$02B15F54" "DEMO2"@"SEL$2" "DEMO1"@"SEL$1")
       USE_NL(@"SEL$02B15F54" "DEMO1"@"SEL$1")
       PARTIAL_JOIN(@"SEL$02B15F54" "DEMO1"@"SEL$1")
       END_OUTLINE_DATA
   */</code></pre>
<p>So we are in 12.1.0.2 and we need a hint for that. Let&#8217;s go to 12.1.0.2.1 (which implicitely set _convert_set_to_join=true).</p>
<pre><code>PLAN_TABLE_OUTPUT
 -----------------------------------------------------------------------------
 SQL_ID        9fpg8nyjaqb5f, child number 1
 -------------------------------------
 select * from DEMO1 intersect select * from DEMO2
Plan hash value: 118900122
------------------------------------------------------------------------------
 | Id  | Operation           | Name    | Starts | E-Rows | A-Rows || Used-Mem |
 ------------------------------------------------------------------------------
 |   0 | SELECT STATEMENT    |         |      1 |        |     10 ||          |
 |   1 |  HASH UNIQUE        |         |      1 |     10 |     10 || 1260K (0)|
 |   2 |   NESTED LOOPS SEMI |         |      1 |     10 |     10 ||          |
 |   3 |    INDEX FULL SCAN  | DEMO2_N |      1 |     10 |     10 ||          |
 |*  4 |    INDEX UNIQUE SCAN| DEMO1_N |     10 |    100K|     10 ||          |
 ------------------------------------------------------------------------------
Predicate Information (identified by operation id):
 ---------------------------------------------------
   4 - access("DEMO1"."N"="DEMO2"."N")
Note
 -----
    - this is an adaptive plan</code></pre>
<p>Ok, we have the Set to Join Conversion here in 12.1.0.2.1</p>
<p>But don&#8217;t you see another difference?<br />
.<br />
.<br />
.<br />
.<br />
The deduplication needs a workarea here. It is not a NOSORT operation &#8211; even if the result comes from the index. It seems that the CBO cannot guarantee that the result comes sorted. The clue is in the execution plan note.</p>
<p>But that&#8217;s for a future blog post.</p>
<p>L’article <a href="https://www.dbi-services.com/blog/oracle-121021-set-to-join-conversion/">Oracle 12.1.0.2.1 Set to Join Conversion</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/oracle-121021-set-to-join-conversion/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Partial Join Evaluation in Oracle 12c</title>
		<link>https://www.dbi-services.com/blog/partial-join-evaluation-in-oracle-12c/</link>
					<comments>https://www.dbi-services.com/blog/partial-join-evaluation-in-oracle-12c/#respond</comments>
		
		<dc:creator><![CDATA[Oracle Team]]></dc:creator>
		<pubDate>Sun, 13 Jul 2014 23:21:00 +0000</pubDate>
				<category><![CDATA[Database management]]></category>
		<category><![CDATA[Oracle]]></category>
		<category><![CDATA[Cost Based Optimizer]]></category>
		<category><![CDATA[Oracle 12c]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/partial-join-evaluation-in-oracle-12c/</guid>

					<description><![CDATA[<p>By Franck Pachot . Do you think that it&#8217;s better to write semi-join SQL statements with IN(), EXISTS(), or to do a JOIN? Usually, the optimizer will evaluate the cost and do the transformation for you. And in this area, one more transformation has been introduced in 12c which is the Partial Join Evaluation (PJE). [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/partial-join-evaluation-in-oracle-12c/">Partial Join Evaluation in Oracle 12c</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h2>By Franck Pachot</h2>
<p>.<br />
Do you think that it&#8217;s better to write semi-join SQL statements with IN(), EXISTS(), or to do a JOIN? Usually, the optimizer will evaluate the cost and do the transformation for you. And in this area, one more transformation has been introduced in 12c which is the Partial Join Evaluation (PJE).</p>
<p>First, let&#8217;s have a look at the 11g behaviour. For that example, I use the SCOTT schema, but I hire a lot more employees in departement 40:</p>
<pre><code>
SQL&gt; alter table EMP modify empno number(10);
Table altered.
SQL&gt; insert into EMP(empno,deptno) select rownum+10000,40 from EMP,(select * from dual connect by level
</code></pre>
<p>Why department 40? I&#8217;ll explain it below, but I let you think about it before. In the default SCOTT schema, there is a department 40 in DEPT table, but which has no employees in EMP. And the new transformation is not useful in that case.</p>
<h3>11g behaviour</h3>
<p>Now, I&#8217;m running the following query to check all the departments that have at least one employee:<br />
I can write it with IN:</p>
<pre><code>
PLAN_TABLE_OUTPUT
 ------------------------------------------------------------------------
 SQL_ID 6y71msam9w32r, child number 0
 -------------------------------------
 select distinct deptno,dname from dept
 where deptno in ( select deptno from emp)
&nbsp;
Plan hash value: 1754319153
&nbsp;
------------------------------------------------------------------------
| Id  | Operation          | Name | Starts | E-Rows | A-Rows | Buffers |
------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |      1 |        |      4 |      15 |
|*  1 |  HASH JOIN SEMI    |      |      1 |      4 |      4 |      15 |
|   2 |   TABLE ACCESS FULL| DEPT |      1 |      4 |      4 |       7 |
|   3 |   TABLE ACCESS FULL| EMP  |      1 |  15068 |    388 |       8 |
------------------------------------------------------------------------
Predicate Information (identified by operation id):
 ---------------------------------------------------
&nbsp;
   1 - access("DEPTNO"="DEPTNO")
</code></pre>
<p>or with EXISTS:</p>
<pre><code>
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------
SQL_ID  cbpa3zjtzfzrn, child number 0
-------------------------------------
select distinct deptno,dname from dept 
 where exists ( select 1 from emp where emp.deptno=dept.deptno)
&nbsp;
Plan hash value: 1754319153
&nbsp;
------------------------------------------------------------------------
| Id  | Operation          | Name | Starts | E-Rows | A-Rows | Buffers |
------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |      1 |        |      4 |      15 |
|*  1 |  HASH JOIN SEMI    |      |      1 |      4 |      4 |      15 |
|   2 |   TABLE ACCESS FULL| DEPT |      1 |      4 |      4 |       7 |
|   3 |   TABLE ACCESS FULL| EMP  |      1 |  15068 |    388 |       8 |
------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
&nbsp;
   1 - access("DEPTNO"="DEPTNO")
</code></pre>
<p>Both are good. We didn&#8217;t have to read the whole EMP table. I have 15000 rows in my table, I do a full scan on it, but look at the A-Rows: only 388 rows were actually read.</p>
<p>The HASH JOIN first read the DEPT table in order to build the hash table. So it already knows that we cannot have more than 4 distinct departments.</p>
<p>Then we do the join to EMP just to check which of those departments have an employee. But we can stop as soon as we find the 4 departments. This is the reason why we have read only 388 rows here. And this is exactly what a Semi Join is: we don&#8217;t need all the matching rows, we return at most one row per matching pair.</p>
<p>Ok. What if we write the join ourselves?</p>
<pre><code>
PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------
SQL_ID  2xjj9jybqja87, child number 1
-------------------------------------
select distinct deptno,dname from dept join emp using(deptno)
&nbsp;
Plan hash value: 2962452962
&nbsp;
-------------------------------------------------------------------------
| Id  | Operation           | Name | Starts | E-Rows | A-Rows | Buffers |
-------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |      1 |        |      4 |     129 |
|   1 |  HASH UNIQUE        |      |      1 |  15068 |      4 |     129 |
|*  2 |   HASH JOIN         |      |      1 |  15068 |  14014 |     129 |
|   3 |    TABLE ACCESS FULL| DEPT |      1 |      4 |      4 |       7 |
|   4 |    TABLE ACCESS FULL| EMP  |      1 |  15068 |  14014 |     122 |
-------------------------------------------------------------------------
&nbsp;
Predicate Information (identified by operation id):
---------------------------------------------------
&nbsp;
   2 - access("DEPT"."DEPTNO"="EMP"."DEPTNO")
</code></pre>
<p>Bad luck. We have to read all the rows. More rows and more buffers.</p>
<h3>12c behaviour</h3>
<p>Let&#8217;s do the same in 12.1.0.1:</p>
<pre><code>
PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------
SQL_ID  2xjj9jybqja87, child number 0
-------------------------------------
select distinct deptno,dname from dept join emp using(deptno)
&nbsp;
Plan hash value: 1629510749
&nbsp;
-------------------------------------------------------------------------
| Id  | Operation           | Name | Starts | E-Rows | A-Rows | Buffers |
-------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |      1 |        |      4 |      14 |
|   1 |  HASH UNIQUE        |      |      1 |      4 |      4 |      14 |
|*  2 |   HASH JOIN SEMI    |      |      1 |      4 |      4 |      14 |
|   3 |    TABLE ACCESS FULL| DEPT |      1 |      4 |      4 |       7 |
|   4 |    TABLE ACCESS FULL| EMP  |      1 |  15068 |    388 |       7 |
-------------------------------------------------------------------------
&nbsp;
Predicate Information (identified by operation id):
---------------------------------------------------
&nbsp;
   2 - access("DEPT"."DEPTNO"="EMP"."DEPTNO")
</code></pre>
<p>Same plan but less rows have been read. If we check the outlines, we see the new feature:</p>
<pre><code>
      PARTIAL_JOIN(@"SEL$58A6D7F6" "EMP"@"SEL$1")
</code></pre>
<p>And here is what we see in the optimizer trace:</p>
<pre><code>
OPTIMIZER STATISTICS AND COMPUTATIONS
PJE: Checking validity of partial join eval on query block SEL$58A6D7F6 (#1)
PJE: Passed validity of partial join eval by query block SEL$58A6D7F6 (#1)
PJE: Partial join eval conversion for query block SEL$58A6D7F6 (#1).
PJE: Table marked for partial join eval: EMP[EMP]#1
</code></pre>
<p>The hints that control the feature are PARTIAL_JOIN and NO_PARTIAL_JOIN and the transformation is enabled by _optimizer_partial_join_eval which appeared in 12<i>c</i>.</p>
<p>But of course, the optimization is useful only when we have all the values at the beginning of the table. This is why I added at least one employee in department 40. If there are some rows in DEPT that have no matching row in EMP, then Oracle cannot know the result before reaching the end of the table.</p>
<p>L’article <a href="https://www.dbi-services.com/blog/partial-join-evaluation-in-oracle-12c/">Partial Join Evaluation in Oracle 12c</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/partial-join-evaluation-in-oracle-12c/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>ROWNUM vs ROW_NUMBER() and 12c fetch first</title>
		<link>https://www.dbi-services.com/blog/oracle-rownum-vs-rownumber-and-12c-fetch-first/</link>
					<comments>https://www.dbi-services.com/blog/oracle-rownum-vs-rownumber-and-12c-fetch-first/#respond</comments>
		
		<dc:creator><![CDATA[Oracle Team]]></dc:creator>
		<pubDate>Mon, 05 May 2014 03:24:00 +0000</pubDate>
				<category><![CDATA[Database management]]></category>
		<category><![CDATA[Oracle]]></category>
		<category><![CDATA[Cost Based Optimizer]]></category>
		<category><![CDATA[Oracle 12c]]></category>
		<category><![CDATA[SQL]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/oracle-rownum-vs-rownumber-and-12c-fetch-first/</guid>

					<description><![CDATA[<p>By Franck Pachot . Prior to Oracle 12c, there were two ways to do &#8216;top-n&#8217; queries: use rownum after sorting rows with &#8220;order by&#8221; use row_number() over (order by) Top-n queries are usually required for result pagination. The application shows the first 10 rows, then the next 10 on the following page, etc. In these [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/oracle-rownum-vs-rownumber-and-12c-fetch-first/">ROWNUM vs ROW_NUMBER() and 12c fetch first</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h2>By Franck Pachot</h2>
<p>.<br />
Prior to Oracle 12c, there were two ways to do &#8216;top-n&#8217; queries:</p>
<ul>
<li>use rownum after sorting rows with &#8220;order by&#8221;</li>
<li>use row_number() over (order by)</li>
</ul>
<p>Top-n queries are usually required for result pagination. The application shows the first 10 rows, then the next 10 on the following page, etc. In these queries, user interactions are often stateless, so we cannot just open a cursor and fetch 10 rows on each user request.</p>
<p>In both case you will need a subquery. In the former case you need it because otherwise the order by will be applied after the rownum filter. In the latter case you need it because you cannot use analytic functions in the where clause.</p>
<p>But there is a big difference, which is very important to get the optimal plan, and this is what I will expose here before showing the new 12c syntax.</p>
<h3>Query with rownum</h3>
<pre><code>PLAN_TABLE_OUTPUT
 SQL_ID 7x2wat0fhwdn9, child number 0
  -------------------------------------
  select * from (
     select * from test where contract_id=500
     order by start_validity
  ) where rownum &lt;=10
  order by start_validity
  Plan hash value: 2207676858
  --------------------------------------------------------------------------------------
  | Id  | Operation                     | Name    | Starts | E-Rows | A-Rows | Buffers |
  --------------------------------------------------------------------------------------
  |   0 | SELECT STATEMENT              |         |      1 |        |     10 |      14 |
  |*  1 |  COUNT STOPKEY                |         |      1 |        |     10 |      14 |
  |   2 |   VIEW                        |         |      1 |     10 |     10 |      14 |
  |   3 |    TABLE ACCESS BY INDEX ROWID| TEST    |      1 |     10 |     10 |      14 |
  |*  4 |     INDEX RANGE SCAN          | TEST_PK |      1 |        |     16 |       4 |
  --------------------------------------------------------------------------------------
  Predicate Information (identified by operation id):
  ---------------------------------------------------
  1 - filter(ROWNUM&lt;=10)
  4 - access("CONTRACT_ID"=500)</code></pre>
<p>This is good. The optimizer knows that we retrieve only 10 rows and the index access was chosen for that because it returns rows in the right order without the need to sort them.</p>
<h3>Query with row_number()</h3>
<pre><code> PLAN_TABLE_OUTPUT
  SQL_ID gmmz59f64zftf, child number 0
  -------------------------------------
  select * from (
    select test.*,row_number()over(order by start_validity) rn 
    from test where contract_id=500
  ) where rn &lt;=10
 order by start_validity
 Plan hash value: 1795822849
 ------------------------------------------------------------------------------
  | Id  | Operation                | Name | Starts | E-Rows | A-Rows | Buffers |
  ------------------------------------------------------------------------------
  |   0 | SELECT STATEMENT         |      |      1 |        |     10 |    4397 |
  |*  1 |  VIEW                    |      |      1 |     10 |     10 |    4397 |
  |*  2 |   WINDOW SORT PUSHED RANK|      |      1 |   2000 |     10 |    4397 |
  |*  3 |    TABLE ACCESS FULL     | TEST |      1 |   2000 |   2000 |    4397 |
  ------------------------------------------------------------------------------
 Predicate Information (identified by operation id):
  ---------------------------------------------------
 1 - filter("RN"&lt;=10) 
 2 - filter(ROW_NUMBER() OVER ( ORDER BY "START_VALIDITY")&lt;=10) 
 3 - filter("CONTRACT_ID"=500)</code></pre>
<p>This is not good. A full table scan reads all rows, whereas an index can give those 10 rows quickly. Reading and sorting thousands of rows instead of just reading a dozen. So why the index scan was not chosen? In that case I usually force the plan to the one that I expect, so that I can see which bad estimation has driven the CBO choice. Let&#8217;s see the execution plan when forcing range scan :</p>
<pre><code> PLAN_TABLE_OUTPUT
  SQL_ID axxrwar6s9275, child number 0
  -------------------------------------
  select * from (
    select /*+ index(TEST) */ test.*,row_number()over(order by start_validity) rn 
    from test where contract_id=500
 ) where rn&lt;=10
 order by start_validity
  Plan hash value: 1912639229
 --------------------------------------------------------------------------------------
  | Id  | Operation                     | Name    | Starts | E-Rows | A-Rows | Buffers |
  --------------------------------------------------------------------------------------
  |   0 | SELECT STATEMENT              |         |      1 |        |     10 |      15 |
  |*  1 |  VIEW                         |         |      1 |     10 |     10 |      15 |
  |*  2 |   WINDOW NOSORT STOPKEY       |         |      1 |   2000 |     10 |      15 |
  |   3 |    TABLE ACCESS BY INDEX ROWID| TEST    |      1 |   2000 |     11 |      15 |
  |*  4 |     INDEX RANGE SCAN          | TEST_PK |      1 |   2000 |     11 |       4 |
  --------------------------------------------------------------------------------------
 Predicate Information (identified by operation id):
  ---------------------------------------------------
 1 - filter("RN"&lt;=10)
 2 - filter(ROW_NUMBER() OVER ( ORDER BY "START_VALIDITY")&lt;=10)
  4 - access("CONTRACT_ID"=500)</code></pre>
<p>I didn&#8217;t show the cost here. The estimated number of rows is sufficient to understand. The optimizer has estimated the cost of the index access without knowing that we retreive at most 10 rows. When using rownum &lt; 10, the optimizer implicitly add a first_rows(10). But not with row_number().</p>
<p>With the row_number() we have to do it ourselves:</p>
<pre><code>PLAN_TABLE_OUTPUT
  SQL_ID 32m4yadk1ypsn, child number 0
  -------------------------------------
  select /*+ FIRST_ROWS(10) */ * from (
    select test.*,row_number()over(order by start_validity) rn 
    from test where contract_id=500
  ) where rn&lt;=10
 order by start_validity 
  Plan hash value: 1912639229
 --------------------------------------------------------------------------------------
  | Id  | Operation                     | Name    | Starts | E-Rows | A-Rows | Buffers |
  --------------------------------------------------------------------------------------
  |   0 | SELECT STATEMENT              |         |      1 |        |     10 |      15 |
  |*  1 |  VIEW                         |         |      1 |     10 |     10 |      15 |
  |*  2 |   WINDOW NOSORT STOPKEY       |         |      1 |     10 |     10 |      15 |
  |   3 |    TABLE ACCESS BY INDEX ROWID| TEST    |      1 |     10 |     11 |      15 |
  |*  4 |     INDEX RANGE SCAN          | TEST_PK |      1 |        |     11 |       4 |
  --------------------------------------------------------------------------------------
 Predicate Information (identified by operation id):
  ---------------------------------------------------
 1 - filter("RN"&lt;=10) 
 2 - filter(ROW_NUMBER() OVER ( ORDER BY "START_VALIDITY")&lt;=10) 
 4 - access("CONTRACT_ID"=500)</code></pre>
<p>Now the index range scan is chosen, with the right cardinality estimation.</p>
<p>So which solution it the best one? I prefer row_number() for several reasons:</p>
<ul>
<li>I like analytic functions. They have larger possibilities, such as setting the limit as a percentage of total number of rows for example.</li>
<li>11g documentation for rownum says:<br />
The ROW_NUMBER built-in SQL function provides superior support for ordering the results of a query</li>
<li>12c allows the ANSI syntax ORDER BY&#8230;FETCH FIRST&#8230;ROWS ONLY which is translated to row_number() predicate</li>
<li>12c documentation for rownum adds:<br />
The row_limiting_clause of the SELECT statement provides superior support</li>
<li>rownum has <a href="http://jonathanlewis.wordpress.com/2008/11/11/first_rows_n/trackback">first_rows_n issues</a> as well</li>
</ul>
<p>As you can see, Oracle does not say that rownum is de-supported. Just that there is another function with &#8216;superior support&#8217;. Which is the same I presume.</p>
<p>So, I use row_number() in 11g or the ANSI syntax in 12c but in both cases I have to add the FIRST_ROWS(n) hint in orde rto get the right plan. Warning: don&#8217;t use the old FIRST_ROWS hint anymore which was rule based and is deprecated. The right way to tell Oracle that you will fetch only n rows is the FIRST_ROWS(n) hint.</p>
<p>Now here is the same query with the 12c syntax.</p>
<h3>Query with 12c syntax</h3>
<pre><code>PLAN_TABLE_OUTPUT
  SQL_ID 49m5a3f33cmd0, child number 0
  -------------------------------------
  select /*+ FIRST_ROWS(10) */ * from test where contract_id=500 
  order by start_validity fetch first 10 rows only
 Plan hash value: 1912639229
 --------------------------------------------------------------------------------------
  | Id  | Operation                     | Name    | Starts | E-Rows | A-Rows | Buffers |
  --------------------------------------------------------------------------------------
  |   0 | SELECT STATEMENT              |         |      1 |        |     10 |      15 |
  |*  1 |  VIEW                         |         |      1 |     10 |     10 |      15 |
  |*  2 |   WINDOW NOSORT STOPKEY       |         |      1 |     10 |     10 |      15 |
  |   3 |    TABLE ACCESS BY INDEX ROWID| TEST    |      1 |     10 |     11 |      15 |
  |*  4 |     INDEX RANGE SCAN          | TEST_PK |      1 |        |     11 |       4 |
  --------------------------------------------------------------------------------------
 Predicate Information (identified by operation id):
  ---------------------------------------------------
 1 - filter("from$_subquery$_002"."rowlimit_$$_rownumber" &lt;=10)
 2 - filter(ROW_NUMBER() OVER ( ORDER BY "TEST"."START_VALIDITY") &lt;=10 )  
 4 - access("CONTRACT_ID"=500)</code></pre>
<p>The 12c syntax has been translated with row_number() predicates and I added the FIRST_ROWS(10) hint in order to have the right estimations.</p>
<p>L’article <a href="https://www.dbi-services.com/blog/oracle-rownum-vs-rownumber-and-12c-fetch-first/">ROWNUM vs ROW_NUMBER() and 12c fetch first</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/oracle-rownum-vs-rownumber-and-12c-fetch-first/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 
Lazy Loading (feed)

Served from: www.dbi-services.com @ 2026-10-18 00:18:18 by W3 Total Cache
-->