<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:blogger="http://schemas.google.com/blogger/2008" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" gd:etag="W/&quot;Dk4FRXo8eCp7ImA9WhJQEko.&quot;"><id>tag:blogger.com,1999:blog-28935478</id><updated>2012-07-26T04:55:14.470+01:00</updated><title>Oracle Tips</title><subtitle type="html">Oracle Tips, Tricks and Information.                                                                                                                           David Fitzjarrell</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://oratips-ddf.blogspot.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>ddf</name><uri>http://www.blogger.com/profile/01396093972232442857</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="24" src="http://bp3.blogger.com/_NFgX5tgJhZQ/SDR1AEtRBoI/AAAAAAAAAAM/L6UBwufG5fA/S220/David1.jpg" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>68</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/blogspot/oratips" /><feedburner:info xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" uri="blogspot/oratips" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry gd:etag="W/&quot;CE8CR30-eCp7ImA9WhJSFUU.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-2901447303446439208</id><published>2012-06-26T19:58:00.000+01:00</published><updated>2012-07-06T14:54:26.350+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-07-06T14:54:26.350+01:00</app:edited><title>Nullified Remains</title><content type="html">Just recently a service request was opened with Oracle Support regarding the "when others then null" exception handler when it was found in an Oracle-supplied form for the Oracle Inventory application from the E-Business suite.  It appears the person who opened the SR believes (and rightly so) this is not the most informative of exception handlers nor is it proper coding practice; the request was opened in hopes of disallowing such constructs.  I originally posted:&lt;br /&gt;&lt;br /&gt;

"Sadly Oracle Support may not do anything about this for two reasons: &lt;br /&gt; &lt;br /&gt;

1) It's valid code.&lt;br /&gt;
2) It's not causing another error to surface.
"&lt;br /&gt;&lt;br /&gt;

Amending my post to reflect the actual nature of the SR it's now clear why this was opened -- Oracle should &lt;b&gt;&lt;em&gt;never&lt;/em&gt;&lt;/b&gt; issue forms or production code using this exception 'non-handler' so it's understandable why Oracle Support filed a bug report (bug number 14237626) for it.  Why is coding "when others then null" not the best idea?  Let's look at some examples and see what could be hidden behind that glorious construct.  Creating a table used in a previous post:&lt;br /&gt;&lt;br /&gt;

&lt;pre&gt;&lt;span style="color:#6600cc;"&gt;SQL&gt; --
SQL&gt; -- Create a test table
SQL&gt; --
SQL&gt; create table pktest(
2 pk number,
3 val1 number,
4 val2 number,
5 val3 number
6 );&lt;p&gt;

Table created.&lt;p&gt;

SQL&gt;
SQL&gt; --
SQL&gt; -- Add the primary key
SQL&gt; --
SQL&gt; alter table pktest add constraint pktestpk primary key (pk);&lt;p&gt;

Table altered.&lt;p&gt;

SQL&gt;
SQL&gt; --
SQL&gt; -- Insert data
SQL&gt; --
SQL&gt; begin
2 for i in 1..25 loop
3 insert into pktest
4 values(i,i+1,i+2,i+3);
5 end loop;
6 end;
7 /&lt;p&gt;

PL/SQL procedure successfully completed.&lt;p&gt;

SQL&gt;
SQL&gt; --
SQL&gt; -- Commit the data
SQL&gt; --
SQL&gt; commit;&lt;p&gt;

Commit complete.&lt;p&gt;

SQL&gt;&lt;/span&gt;&lt;/p&gt;&lt;/pre&gt;Using this table/key combination let's try a transaction that will generate an error; we'll hide it by using "when others then null":

&lt;pre&gt;&lt;span style="color:#cc0000;"&gt;SQL&gt; --
SQL&gt; -- Try and insert an existing key
SQL&gt; -- value into the test table
SQL&gt; --
SQL&gt; -- Intentionally obscure the original
SQL&gt; -- error
SQL&gt; --
SQL&gt; begin
2 insert into pktest values (1,2, 3, 4);
3 exception
4 when others then
5 null;
6 end;
7 /&lt;p&gt;

PL/SQL procedure successfully completed.&lt;p&gt;

SQL&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/span&gt;An insert was intentionally attempted that violates the primary key constraint yet no error was thrown -- how lovely.  The worst part of this is the code is declared to have run successfully; it appears that the insert was executed without error (which we know is NOT true) so the end user who ran this glorious piece of code has no idea his or her insert failed.  These, of course, are not the only errors that can be hidden from view as other, more insidious errors can be ignored:&lt;br /&gt;&lt;br /&gt;

&lt;pre&gt;&lt;span style="color:#cc0000;"&gt;SQL&gt;
SQL&gt; --
SQL&gt; -- Artificially generate some rather severe
SQL&gt; -- errors
SQL&gt; --
SQL&gt; -- Ignore them in the exception handler
SQL&gt; --
SQL&gt; declare
  2          bad             exception;
  3          reallybad       exception;
  4          trulyreallybad  exception;
  5
  6          pragma exception_init(bad, -43); -- remote operation failed
  7          pragma exception_init(reallybad, -1019); -- unable to allocate memory on user side
  8          pragma exception_init(trulyreallybad, -1034); -- Oracle not available
  9  begin
 10          begin
 11                  raise bad;
 12          exception
 13                  when others then null;
 14          end;
 15          begin
 16                  raise reallybad;
 17          exception
 18                  when others then null;
 19          end;
 20          begin
 21                  raise trulyreallybad;
 22          exception
 23                  when others then null;
 24          end;
 25  end;
 26  /&lt;p&gt;

PL/SQL procedure successfully completed.&lt;p&gt;

SQL&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/span&gt;Notice that even these errors were not reported, including the 'Oracle not available' error indicating connection to the database failed resulting in nothing being executed.  Oracle reported that the PL/SQL ran successfully (meaning without error) all because of the 'when others then null' error mis-handler.&lt;br /&gt;&lt;br /&gt;

All of this transpires due to a lack of knowledge of what errors could be expected, a bout of laziness on the part of the developer or both -- "Gee, I don't know what other errors to expect but they can't be serious so I'll ignore them."  Any error is serious to the end user as it prevents work from being completed, and if those errors are ignored and the code block execution looks successful then, when problems arise because of missing data, the issue is harder to troubleshoot.  Absent an error message, no matter how trivial that error may seem to the developer, the end user has no indication that the insert/update/delete failed and has nothing to report to the help desk.  Codng such mis-handlers also is a disservice to the developer/programmer as they won't know what caused the problem any more than the user who ran the code.  Not knowing the cause makes the solution that much more difficult to find.  In that case everybody loses.&lt;br /&gt;&lt;br /&gt;

This is the third post on error handling and error reporting I've written, two on properly reporting error text so the end user can talk intellitently to the help desk on what went wrong, and this one, on making sure there IS an error message to report.  All three should be read as a set (in my opinion) so that error messages are reported and the text that the end user sees provides useful information to the service desk personnel.  This falls, really, on the developers and programmers as the error handlers they code directly affect what the end users see when errors arise, and the error messages should be clear enough for a non-technical Oracle user to describe to the help desk representatives with whom they speak.  A little effort on the development side goes a long way in making troubleshooting easier if, or when, the time comes.&lt;br/&gt;&lt;br /&gt;

Third time is a charm.&lt;br /&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=mMK5mzMtTXY:CqQP0UJNKBI:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=mMK5mzMtTXY:CqQP0UJNKBI:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://oratips-ddf.blogspot.com/feeds/2901447303446439208/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=2901447303446439208" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/2901447303446439208?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/2901447303446439208?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2012/06/nullified-remains.html" title="Nullified Remains" /><author><name>David Fitzjarrell</name><uri>https://plus.google.com/102215427046411604711</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-y2hTTVpTe4o/AAAAAAAAAAI/AAAAAAAAAGw/KabdyVeaX9M/s512-c/photo.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;CEUNRn4yfCp7ImA9WhVaF0U.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-944595021471798523</id><published>2012-06-15T18:34:00.003+01:00</published><updated>2012-06-15T18:44:57.094+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-06-15T18:44:57.094+01:00</app:edited><title>Compound Interest</title><content type="html">Oracle 11g offers a new twist on triggers, the compound trigger, a trigger than can act both before and after an update, insert or delete has occurred.  This makes possible the abilty in one trigger to perform processing similar to a stored procedure without having to write such a procedure to call from a traditional trigger.  Compound triggers can be used to avoid the dreaded mutating table error or to process and accept or reject updates to a table based upon desired criteria.  Before we look at such an example a description of how a compound trigger is constructed is in order.&lt;br /&gt;&lt;br /&gt;

Compound triggers can have up to four sections:&lt;br /&gt;&lt;br /&gt;

 the BEFORE section&lt;br /&gt;
 the BEFORE EACH ROW section&lt;br /&gt;
 the AFTER EACH ROW section&lt;br /&gt;
 the AFTER section&lt;br /&gt;&lt;br /&gt;

At least two of the sections must be included (including only one of the four would result in traditional trigger) and it does not matter which two of the sections are used.  For example such a trigger can include a BEFORE EACH ROW section and an AFTER section; the two sections need not be 'matched' (BEFORE, BEFORE EACH ROW, for instance).  Also the COMPOUND TRIGGER STATEMENT must be included so Oracle will recognize the above four constructs and treat them accordingly.  The general syntax is:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;
create or replace trigger &amp;lt;trigger name&amp;gt;
for &amp;lt;insert|update|delete&amp;gt; &amp;lt;of column_name&amp;gt; on &amp;lt;tablename&amp;gt;
COMPOUND TRIGGER
&amp;lt;declare section&amp;gt;
BEFORE
&amp;lt;before section&amp;gt;
BEFORE EACH ROW
&amp;lt;before each row section&amp;gt;
AFTER EACH ROW
&amp;lt;after each row section&amp;gt;
AFTER
&amp;lt;after section&amp;gt;
END;
/&lt;/span&gt;&lt;/pre&gt;

Since compound triggers are relatively new and many may not have had the opportunity to write or use them I have provided a working example.  Setting the stage for this trigger HR has set a restriction on the size of a raise to be given; based on the department the raise cannot exceed 12 percent of the department average salary.  A compound trigger can be used to process the raise amounts assigned.  Such a compound trigger is shown below, along with several ways of executing the raises:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;
SQL&gt; create or replace trigger check_raise_on_avg
  2  for update of sal on emp
  3  COMPOUND TRIGGER
  4    Twelve_Percent        constant number:=0.12;
  5
  6    -- Declare collection type and variable:
  7
  8    TYPE Department_Salaries_t  IS TABLE OF Emp.Sal%TYPE
  9                                  INDEX BY VARCHAR2(80);
 10    Department_Avg_Salaries     Department_Salaries_t;
 11    TYPE Sal_t             IS TABLE OF Emp.Sal%TYPE;
 12    Avg_Salaries                Sal_t;
 13    TYPE Deptno_t       IS TABLE OF Emp.Deptno%TYPE;
 14    Department_IDs              Deptno_t;
 15
 16    BEFORE STATEMENT IS
 17    BEGIN
 18      SELECT               AVG(e.Sal), NVL(e.Deptno, -1)
 19        BULK COLLECT INTO  Avg_Salaries, Department_IDs
 20        FROM               Emp e
 21        GROUP BY           e.Deptno;
 22      FOR j IN 1..Department_IDs.COUNT() LOOP
 23        Department_Avg_Salaries(Department_IDs(j)) := Avg_Salaries(j);
 24      END LOOP;
 25    END BEFORE STATEMENT;
 26
 27    AFTER EACH ROW IS
 28    BEGIN
 29      IF :NEW.Sal - :Old.Sal &amp;gt;
 30        Twelve_Percent*Department_Avg_Salaries(:NEW.Deptno)
 31      THEN
 32        Raise_Application_Error(-20000, 'Raise too large');
 33      END IF;
 34    END AFTER EACH ROW;
 35  END Check_Raise_On_Avg;
 36  /

Trigger created.

SQL&gt; select empno, sal from emp;

     EMPNO        SAL
---------- ----------
      7369        800
      7499       1600
      7521       1250
      7566       2975
      7654       1250
      7698       2850
      7782       2450
      7788       3000
      7839       5000
      7844       1500
      7876       1100
      7900        950
      7902       3000
      7934       1300

14 rows selected.

SQL&gt;
SQL&gt; update emp set sal=sal*1.10 where empno = 7369;

1 row updated.

SQL&gt;
SQL&gt; select empno, sal from emp;

     EMPNO        SAL
---------- ----------
      7369        880
      7499       1600
      7521       1250
      7566       2975
      7654       1250
      7698       2850
      7782       2450
      7788       3000
      7839       5000
      7844       1500
      7876       1100
      7900        950
      7902       3000
      7934       1300

14 rows selected.

SQL&gt;
SQL&gt; rollback;

Rollback complete.

SQL&gt;
SQL&gt; update emp set sal=sal*1.08 where deptno = 20;

5 rows updated.

SQL&gt;
SQL&gt; select empno, sal from emp;

     EMPNO        SAL
---------- ----------
      7369        864
      7499       1600
      7521       1250
      7566       3213
      7654       1250
      7698       2850
      7782       2450
      7788       3240
      7839       5000
      7844       1500
      7876       1188
      7900        950
      7902       3240
      7934       1300

14 rows selected.

SQL&gt;
SQL&gt; rollback;

Rollback complete.

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

Does the trigger reject raises?  It certainly does:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;
SQL&gt; update emp set sal=sal*1.10 where deptno = 30;
update emp set sal=sal*1.10 where deptno = 30
       *
ERROR at line 1:
ORA-20000: Raise too large
ORA-06512: at "BING.CHECK_RAISE_ON_AVG", line 30
ORA-04088: error during execution of trigger 'BING.CHECK_RAISE_ON_AVG'


SQL&gt;
SQL&gt; select empno, sal from emp;

     EMPNO        SAL
---------- ----------
      7369        800
      7499       1600
      7521       1250
      7566       2975
      7654       1250
      7698       2850
      7782       2450
      7788       3000
      7839       5000
      7844       1500
      7876       1100
      7900        950
      7902       3000
      7934       1300

14 rows selected.

SQL&gt; update emp set sal=sal*1.10 where empno = 7698;
update emp set sal=sal*1.10 where empno = 7698
       *
ERROR at line 1:
ORA-20000: Raise too large
ORA-06512: at "BING.CHECK_RAISE_ON_AVG", line 30
ORA-04088: error during execution of trigger 'BING.CHECK_RAISE_ON_AVG'


SQL&gt;
SQL&gt; select empno, sal from emp;

     EMPNO        SAL
---------- ----------
      7369        800
      7499       1600
      7521       1250
      7566       2975
      7654       1250
      7698       2850
      7782       2450
      7788       3000
      7839       5000
      7844       1500
      7876       1100
      7900        950
      7902       3000
      7934       1300

14 rows selected.

SQL&gt;
&lt;/span&gt;&lt;/pre&gt;

The first rejected update unfortunately disallowed all of the raises based on the failure of a few; the second update shows one employee where a 10 percent raise would be greater than 12 percent of the departmental average salary.  Of course it is usually rare to see such a large raise given throughout an entire department so such occurrences would be few as raises are usually processed (outside of cost-of-living adjustments) on an individual basis.&lt;br /&gt;&lt;br /&gt;

Please note that doing the above in a traditional trigger would have resulted in a mutating table error since the table being updated cannot be queried during the update; all successful raises were processed and no such error was thrown.&lt;br /&gt;&lt;br /&gt;

Compound triggers are a nice addition to an already robust database system; they may not be commonplace but having them available certainly makes application development simpler as business rules that may be unenforceable using a regular trigger can be successfully implemented.  They may be considered as 'specialty tools' in the database realm but remember that plumbers, builders and mechanics also have tools they only use once in a while and when the situation arises where a compound trigger can be useful it's good to have them around.&lt;br /&gt;&lt;br /&gt;

So when do I get my raise?&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=EhbzaLkZceU:WUVsCgQ3Z38:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=EhbzaLkZceU:WUVsCgQ3Z38:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://oratips-ddf.blogspot.com/feeds/944595021471798523/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=944595021471798523" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/944595021471798523?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/944595021471798523?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2012/06/compound-interest.html" title="Compound Interest" /><author><name>David Fitzjarrell</name><uri>https://plus.google.com/102215427046411604711</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-y2hTTVpTe4o/AAAAAAAAAAI/AAAAAAAAAGw/KabdyVeaX9M/s512-c/photo.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;Dk4NRX87eyp7ImA9WhVaFk0.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-7907386764933314772</id><published>2012-02-27T20:21:00.002Z</published><updated>2012-06-13T17:29:54.103+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-06-13T17:29:54.103+01:00</app:edited><title>Collecting Thoughts</title><content type="html">Collections are an interesting lot.  They can be one of the most useful tools in the Oracle arsenal, yet they can also be very frustrating to implement.  For those unfamiliar with them a collection/varray is defined as "an ordered group of elements, all of the same type. Each element has a unique subscript that determines its position in the collection." The definition seems simple enough but it may be deceptively so.  To make matters even more confusing to use a collection you must create a database type for it to reference; a varray requires a type as well but that type can be declared in the PL/SQL block.  To clear the air a bit let's go through some examples of defining and using collections and varrays:  The first example uses a collection to store vendor ids and then process them for a report.  The code builds two 'tables' and compares the contents of them by loading collections and comparing one collection to the other; output is displayed for the conditions of the two tables being equal and the two tables not being equal:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;
SQL&gt; 
SQL&gt; set serveroutput on size 1000000
SQL&gt; 
SQL&gt; CREATE OR REPLACE type integer_table is table of integer;
  2  /

Type created.

SQL&gt; 
SQL&gt; DECLARE
  2  
  3  
  4   vendor_key_table   integer_table;
  5   vendor_key_table2   integer_table;
  6   CURSOR tst
  7   IS
  8      SELECT   purch_order, SUM (dollars),
  9        CAST (COLLECT (TO_NUMBER (vendor_key)) AS integer_table)
 10          FROM (SELECT 1 purch_order, 3 dollars, 435235 vendor_key
 11           FROM DUAL
 12         UNION ALL
 13         SELECT 1 purch_order, 8 dollars, 123452 vendor_key
 14           FROM DUAL
 15         UNION ALL
 16         SELECT 2 purch_order, 7 dollars, 433738 vendor_key
 17           FROM DUAL
 18         UNION ALL
 19         SELECT 2 purch_order, 4 dollars, 383738 vendor_key
 20           FROM DUAL
 21         UNION ALL
 22         SELECT 2 purch_order, 5 dollars, 387118 vendor_key
 23           FROM DUAL)
 24      GROUP BY purch_order;
 25  
 26  
 27    CURSOR tst2
 28   IS
 29    SELECT purch_order, SUM (dollars),
 30        CAST (COLLECT (TO_NUMBER (vendor_key)) AS integer_table)
 31          FROM (SELECT 1 purch_order, 3 dollars, 435235 vendor_key
 32           FROM DUAL
 33         UNION ALL
 34         SELECT 2 purch_order, 4 dollars, 383738 vendor_key
 35           FROM DUAL
 36         UNION ALL
 37         SELECT 2 purch_order, 7 dollars, 433738 vendor_key
 38           FROM DUAL
 39         UNION ALL
 40         SELECT 2 purch_order, 5 dollars, 387118 vendor_key
 41           FROM DUAL)
 42      GROUP BY purch_order;
 43   v_purch_order    NUMBER;
 44   v_dollars    NUMBER;
 45  
 46  
 47   mystr     VARCHAR2 (4000);
 48  
 49  
 50   v_purch_order2     NUMBER;
 51   v_dollars2     NUMBER;
 52  
 53  
 54   mystr2      VARCHAR2 (4000);
 55  BEGIN
 56   OPEN tst;
 57   open tst2;
 58  
 59  
 60   LOOP
 61      mystr := NULL;
 62      mystr2 := NULL;
 63  
 64  
 65      FETCH tst
 66       INTO v_purch_order, v_dollars, vendor_key_table;
 67  
 68  
 69      FETCH tst2
 70       INTO v_purch_order2, v_dollars2, vendor_key_table2;
 71  
 72  
 73      IF tst%NOTFOUND
 74      THEN
 75         EXIT;
 76      END IF;
 77  
 78  
 79      if vendor_key_table = vendor_key_table2 then
 80          dbms_output.put_line('equal');
 81      else
 82          dbms_output.put_line(' not equal');
 83      end if;
 84  
 85  
 86      -- loop through the collection and build a string so that
 87      -- we can display it and prove that it works
 88      FOR cur1 IN (SELECT COLUMN_VALUE vendor_key
 89       FROM TABLE (vendor_key_table))
 90      LOOP
 91         mystr := mystr || ',' || cur1.vendor_key;
 92         -- /* based on the value of the sum, you can do something with each detail*/
 93         -- if v_dollars &gt; 12 then
 94         --   UPDATE VENDOR SET paid_status = 'P' where vendor_key = cur1.vendor_key;
 95         -- end if;
 96      END LOOP;
 97  
 98  
 99      DBMS_OUTPUT.put_line (   'Purchace Order-&gt; '
100       || TO_CHAR (v_purch_order)
101       || ' dollar total-&gt; '
102       || TO_CHAR (v_dollars)
103       || ' vendorkey list-&gt; '
104       || SUBSTR (mystr, 2));
105  
106  
107      -- loop throught the collection and build a string so that
108      -- we can display it and prove that it works
109      FOR cur2 IN (SELECT COLUMN_VALUE vendor_key
110       FROM TABLE (vendor_key_table2))
111      LOOP
112         mystr2 := mystr2 || ',' || cur2.vendor_key;
113      END LOOP;
114  
115  
116      DBMS_OUTPUT.put_line (   'Purchace Order-&gt; '
117       || TO_CHAR (v_purch_order2)
118       || ' dollar total-&gt; '
119       || TO_CHAR (v_dollars2)
120       || ' vendorkey list-&gt; '
121       || SUBSTR (mystr2, 2));
122  
123  
124   END LOOP;
125   CLOSE tst;
126   CLOSE tst2;
127  END;
128  /
not equal
Purchace Order-&gt; 1   dollar total-&gt; 11   vendorkey list-&gt; 435235,123452
Purchace Order-&gt; 1   dollar total-&gt; 3   vendorkey list-&gt; 435235
equal
Purchace Order-&gt; 2   dollar total-&gt; 16   vendorkey list-&gt; 433738,387118,383738
Purchace Order-&gt; 2   dollar total-&gt; 16   vendorkey list-&gt; 383738,387118,433738

PL/SQL procedure successfully completed.

SQL&gt; 
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
Comparing the collections rather than looping through each table makes the work much easier to complete.  Notice we only needed one type created; the same type satisfied the conditions for both collection tables.&lt;br /&gt;
&lt;br /&gt;
The next example shows how things can go astray with the bulk collect operation when the limit does not evenly divide the result set.  In the first part of the example we use the well-known 'exit when cursor%notfound;' directive with less than stellar results (we miss inserting 5 records into the second table); the second part of the example shows how to properly implement an exit from a bulk collect operation; this one uses a varray:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt; 
SQL&gt; 
SQL&gt; set echo on linesize 150 trimspool on
SQL&gt; 
SQL&gt; create table emp_test as select * From emp where 0=1;

Table created.

SQL&gt; 
SQL&gt; declare
  2        type empcoltyp is table of emp%rowtype;
  3        emp_c empcoltyp;
  4  
  5        cursor get_emp_data is
  6        select * from emp;
  7  
  8  begin
  9        open get_emp_data;
 10        loop
 11        fetch get_emp_data bulk collect into emp_c limit 9;
 12        exit when get_emp_data%notfound;
 13  
 14        for i in 1..emp_c.count loop
 15         insert into emp_test (empno, ename, sal)
 16         values (emp_c(i).empno, emp_c(i).ename, emp_c(i).sal);
 17        end loop;
 18  
 19        end loop;
 20  
 21        commit;
 22  
 23  end;
 24  /

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; select * from emp_test;

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM     DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
      7369 SMITH                                            800
      7499 ALLEN                                           1600
      7521 WARD                                            1250
      7566 JONES                                           2975
      7654 MARTIN                                          1250
      7698 BLAKE                                           2850
      7782 CLARK                                           2450
      7788 SCOTT                                           3000
      7839 KING                                            5000

9 rows selected.

SQL&gt; 
SQL&gt; truncate table emp_test;

Table truncated.

SQL&gt; 
SQL&gt; declare
  2        type empcoltyp is table of emp%rowtype;
  3        emp_c empcoltyp;
  4  
  5        cursor get_emp_data is
  6        select * from emp;
  7  
  8  begin
  9        open get_emp_data;
 10        loop
 11        fetch get_emp_data bulk collect into emp_c limit 9;
 12        exit when emp_c.count = 0;
 13  
 14        for i in 1..emp_c.count loop
 15         insert into emp_test (empno, ename, sal)
 16         values (emp_c(i).empno, emp_c(i).ename, emp_c(i).sal);
 17        end loop;
 18  
 19        end loop;
 20  
 21        commit;
 22  
 23  end;
 24  /

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; select * from emp_test;

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM     DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
      7369 SMITH                                            800
      7499 ALLEN                                           1600
      7521 WARD                                            1250
      7566 JONES                                           2975
      7654 MARTIN                                          1250
      7698 BLAKE                                           2850
      7782 CLARK                                           2450
      7788 SCOTT                                           3000
      7839 KING                                            5000
      7844 TURNER                                          1500
      7876 ADAMS                                           1100

     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM     DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
      7900 JAMES                                            950
      7902 FORD                                            3000
      7934 MILLER                                          1300

14 rows selected.

SQL&gt; 
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
What happened in the first part?  Since the limit was more than the number of remaining records the NOTFOUND indicator was set at the end of the fetch.  We had 5 records left to process in the varray but the 'exit when cursor%notfound;' statement terminated the loop BEFORE we could get the remainng 5 records inserted into our table, thus they were lost.  Using the 'exit when collection.count = 0;' construct prevents us from missing records since the count was greater than 0 even when the NOTFOUND indicator was set.  This allows us to process the remaining records in the varray before exiting the loop.  [Yes, the exit could be coded at the end of the loop rather than the beginning and the 'exit when cursor%NOTFOUND;' would process the remaing records but that, to me, defeats the purpose of the conditional exit.  As I see it we want to exit the loop when no more work is to be done, not look for partial sets of data to apply then exit before the next (unsuccessful) fetch.]&lt;br /&gt;
&lt;br /&gt;
Our next example does two things: loads data using bulk collect into a varray then uses the FORALL loop construct to quickly process the collection and insert the data into a staging table.  The second part is a bit contrived as it uses a collection to  process deletes from a table -- deletes that could have easily been executed with a single SQL statement -- but it does show the power of using collections and varrays:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;
SQL&gt; 
SQL&gt; set echo on timing on
SQL&gt; 
SQL&gt; create table stage_data(
  2        uname varchar2(30),
  3        ujob varchar2(20),
  4        usal number
  5  );

Table created.

SQL&gt; 
SQL&gt; begin
  2        for i in 1..1000000 loop
  3        insert into stage_data
  4        values ('Blorp'||i, 'Job'||i, 1200*(mod(i,3)));
  5        end loop;
  6  
  7        commit;
  8  
  9  end;
 10  /

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; 
SQL&gt; create table forall_load(
  2        uname varchar2(30),
  3        ujob varchar2(20),
  4        usal number
  5  );

Table created.

SQL&gt; 
SQL&gt; CREATE OR REPLACE PROCEDURE fast_way IS
  2  
  3  TYPE myarray IS TABLE OF stage_data%ROWTYPE;
  4  l_data myarray;
  5  
  6  CURSOR r IS
  7  SELECT *
  8  FROM stage_data;
  9  
 10  BEGIN
 11   OPEN r;
 12   LOOP
 13     FETCH r BULK COLLECT INTO l_data LIMIT 1000;
 14  
 15     FORALL i IN 1..l_data.COUNT
 16        INSERT INTO forall_load VALUES l_data(i);
 17  
 18     EXIT WHEN l_data.count=0;
 19    END LOOP;
 20    COMMIT;
 21    CLOSE r;
 22  END fast_way;
 23  /

Procedure created.

SQL&gt; 
SQL&gt; exec fast_way;

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; select * from forall_load where uname like '%99999%';

UNAME                          UJOB                       USAL
------------------------------ -------------------- ----------
Blorp99999                     Job99999                      0
Blorp199999                    Job199999                  1200
Blorp299999                    Job299999                  2400
Blorp399999                    Job399999                     0
Blorp499999                    Job499999                  1200
Blorp599999                    Job599999                  2400
Blorp699999                    Job699999                     0
Blorp799999                    Job799999                  1200
Blorp899999                    Job899999                  2400
Blorp999990                    Job999990                     0
Blorp999991                    Job999991                  1200

UNAME                          UJOB                       USAL
------------------------------ -------------------- ----------
Blorp999992                    Job999992                  2400
Blorp999993                    Job999993                     0
Blorp999994                    Job999994                  1200
Blorp999995                    Job999995                  2400
Blorp999996                    Job999996                     0
Blorp999997                    Job999997                  1200
Blorp999998                    Job999998                  2400
Blorp999999                    Job999999                     0

19 rows selected.

SQL&gt; 
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
Trust me that the data loads took very little time to process.  Here is the contrived part, but it is still a good example of the power of using collections:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;
SQL&gt; CREATE OR REPLACE PROCEDURE del_rows IS
  2  
  3  TYPE myarray IS TABLE OF stage_data.uname%TYPE;
  4  l_data myarray;
  5  
  6  CURSOR r IS
  7  SELECT uname
  8  FROM stage_data
  9  where uname like '%9999%';
 10  
 11  BEGIN
 12   OPEN r;
 13   LOOP
 14     FETCH r BULK COLLECT INTO l_data LIMIT 1000;
 15  
 16     FORALL i IN 1..l_data.COUNT
 17        delete from forall_load where uname = l_data(i);
 18  
 19     EXIT WHEN l_data.count=0;
 20    END LOOP;
 21    COMMIT;
 22    CLOSE r;
 23  END del_rows;
 24  /

Procedure created.

SQL&gt; 
SQL&gt; show errors
No errors.
SQL&gt; 
SQL&gt; exec del_rows;

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; select * from forall_load where uname like '%99999%';

no rows selected

SQL&gt; 
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
Collections can be a real timesaver for bulk processng of data; they may not be applicable in every sitution but when the conditions are right they can make your job so much easier.  Master collections and varrays and you may be able to amaze your friends.&lt;br /&gt;
&lt;br /&gt;
Collections, anyone?&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=jbjgdazDlhk:qotWMxpl2Ag:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=jbjgdazDlhk:qotWMxpl2Ag:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://oratips-ddf.blogspot.com/feeds/7907386764933314772/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=7907386764933314772" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/7907386764933314772?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/7907386764933314772?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2012/02/collecting-thoughts.html" title="Collecting Thoughts" /><author><name>David Fitzjarrell</name><uri>https://plus.google.com/102215427046411604711</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-y2hTTVpTe4o/AAAAAAAAAAI/AAAAAAAAAGw/KabdyVeaX9M/s512-c/photo.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;D04DRn05eCp7ImA9WhRbFk8.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-3719870654690949776</id><published>2012-01-20T00:25:00.012Z</published><updated>2012-02-07T14:46:17.320Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-02-07T14:46:17.320Z</app:edited><title>At The Touch Of A Button</title><content type="html">It intrigues me that some DBAs can be lost without tools like Oracle Enterprise Manager or TOAD, so much so that they can't complete a task without a GUI.  What makes this even more disconcerting is these DBAs can execute tasks that they may be unable to complete absent such tools.  If what the tool does 'behind the scenes' is a mystery to the users it stands to reason that a user, using a GUI, could do some damage to a database by executing misunderstood tasks simply by pressing 'buttons'.&lt;br /&gt;
&lt;br /&gt;
When I started as a DBA [the earth's crust was still cooling and dirt didn't yet have its official name] there was the command line.  That was it.  Nothing else.  No GUI, no OEM, no slick and nifty applications coded to make DBA life easier.  A database was managed at the SQL&gt; or SVRMGR&gt; prompt (depending upon what needed to be done).  Pretty graphs didn't exist, alerts didn't get generated unless the DBA wrote a script and scheduled it through cron (or the Windows scheduler) to check the database for space or memory or process count and send an email to the DBA should any of the acceptable criteria be violated.  Yes, it was a hard life for a DBA, with all of that scripting and manual labor [typing is such back-breaking work].  A DBA had to know what commands did what and when to use them.  A DBA also had to know where to excavate performance data, storage numbers, memory usage and user activity from the data dictionary by actually using the manuals and looking things up.  Now tools like OEM, TOAD and others make it easy for someone to be a DBA by making most tasks as easy as 'point and click', which is a real disservice to the modern DBA, in my opinion.&lt;br /&gt;
&lt;br /&gt;
What if other, daily tasks were modified so that even the uneducated could perform them?  Would anyone want someone behind the wheel of a car who didn't have any instruction at all in how to drive or operate the vehicle?  Would anyone want a carpenter, plumber, electrician or mechanic performing any work with the newest power tools but having absolutely no idea how to operate them safely and properly?  Clearly no one would want a surgeon operating with the latest gadgets but absent a medical degree.  Yet, this is what allows people to be DBAs in the modern world -- no knowledge of the intricacies of the database they manage, no knowledge of the commands necessary to perform basic functions such as adding a datafile to a tablespace, resizing a datafile in a database, adding a user account, creating roles, granting roles -- the list can go on.  Sit them in front of a GUI tool and explain the basics to them ("navigate here, press this button") and they're immediately DBAs.  The prospect is disturbing.&lt;br /&gt;
&lt;br /&gt;
It's my privilege to know a number of &lt;b&gt;&lt;em&gt;really&lt;/em&gt;&lt;/b&gt; good DBAs in this world, DBAs who do know how to create a database, turn on and off archivelogging, restore and recover a database from a reliable backup, how to take reliable backups and do it all from the command line interface.  These same DBAs use OEM, RMAN and TOAD to make their lives a bit easier, and I do the same thing so I see no issue with that.  I also know (and know of) some DBAs who &lt;b&gt;&lt;em&gt;can't&lt;/em&gt;&lt;/b&gt; do the job &lt;b&gt;&lt;em&gt;without&lt;/em&gt;&lt;/b&gt; OEM or TOAD -- I've been told this in several interviews I've held when looking for additional DBA resources.  Some of the most basic questions weren't answered satisfactorily as I was given step-by-step directions on how to navigate to the page where that particular button resides instead of being told the commands necessary to complete the task in question.  In an emergency situation OEM or TOAD may not be available and DBAs who don't know the command line may be looking for another employer.&lt;br /&gt;
&lt;br /&gt;
It's my belief that enterprises who train DBAs need to concentrate not only on the tools but on the basic knowledge as well, educating their students not only in OEM but in how to go about managing a database absent those nifty tools.  Understanding how the tool works only makes for better DBAs and frees them from being tethered to a graphical user interface, an interface they are dependent upon to perform the most basic and mundane of DBA tasks.&lt;br /&gt;
&lt;br /&gt;
Education and training are demanded by society for teachers, doctors, lawyers, dentists, even insurance agents (not to disparage insurance agents).  Why the industry doesn't demand the same of DBAs is a mystery.  &lt;span style="color:#C94729;"&gt;&lt;em&gt;[Certification, in many cases, is a requirement on the resume but 'brain dumps' and courses exist to 'train' those uneducated in the chosen DBMS so such 'credentials' can be acquired absent any real work experience.  Many of these courses are centered around GUI management tools; sadly the underlying framework is glossed over in deference to learning to navigate the chosen graphical interface.  Such an environment produces, in the Oracle arena, Oracle Certified Professionals completely absent any professional experience.]&lt;/em&gt;&lt;/span&gt;  Yes, experience counts but if that experience is nothing more than a set of rote instructions on  how to navigate a GUI tool how much worth does it bring to the employer?  Not much, really.&lt;br /&gt;
&lt;br /&gt;
Database administration is a respected profession, and most DBAs in the workforce are qualified and capable.  Occasionally a few get through who meet the description I've given here.  It's those few I write about, and ask that they further their education and learn how their chosen DBMS works and how, in an emergency, to do their jobs absent any flashy graphic tools.&lt;br /&gt;
&lt;br /&gt;
I don't believe that's too much to ask.&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=h8QPq6B84UI:apvMbEVAyhA:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=h8QPq6B84UI:apvMbEVAyhA:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://oratips-ddf.blogspot.com/feeds/3719870654690949776/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=3719870654690949776" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/3719870654690949776?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/3719870654690949776?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2012/01/at-touch-of-button.html" title="At The Touch Of A Button" /><author><name>David Fitzjarrell</name><uri>https://plus.google.com/102215427046411604711</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-y2hTTVpTe4o/AAAAAAAAAAI/AAAAAAAAAGw/KabdyVeaX9M/s512-c/photo.jpg" /></author><thr:total>2</thr:total></entry><entry gd:etag="W/&quot;AkIERH4-cCp7ImA9WhRVF08.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-6283232057305635570</id><published>2012-01-13T19:21:00.002Z</published><updated>2012-01-16T15:41:45.058Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-16T15:41:45.058Z</app:edited><title>My, How You've Grown</title><content type="html">Indexes are interesting objects -- they can dramatically improve performance but their management can be, well, tricky.  Depending upon how data is inserted into and deleted from a table the size an index can attain could be surprising to the DBA.  How can the size be surprising?  Let's take an example through a number of iterations and see what Oracle does with the index, and explain why the results shouldn't be unexpected.&lt;br /&gt;
&lt;br /&gt;
Setting the stage we'll create a table and a primary key index, load 200000 rows, delete the existing rows and insert new keys then see how the index responds.  We'll do this several times, under differing conditions, to see if the behaviour changes and, if so, why.  By the end of the example we should know how index leaf blocks are used and re-used and why some dead space can remain in an index even though general wisdom says otherwise.  We begin:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;  
SQL&gt; 
SQL&gt; --
SQL&gt; -- Create our test table and primary key index
SQL&gt; --
SQL&gt; 
SQL&gt; create table biggy (id number constraint biggy_pk primary key, name varchar2(100));

Table created.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Insert 200,000 rows
SQL&gt; --
SQL&gt; 
SQL&gt; insert into biggy select rownum, 'BIGGY' from dual connect by level &lt;= 200000;

200000 rows created.

SQL&gt; 
SQL&gt; commit;

Commit complete.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Validate the index structure
SQL&gt; --
SQL&gt; -- Report on the current index configuration
SQL&gt; --
SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     200000           0        374          1

SQL&gt; 
SQL&gt; --
SQL&gt; -- Create a procedure to delete the existing rows and add
SQL&gt; -- new rows with increasing PK values
SQL&gt; --
SQL&gt; -- Note that we add a new record with an increasing PK at the
SQL&gt; -- end of the index right after we delete the index entry for
SQL&gt; -- the lowest PK value
SQL&gt; --
SQL&gt; --
SQL&gt; -- This can do some strange things to the index structure
SQL&gt; -- as the deleted leaf block cannot be reused since the new
SQL&gt; -- key value is outside of the key range the deleted leaf block
SQL&gt; -- is found in
SQL&gt; --
SQL&gt; 
SQL&gt; create or replace procedure delete_insert_rows(p_commit_after in number)
  2  as
  3       n number;
  4       m number;
  5  begin
  6       select min(id),max(id) into n,m from biggy;
  7       for i in 1..200000 loop
  8    delete from biggy where id=n+i-1;
  9    insert into biggy values(m+i,'Big index test');
 10    if mod(i,p_commit_after)=0 then
 11         commit;
 12    end if;
 13       end loop;
 14       commit;
 15  end;
 16  /

Procedure created.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Replace the 200,000 existing rows with 200,000 new rows
SQL&gt; --
SQL&gt; 
SQL&gt; exec delete_insert_rows(1000)

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Check current index structure
SQL&gt; --
SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     245378       45378        495          1

SQL&gt; 
SQL&gt; SELECT name,
  2       del_lf_rows,
  3       lf_rows - del_lf_rows lf_rows_used,
  4       to_char(del_lf_rows / (lf_rows)*100,'999.99999') ibadness
  5  FROM index_stats
  6  where name = upper('biggy_pk');

NAME                           DEL_LF_ROWS LF_ROWS_USED IBADNESS
------------------------------ ----------- ------------ ----------
BIGGY_PK                             45378       200000   18.49310

SQL&gt; 
SQL&gt; --
SQL&gt; -- Do it again
SQL&gt; --
SQL&gt; 
SQL&gt; exec delete_insert_rows(2000)

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     293295       93295        587          1

SQL&gt; 
SQL&gt; SELECT name,
  2       del_lf_rows,
  3       lf_rows - del_lf_rows lf_rows_used,
  4       to_char(del_lf_rows / (lf_rows)*100,'999.99999') ibadness
  5  FROM index_stats
  6  where name = upper('biggy_pk');

NAME                           DEL_LF_ROWS LF_ROWS_USED IBADNESS
------------------------------ ----------- ------------ ----------
BIGGY_PK                             93295       200000   31.80927

SQL&gt; 
SQL&gt; --
SQL&gt; -- Get the object_id for the PK index so we can perform a treedump
SQL&gt; --
SQL&gt; 
SQL&gt; column object_id new_value objid
SQL&gt; 
SQL&gt; select object_id from dba_objects where object_name = 'BIGGY_PK';

 OBJECT_ID
----------
     69706

SQL&gt; 
SQL&gt; --
SQL&gt; -- Execute the treedump for analysis
SQL&gt; --
SQL&gt; 
SQL&gt; alter session set events 'immediate trace name treedump level &amp;objid';

Session altered.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Go through 8 more runs of the delete/replace procedure
SQL&gt; --
SQL&gt; 
SQL&gt; exec delete_insert_rows(3000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(4000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(5000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(6000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(7000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(8000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(9000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(10000)

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Check the index structure and report on it
SQL&gt; --
SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                3     484820      284820       1011          5

SQL&gt; 
SQL&gt; SELECT name,
  2       del_lf_rows,
  3       lf_rows - del_lf_rows lf_rows_used,
  4       to_char(del_lf_rows / (lf_rows)*100,'999.99999') ibadness
  5  FROM index_stats
  6  where name = upper('biggy_pk');

NAME                           DEL_LF_ROWS LF_ROWS_USED IBADNESS
------------------------------ ----------- ------------ ----------
BIGGY_PK                            284820       200000   58.74758

&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
We find that the way we've deleted and added the rows has affected how the leaf blocks are managed using a standard primary key index as the index has more than doubled in size; a good portion of that storage is empty leaf blocks that could not be reused by the new keys because they are out of the key range for the associated branch block.  Let's reverse the index and try this exercise again:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;  
SQL&gt; 
SQL&gt; --
SQL&gt; -- Drop the existing table and index then recreate
SQL&gt; -- 
SQL&gt; 
SQL&gt; drop table biggy purge;

Table dropped.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Recreate our test table and primary key index
SQL&gt; -- This time the PK is a reverse-key index
SQL&gt; --
SQL&gt; 
SQL&gt; create table biggy (id number, name varchar2(100));

Table created.

SQL&gt; create unique index biggy_pk on biggy(id) reverse;

Index created.

SQL&gt; alter table biggy add constraint biggy_pk primary key(id) using index biggy_pk;

Table altered.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Insert 200,000 rows
SQL&gt; --
SQL&gt; 
SQL&gt; insert into biggy select rownum, 'BIGGY' from dual connect by level &lt;= 200000;

200000 rows created.

SQL&gt; 
SQL&gt; commit;

Commit complete.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Validate the index structure
SQL&gt; --
SQL&gt; -- Report on the current index configuration
SQL&gt; --
SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     200000           0        512          1

SQL&gt; 
SQL&gt; --
SQL&gt; -- The reverse-key index greatly improves the performance and
SQL&gt; -- decreases the instance of unused empty leaf blocks
SQL&gt; -- compared to our first run
SQL&gt; --
SQL&gt; 
SQL&gt; --
SQL&gt; -- Replace the 200,000 existing rows with 200,000 new rows
SQL&gt; --
SQL&gt; 
SQL&gt; exec delete_insert_rows(1000)

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Check current index structure
SQL&gt; --
SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     202645        2645        512          1

SQL&gt; 
SQL&gt; SELECT name,
  2       del_lf_rows,
  3       lf_rows - del_lf_rows lf_rows_used,
  4       to_char(del_lf_rows / (lf_rows)*100,'999.99999') ibadness
  5  FROM index_stats
  6  where name = upper('biggy_pk');

NAME                           DEL_LF_ROWS LF_ROWS_USED IBADNESS
------------------------------ ----------- ------------ ----------
BIGGY_PK                              2645       200000    1.30524

SQL&gt; 
SQL&gt; --
SQL&gt; -- Do it again
SQL&gt; --
SQL&gt; 
SQL&gt; exec delete_insert_rows(2000)

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; select count(*), min(id), max(id) from biggy;

  COUNT(*)    MIN(ID)    MAX(ID)
---------- ---------- ----------
    200000     400001     600000

SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     205095        5095        512          1

SQL&gt; 
SQL&gt; SELECT name,
  2       del_lf_rows,
  3       lf_rows - del_lf_rows lf_rows_used,
  4       to_char(del_lf_rows / (lf_rows)*100,'999.99999') ibadness
  5  FROM index_stats
  6  where name = upper('biggy_pk');

NAME                           DEL_LF_ROWS LF_ROWS_USED IBADNESS
------------------------------ ----------- ------------ ----------
BIGGY_PK                              5095       200000    2.48421

SQL&gt; 
SQL&gt; --
SQL&gt; -- Get the object_id for the PK index so we can perform a treedump
SQL&gt; --
SQL&gt; 
SQL&gt; column object_id new_value objid
SQL&gt; 
SQL&gt; select object_id from dba_objects where object_name = 'BIGGY_PK';

 OBJECT_ID
----------
     69709

SQL&gt; 
SQL&gt; --
SQL&gt; -- Execute the treedump for analysis
SQL&gt; --
SQL&gt; 
SQL&gt; alter session set events 'immediate trace name treedump level &amp;objid';

Session altered.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Go through 8 more runs of the delete/replace procedure
SQL&gt; --
SQL&gt; 
SQL&gt; exec delete_insert_rows(3000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(4000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(5000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(6000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(7000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(8000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(9000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(10000)

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Check the index structure and report on it
SQL&gt; --
SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     210249       10249        523          1

SQL&gt; 
SQL&gt; SELECT name,
  2       del_lf_rows,
  3       lf_rows - del_lf_rows lf_rows_used,
  4       to_char(del_lf_rows / (lf_rows)*100,'999.99999') ibadness
  5  FROM index_stats
  6  where name = upper('biggy_pk');

NAME                           DEL_LF_ROWS LF_ROWS_USED IBADNESS
------------------------------ ----------- ------------ ----------
BIGGY_PK                             10249       200000    4.87470

SQL&gt; 
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
The reverse-key index made a substantial impact on the number of empty leaf blocks left unused in the index.  Let't see if manual segment space management causes a change in behaviour over the prior tests using ASSM:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;  
SQL&gt; 
SQL&gt; --
SQL&gt; -- Create our test table and primary key index
SQL&gt; -- Use a tablespace with manual segment management
SQL&gt; --
SQL&gt; 
SQL&gt; create table biggy (id number, name varchar2(100));

Table created.

SQL&gt; create unique index biggy_pk on biggy(id) tablespace bing_idx;

Index created.

SQL&gt; alter table biggy add constraint biggy_pk primary key(id) using index biggy_pk;

Table altered.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Insert 200,000 rows
SQL&gt; --
SQL&gt; 
SQL&gt; insert into biggy select rownum, 'BIGGY' from dual connect by level &lt;= 200000;

200000 rows created.

SQL&gt; 
SQL&gt; commit;

Commit complete.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Validate the index structure
SQL&gt; --
SQL&gt; -- Report on the current index configuration
SQL&gt; --
SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     200000           0        374          1

SQL&gt; 
SQL&gt; --
SQL&gt; -- Create a procedure to delete the existing rows and add
SQL&gt; -- new rows with increasing PK values
SQL&gt; --
SQL&gt; -- Note that we add a new record with an increasing PK at the
SQL&gt; -- end of the index right after we delete the index entry for
SQL&gt; -- the lowest PK value
SQL&gt; --
SQL&gt; --
SQL&gt; -- This can do some strange things to the index structure
SQL&gt; -- as the deleted leaf block cannot be reused since the new
SQL&gt; -- key value is outside of the key range the deleted leaf block
SQL&gt; -- is found in when ASSM is used
SQL&gt; --
SQL&gt; -- MSSM may eliminate the behaviour
SQL&gt; --
SQL&gt; 
SQL&gt; create or replace procedure delete_insert_rows(p_commit_after in number)
  2  as
  3       n number;
  4       m number;
  5  begin
  6       select min(id),max(id) into n,m from biggy;
  7       for i in 1..200000 loop
  8    delete from biggy where id=n+i-1;
  9    insert into biggy values(m+i,'Big index test');
 10    if mod(i,p_commit_after)=0 then
 11         commit;
 12    end if;
 13       end loop;
 14       commit;
 15  end;
 16  /

Procedure created.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Replace the 200,000 existing rows with 200,000 new rows
SQL&gt; --
SQL&gt; 
SQL&gt; exec delete_insert_rows(1000)

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Check current index structure
SQL&gt; --
SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     201000        1000        379          1

SQL&gt; 
SQL&gt; SELECT name,
  2       del_lf_rows,
  3       lf_rows - del_lf_rows lf_rows_used,
  4       to_char(del_lf_rows / (lf_rows)*100,'999.99999') ibadness
  5  FROM index_stats
  6  where name = upper('biggy_pk');

NAME                           DEL_LF_ROWS LF_ROWS_USED IBADNESS
------------------------------ ----------- ------------ ----------
BIGGY_PK                              1000       200000     .49751

SQL&gt; 
SQL&gt; --
SQL&gt; -- Do it again
SQL&gt; --
SQL&gt; 
SQL&gt; exec delete_insert_rows(2000)

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     202000        2000        382          1

SQL&gt; 
SQL&gt; SELECT name,
  2       del_lf_rows,
  3       lf_rows - del_lf_rows lf_rows_used,
  4       to_char(del_lf_rows / (lf_rows)*100,'999.99999') ibadness
  5  FROM index_stats
  6  where name = upper('biggy_pk');

NAME                           DEL_LF_ROWS LF_ROWS_USED IBADNESS
------------------------------ ----------- ------------ ----------
BIGGY_PK                              2000       200000     .99010

SQL&gt; 
SQL&gt; --
SQL&gt; -- Get the object_id for the PK index so we can perform a treedump
SQL&gt; --
SQL&gt; 
SQL&gt; column object_id new_value objid
SQL&gt; 
SQL&gt; select object_id from dba_objects where object_name = 'BIGGY_PK';

 OBJECT_ID
----------
     69711

SQL&gt; 
SQL&gt; --
SQL&gt; -- Execute the treedump for analysis
SQL&gt; --
SQL&gt; 
SQL&gt; alter session set events 'immediate trace name treedump level &amp;objid';

Session altered.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Go through 8 more runs of the delete/replace procedure
SQL&gt; --
SQL&gt; 
SQL&gt; exec delete_insert_rows(3000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(4000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(5000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(6000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(7000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(8000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(9000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(10000)

PL/SQL procedure successfully completed.

SQL&gt; --
SQL&gt; -- Check the index structure and report on it
SQL&gt; --
SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     210000       10000        422          1

SQL&gt; 
SQL&gt; SELECT name,
  2       del_lf_rows,
  3       lf_rows - del_lf_rows lf_rows_used,
  4       to_char(del_lf_rows / (lf_rows)*100,'999.99999') ibadness
  5  FROM index_stats
  6  where name = upper('biggy_pk');

NAME                           DEL_LF_ROWS LF_ROWS_USED IBADNESS
------------------------------ ----------- ------------ ----------
BIGGY_PK                             10000       200000    4.76190

SQL&gt; 
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
We note that manual segment space management didn't really make much of a difference in the storage with the standard primary key index; the index is still much larger than it 'should' be.  Will it make a difference with the reverse-key version?  Let's test it and see:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;  

SQL&gt; drop table biggy purge;

Table dropped.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Recreate our test table and primary key index
SQL&gt; -- This time the PK is a reverse-key index
SQL&gt; -- Again we use a tablespace with manual extent management
SQL&gt; --
SQL&gt; 
SQL&gt; create table biggy (id number, name varchar2(100));

Table created.

SQL&gt; create unique index biggy_pk on biggy(id) reverse tablespace bing_idx;

Index created.

SQL&gt; alter table biggy add constraint biggy_pk primary key(id) using index biggy_pk;

Table altered.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Insert 200,000 rows
SQL&gt; --
SQL&gt; 
SQL&gt; insert into biggy select rownum, 'BIGGY' from dual connect by level &lt;= 200000;

200000 rows created.

SQL&gt; 
SQL&gt; commit;

Commit complete.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Validate the index structure
SQL&gt; --
SQL&gt; -- Report on the current index configuration
SQL&gt; --
SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     200000           0        512          1

SQL&gt; 
SQL&gt; --
SQL&gt; -- The reverse-key index greatly improves the performance and
SQL&gt; -- decreases the instance of unused empty leaf blocks
SQL&gt; -- compared to our first run
SQL&gt; --
SQL&gt; 
SQL&gt; --
SQL&gt; -- Replace the 200,000 existing rows with 200,000 new rows
SQL&gt; --
SQL&gt; 
SQL&gt; exec delete_insert_rows(1000)

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Check current index structure
SQL&gt; --
SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     202645        2645        512          1

SQL&gt; 
SQL&gt; SELECT name,
  2       del_lf_rows,
  3       lf_rows - del_lf_rows lf_rows_used,
  4       to_char(del_lf_rows / (lf_rows)*100,'999.99999') ibadness
  5  FROM index_stats
  6  where name = upper('biggy_pk');

NAME                           DEL_LF_ROWS LF_ROWS_USED IBADNESS
------------------------------ ----------- ------------ ----------
BIGGY_PK                              2645       200000    1.30524

SQL&gt; 
SQL&gt; --
SQL&gt; -- Do it again
SQL&gt; --
SQL&gt; 
SQL&gt; exec delete_insert_rows(2000)

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     205095        5095        512          1

SQL&gt; 
SQL&gt; SELECT name,
  2       del_lf_rows,
  3       lf_rows - del_lf_rows lf_rows_used,
  4       to_char(del_lf_rows / (lf_rows)*100,'999.99999') ibadness
  5  FROM index_stats
  6  where name = upper('biggy_pk');

NAME                           DEL_LF_ROWS LF_ROWS_USED IBADNESS
------------------------------ ----------- ------------ ----------
BIGGY_PK                              5095       200000    2.48421

SQL&gt; 
SQL&gt; --
SQL&gt; -- Get the object_id for the PK index so we can perform a treedump
SQL&gt; --
SQL&gt; 
SQL&gt; column object_id new_value objid
SQL&gt; 
SQL&gt; select object_id from dba_objects where object_name = 'BIGGY_PK';

 OBJECT_ID
----------
     69714

SQL&gt; 
SQL&gt; --
SQL&gt; -- Execute the treedump for analysis
SQL&gt; --
SQL&gt; 
SQL&gt; alter session set events 'immediate trace name treedump level &amp;objid';

Session altered.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Go through 8 more runs of the delete/replace procedure
SQL&gt; --
SQL&gt; 
SQL&gt; exec delete_insert_rows(3000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(4000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(5000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(6000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(7000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(8000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(9000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(10000)

PL/SQL procedure successfully completed.

SQL&gt; --
SQL&gt; -- Check the index structure and report on it
SQL&gt; --
SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     210249       10249        523          1

SQL&gt; 
SQL&gt; SELECT name,
  2       del_lf_rows,
  3       lf_rows - del_lf_rows lf_rows_used,
  4       to_char(del_lf_rows / (lf_rows)*100,'999.99999') ibadness
  5  FROM index_stats
  6  where name = upper('biggy_pk');

NAME                           DEL_LF_ROWS LF_ROWS_USED IBADNESS
------------------------------ ----------- ------------ ----------
BIGGY_PK                             10249       200000    4.87470

SQL&gt; 
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
No real difference seen with the reverse-key index so the segment space management (at least in 11gR2) isn't a factor.  One thought on mitigating this behaviour is to set session_cached_cursors to 0; let's see what that does:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;  
SQL&gt; 
SQL&gt; --
SQL&gt; -- Set session_cached_cursors to 0
SQL&gt; --
SQL&gt; -- May improve the situation further
SQL&gt; --
SQL&gt; 
SQL&gt; alter session set session_cached_cursors = 0;

Session altered.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Go through the whole process one more time
SQL&gt; -- with and without a reverse-key index
SQL&gt; -- and using ASSM then MSSM to see which is better
SQL&gt; --
SQL&gt; 
SQL&gt; --
SQL&gt; -- Create our test table and primary key index
SQL&gt; --
SQL&gt; 
SQL&gt; create table biggy (id number constraint biggy_pk primary key, name varchar2(100));

Table created.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Insert 200,000 rows
SQL&gt; --
SQL&gt; 
SQL&gt; insert into biggy select rownum, 'BIGGY' from dual connect by level &lt;= 200000;

200000 rows created.

SQL&gt; 
SQL&gt; commit;

Commit complete.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Validate the index structure
SQL&gt; --
SQL&gt; -- Report on the current index configuration
SQL&gt; --
SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     200000           0        374          1

SQL&gt; 
SQL&gt; --
SQL&gt; -- Create a procedure to delete the existing rows and add
SQL&gt; -- new rows with increasing PK values
SQL&gt; --
SQL&gt; -- Note that we add a new record with an increasing PK at the
SQL&gt; -- end of the index right after we delete the index entry for
SQL&gt; -- the lowest PK value
SQL&gt; --
SQL&gt; --
SQL&gt; -- This can do some strange things to the index structure
SQL&gt; -- as the deleted leaf block cannot be reused since the new
SQL&gt; -- key value is outside of the key range the deleted leaf block
SQL&gt; -- is found in
SQL&gt; --
SQL&gt; 
SQL&gt; create or replace procedure delete_insert_rows(p_commit_after in number)
  2  as
  3       n number;
  4       m number;
  5  begin
  6       select min(id),max(id) into n,m from biggy;
  7       for i in 1..200000 loop
  8    delete from biggy where id=n+i-1;
  9    insert into biggy values(m+i,'Big index test');
 10    if mod(i,p_commit_after)=0 then
 11         commit;
 12    end if;
 13       end loop;
 14       commit;
 15  end;
 16  /

Procedure created.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Replace the 200,000 existing rows with 200,000 new rows
SQL&gt; --
SQL&gt; 
SQL&gt; exec delete_insert_rows(1000)

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Check current index structure
SQL&gt; --
SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     225031       25031        440          1

SQL&gt; 
SQL&gt; SELECT name,
  2       del_lf_rows,
  3       lf_rows - del_lf_rows lf_rows_used,
  4       to_char(del_lf_rows / (lf_rows)*100,'999.99999') ibadness
  5  FROM index_stats
  6  where name = upper('biggy_pk');

NAME                           DEL_LF_ROWS LF_ROWS_USED IBADNESS
------------------------------ ----------- ------------ ----------
BIGGY_PK                             25031       200000   11.12336

SQL&gt; 
SQL&gt; --
SQL&gt; -- Do it again
SQL&gt; --
SQL&gt; 
SQL&gt; exec delete_insert_rows(2000)

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     228120       28120        446          1

SQL&gt; 
SQL&gt; SELECT name,
  2       del_lf_rows,
  3       lf_rows - del_lf_rows lf_rows_used,
  4       to_char(del_lf_rows / (lf_rows)*100,'999.99999') ibadness
  5  FROM index_stats
  6  where name = upper('biggy_pk');

NAME                           DEL_LF_ROWS LF_ROWS_USED IBADNESS
------------------------------ ----------- ------------ ----------
BIGGY_PK                             28120       200000   12.32685

SQL&gt; 
SQL&gt; --
SQL&gt; -- Get the object_id for the PK index so we can perform a treedump
SQL&gt; --
SQL&gt; 
SQL&gt; column object_id new_value objid
SQL&gt; 
SQL&gt; select object_id from dba_objects where object_name = 'BIGGY_PK';

 OBJECT_ID
----------
     69716

SQL&gt; 
SQL&gt; --
SQL&gt; -- Execute the treedump for analysis
SQL&gt; --
SQL&gt; 
SQL&gt; alter session set events 'immediate trace name treedump level &amp;objid';

Session altered.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Go through 8 more runs of the delete/replace procedure
SQL&gt; --
SQL&gt; 
SQL&gt; exec delete_insert_rows(3000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(4000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(5000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(6000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(7000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(8000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(9000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(10000)

PL/SQL procedure successfully completed.

SQL&gt; --
SQL&gt; -- Check the index structure and report on it
SQL&gt; --
SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     237132       37132        495          1

SQL&gt; 
SQL&gt; SELECT name,
  2       del_lf_rows,
  3       lf_rows - del_lf_rows lf_rows_used,
  4       to_char(del_lf_rows / (lf_rows)*100,'999.99999') ibadness
  5  FROM index_stats
  6  where name = upper('biggy_pk');

NAME                           DEL_LF_ROWS LF_ROWS_USED IBADNESS
------------------------------ ----------- ------------ ----------
BIGGY_PK                             37132       200000   15.65879

SQL&gt; 
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
Nothing changed for the standard index; let's again test the reverse-key index and see what that produces:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;  

SQL&gt; drop table biggy purge;

Table dropped.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Recreate our test table and primary key index
SQL&gt; -- This time the PK is a reverse-key index
SQL&gt; --
SQL&gt; 
SQL&gt; create table biggy (id number, name varchar2(100));

Table created.

SQL&gt; create unique index biggy_pk on biggy(id) reverse;

Index created.

SQL&gt; alter table biggy add constraint biggy_pk primary key(id) using index biggy_pk;

Table altered.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Insert 200,000 rows
SQL&gt; --
SQL&gt; 
SQL&gt; insert into biggy select rownum, 'BIGGY' from dual connect by level &lt;= 200000;

200000 rows created.

SQL&gt; 
SQL&gt; commit;

Commit complete.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Validate the index structure
SQL&gt; --
SQL&gt; -- Report on the current index configuration
SQL&gt; --
SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     200000           0        512          1

SQL&gt; 
SQL&gt; --
SQL&gt; -- The reverse-key index greatly improves the performance and
SQL&gt; -- decreases the instance of unused empty leaf blocks
SQL&gt; -- compared to our first run
SQL&gt; --
SQL&gt; 
SQL&gt; --
SQL&gt; -- Replace the 200,000 existing rows with 200,000 new rows
SQL&gt; --
SQL&gt; 
SQL&gt; exec delete_insert_rows(1000)

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Check current index structure
SQL&gt; --
SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     202645        2645        512          1

SQL&gt; 
SQL&gt; SELECT name,
  2       del_lf_rows,
  3       lf_rows - del_lf_rows lf_rows_used,
  4       to_char(del_lf_rows / (lf_rows)*100,'999.99999') ibadness
  5  FROM index_stats
  6  where name = upper('biggy_pk');

NAME                           DEL_LF_ROWS LF_ROWS_USED IBADNESS
------------------------------ ----------- ------------ ----------
BIGGY_PK                              2645       200000    1.30524

SQL&gt; 
SQL&gt; --
SQL&gt; -- Do it again
SQL&gt; --
SQL&gt; 
SQL&gt; exec delete_insert_rows(2000)

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     205095        5095        512          1

SQL&gt; 
SQL&gt; SELECT name,
  2       del_lf_rows,
  3       lf_rows - del_lf_rows lf_rows_used,
  4       to_char(del_lf_rows / (lf_rows)*100,'999.99999') ibadness
  5  FROM index_stats
  6  where name = upper('biggy_pk');

NAME                           DEL_LF_ROWS LF_ROWS_USED IBADNESS
------------------------------ ----------- ------------ ----------
BIGGY_PK                              5095       200000    2.48421

SQL&gt; 
SQL&gt; --
SQL&gt; -- Get the object_id for the PK index so we can perform a treedump
SQL&gt; --
SQL&gt; 
SQL&gt; column object_id new_value objid
SQL&gt; 
SQL&gt; select object_id from dba_objects where object_name = 'BIGGY_PK';

 OBJECT_ID
----------
     69718

SQL&gt; 
SQL&gt; --
SQL&gt; -- Execute the treedump for analysis
SQL&gt; --
SQL&gt; 
SQL&gt; alter session set events 'immediate trace name treedump level &amp;objid';

Session altered.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Go through 8 more runs of the delete/replace procedure
SQL&gt; --
SQL&gt; 
SQL&gt; exec delete_insert_rows(3000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(4000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(5000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(6000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(7000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(8000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(9000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(10000)

PL/SQL procedure successfully completed.

SQL&gt; --
SQL&gt; -- Check the index structure and report on it
SQL&gt; --
SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     210249       10249        523          1

SQL&gt; 
SQL&gt; SELECT name,
  2       del_lf_rows,
  3       lf_rows - del_lf_rows lf_rows_used,
  4       to_char(del_lf_rows / (lf_rows)*100,'999.99999') ibadness
  5  FROM index_stats
  6  where name = upper('biggy_pk');

NAME                           DEL_LF_ROWS LF_ROWS_USED IBADNESS
------------------------------ ----------- ------------ ----------
BIGGY_PK                             10249       200000    4.87470

SQL&gt; 
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
Again no change is realized; we try again with manual segment space management and collect the results:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;  
SQL&gt; drop table biggy purge;

Table dropped.

SQL&gt; 
SQL&gt; 
SQL&gt; --
SQL&gt; -- Create our test table and primary key index
SQL&gt; -- Use a tablespace with manual segment management
SQL&gt; --
SQL&gt; 
SQL&gt; create table biggy (id number, name varchar2(100));

Table created.

SQL&gt; create unique index biggy_pk on biggy(id) tablespace bing_idx;

Index created.

SQL&gt; alter table biggy add constraint biggy_pk primary key(id) using index biggy_pk;

Table altered.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Insert 200,000 rows
SQL&gt; --
SQL&gt; 
SQL&gt; insert into biggy select rownum, 'BIGGY' from dual connect by level &lt;= 200000;

200000 rows created.

SQL&gt; 
SQL&gt; commit;

Commit complete.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Validate the index structure
SQL&gt; --
SQL&gt; -- Report on the current index configuration
SQL&gt; --
SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     200000           0        374          1

SQL&gt; 
SQL&gt; --
SQL&gt; -- Create a procedure to delete the existing rows and add
SQL&gt; -- new rows with increasing PK values
SQL&gt; --
SQL&gt; -- Note that we add a new record with an increasing PK at the
SQL&gt; -- end of the index right after we delete the index entry for
SQL&gt; -- the lowest PK value
SQL&gt; --
SQL&gt; --
SQL&gt; -- This can do some strange things to the index structure
SQL&gt; -- as the deleted leaf block cannot be reused since the new
SQL&gt; -- key value is outside of the key range the deleted leaf block
SQL&gt; -- is found in when ASSM is used
SQL&gt; --
SQL&gt; -- MSSM may eliminate the behaviour
SQL&gt; --
SQL&gt; 
SQL&gt; create or replace procedure delete_insert_rows(p_commit_after in number)
  2  as
  3       n number;
  4       m number;
  5  begin
  6       select min(id),max(id) into n,m from biggy;
  7       for i in 1..200000 loop
  8    delete from biggy where id=n+i-1;
  9    insert into biggy values(m+i,'Big index test');
 10    if mod(i,p_commit_after)=0 then
 11         commit;
 12    end if;
 13       end loop;
 14       commit;
 15  end;
 16  /

Procedure created.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Replace the 200,000 existing rows with 200,000 new rows
SQL&gt; --
SQL&gt; 
SQL&gt; exec delete_insert_rows(1000)

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Check current index structure
SQL&gt; --
SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     201000        1000        379          1

SQL&gt; 
SQL&gt; SELECT name,
  2       del_lf_rows,
  3       lf_rows - del_lf_rows lf_rows_used,
  4       to_char(del_lf_rows / (lf_rows)*100,'999.99999') ibadness
  5  FROM index_stats
  6  where name = upper('biggy_pk');

NAME                           DEL_LF_ROWS LF_ROWS_USED IBADNESS
------------------------------ ----------- ------------ ----------
BIGGY_PK                              1000       200000     .49751

SQL&gt; 
SQL&gt; --
SQL&gt; -- Do it again
SQL&gt; --
SQL&gt; 
SQL&gt; exec delete_insert_rows(2000)

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     202000        2000        382          1

SQL&gt; 
SQL&gt; SELECT name,
  2       del_lf_rows,
  3       lf_rows - del_lf_rows lf_rows_used,
  4       to_char(del_lf_rows / (lf_rows)*100,'999.99999') ibadness
  5  FROM index_stats
  6  where name = upper('biggy_pk');

NAME                           DEL_LF_ROWS LF_ROWS_USED IBADNESS
------------------------------ ----------- ------------ ----------
BIGGY_PK                              2000       200000     .99010

SQL&gt; 
SQL&gt; --
SQL&gt; -- Get the object_id for the PK index so we can perform a treedump
SQL&gt; --
SQL&gt; 
SQL&gt; column object_id new_value objid
SQL&gt; 
SQL&gt; select object_id from dba_objects where object_name = 'BIGGY_PK';

 OBJECT_ID
----------
     69720

SQL&gt; 
SQL&gt; --
SQL&gt; -- Execute the treedump for analysis
SQL&gt; --
SQL&gt; 
SQL&gt; alter session set events 'immediate trace name treedump level &amp;objid';

Session altered.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Go through 8 more runs of the delete/replace procedure
SQL&gt; --
SQL&gt; 
SQL&gt; exec delete_insert_rows(3000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(4000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(5000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(6000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(7000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(8000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(9000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(10000)

PL/SQL procedure successfully completed.

SQL&gt; --
SQL&gt; -- Check the index structure and report on it
SQL&gt; --
SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     210000       10000        422          1

SQL&gt; 
SQL&gt; SELECT name,
  2       del_lf_rows,
  3       lf_rows - del_lf_rows lf_rows_used,
  4       to_char(del_lf_rows / (lf_rows)*100,'999.99999') ibadness
  5  FROM index_stats
  6  where name = upper('biggy_pk');

NAME                           DEL_LF_ROWS LF_ROWS_USED IBADNESS
------------------------------ ----------- ------------ ----------
BIGGY_PK                             10000       200000    4.76190

SQL&gt; 
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
Again, no change.  One more time with the reverse-key index, using manual segment space management:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;  

SQL&gt; drop table biggy purge;

Table dropped.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Recreate our test table and primary key index
SQL&gt; -- This time the PK is a reverse-key index
SQL&gt; -- Again we use a tablespace with manual extent management
SQL&gt; --
SQL&gt; 
SQL&gt; create table biggy (id number, name varchar2(100));

Table created.

SQL&gt; create unique index biggy_pk on biggy(id) reverse tablespace bing_idx;

Index created.

SQL&gt; alter table biggy add constraint biggy_pk primary key(id) using index biggy_pk;

Table altered.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Insert 200,000 rows
SQL&gt; --
SQL&gt; 
SQL&gt; insert into biggy select rownum, 'BIGGY' from dual connect by level &lt;= 200000;

200000 rows created.

SQL&gt; 
SQL&gt; commit;

Commit complete.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Validate the index structure
SQL&gt; --
SQL&gt; -- Report on the current index configuration
SQL&gt; --
SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     200000           0        512          1

SQL&gt; 
SQL&gt; --
SQL&gt; -- The reverse-key index greatly improves the performance and
SQL&gt; -- decreases the instance of unused empty leaf blocks
SQL&gt; -- compared to our first run
SQL&gt; --
SQL&gt; 
SQL&gt; --
SQL&gt; -- Replace the 200,000 existing rows with 200,000 new rows
SQL&gt; --
SQL&gt; 
SQL&gt; exec delete_insert_rows(1000)

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Check current index structure
SQL&gt; --
SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     202645        2645        512          1

SQL&gt; 
SQL&gt; SELECT name,
  2       del_lf_rows,
  3       lf_rows - del_lf_rows lf_rows_used,
  4       to_char(del_lf_rows / (lf_rows)*100,'999.99999') ibadness
  5  FROM index_stats
  6  where name = upper('biggy_pk');

NAME                           DEL_LF_ROWS LF_ROWS_USED IBADNESS
------------------------------ ----------- ------------ ----------
BIGGY_PK                              2645       200000    1.30524

SQL&gt; 
SQL&gt; --
SQL&gt; -- Do it again
SQL&gt; --
SQL&gt; 
SQL&gt; exec delete_insert_rows(2000)

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     205061        5061        512          1

SQL&gt; 
SQL&gt; SELECT name,
  2       del_lf_rows,
  3       lf_rows - del_lf_rows lf_rows_used,
  4       to_char(del_lf_rows / (lf_rows)*100,'999.99999') ibadness
  5  FROM index_stats
  6  where name = upper('biggy_pk');

NAME                           DEL_LF_ROWS LF_ROWS_USED IBADNESS
------------------------------ ----------- ------------ ----------
BIGGY_PK                              5061       200000    2.46805

SQL&gt; 
SQL&gt; --
SQL&gt; -- Get the object_id for the PK index so we can perform a treedump
SQL&gt; --
SQL&gt; 
SQL&gt; column object_id new_value objid
SQL&gt; 
SQL&gt; select object_id from dba_objects where object_name = 'BIGGY_PK';

 OBJECT_ID
----------
     69722

SQL&gt; 
SQL&gt; --
SQL&gt; -- Execute the treedump for analysis
SQL&gt; --
SQL&gt; 
SQL&gt; alter session set events 'immediate trace name treedump level &amp;objid';

Session altered.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Go through 8 more runs of the delete/replace procedure
SQL&gt; --
SQL&gt; 
SQL&gt; exec delete_insert_rows(3000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(4000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(5000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(6000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(7000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(8000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(9000)

PL/SQL procedure successfully completed.

SQL&gt; exec delete_insert_rows(10000)

PL/SQL procedure successfully completed.

SQL&gt; --
SQL&gt; -- Check the index structure and report on it
SQL&gt; --
SQL&gt; 
SQL&gt; analyze index biggy_pk validate structure;

Index analyzed.

SQL&gt; 
SQL&gt; select name, height, lf_rows, del_lf_rows, lf_blks, br_blks from index_stats;

NAME                               HEIGHT    LF_ROWS DEL_LF_ROWS    LF_BLKS    BR_BLKS
------------------------------ ---------- ---------- ----------- ---------- ----------
BIGGY_PK                                2     210249       10249        523          1

SQL&gt; 
SQL&gt; SELECT name,
  2       del_lf_rows,
  3       lf_rows - del_lf_rows lf_rows_used,
  4       to_char(del_lf_rows / (lf_rows)*100,'999.99999') ibadness
  5  FROM index_stats
  6  where name = upper('biggy_pk');

NAME                           DEL_LF_ROWS LF_ROWS_USED IBADNESS
------------------------------ ----------- ------------ ----------
BIGGY_PK                             10249       200000    4.87470

SQL&gt; 
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
Notice the setting didn't do much of anything to improve the situation.&lt;br /&gt;
&lt;br /&gt;
So, what happened?  The initial pass, with the standard index, causes Oracle to wait to reuse empty leaf blocks until the branch block they are attached to is empty; since we deleted the smallest available key then inserted a new largest key the leaf block released could not immediately be reused as the branch block still had leaf blocks attached to it.  Somewhere around the middle of the whole delete/insert process the leaf blocks we released at the beginning of the process were finally available for reuse.  Reversing the key on the primary key index allowed reuse of the leaf blocks by the new keys since, in reverse order, they could 'fit in' to the key order of the index.  Manual segment space management didn't do much to improve this nor did setting session_cached_cursors to 0. &lt;br /&gt;
&lt;br /&gt;
Of course the ideal method is to delete the rows in batches with the intent of freeing the branch block so the empty leaf blocks can be reused but piecemeal deletes and inserts can and will happen in OLTP systems so such a scenario can be repeated in a running production database.  An interesting side note on this is that primary key indexes aren't usually rebuilt as reverse-key indexes unless block contention is high for the index yet that action can also dramatically reduce the number of empty leaf blocks in the index after rows are deleted.  It may be worth considering the use of a reverse-key primary key index to keep the index size 'reasonable'.&lt;br /&gt;
&lt;br /&gt;
It may be a rare occurrence to have an ever-increasing index even though volumes of data have been deleted but knowing what to do to help correct the situation may prove invaluable should the situation arise.  In my opinion it's better to know something you may not need rather than need something you do not know.&lt;br /&gt;
&lt;br /&gt;
My two cents.&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=0nfIesKyWvE:Zn6Ll3akOh8:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=0nfIesKyWvE:Zn6Ll3akOh8:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://oratips-ddf.blogspot.com/feeds/6283232057305635570/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=6283232057305635570" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/6283232057305635570?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/6283232057305635570?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2012/01/my-how-youve-grown.html" title="My, How You've Grown" /><author><name>David Fitzjarrell</name><uri>https://plus.google.com/102215427046411604711</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-y2hTTVpTe4o/AAAAAAAAAAI/AAAAAAAAAGw/KabdyVeaX9M/s512-c/photo.jpg" /></author><thr:total>2</thr:total></entry><entry gd:etag="W/&quot;DUIBQns-fyp7ImA9WhRWGEU.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-5289398491509978769</id><published>2011-10-27T18:13:00.011+01:00</published><updated>2012-01-06T22:05:53.557Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-06T22:05:53.557Z</app:edited><title>That's Your Problem</title><content type="html">Many times a problem can appear to be more complicated than it actually is.  This is due, I think, to being 'locked into' a thought process not conducive to solving the problem.  Knowing how to read the problem and discover the information provided can help tremendously in working toward a solution.  Let's look at a few  problems and their solutions to see how to change the way you think about, and look at, a problem.&lt;br /&gt;
&lt;br /&gt;
Jonathan Lewis provides the first problem we consider, although it actually appeared in the comp.databases.oracle.server newsgroup several  years ago.  It's gone through several iterations since its original offering and we'll consider the most recent of those here.  The problem:&lt;br /&gt;
&lt;br /&gt;
Two mathematicians met at their college reunion.  Wanting to keep current they started discussing their lives as only mathematicians can:&lt;br /&gt;
&lt;br /&gt;
Mathematician #1: So, do you have any children?&lt;br /&gt;
Mathematician #2: Yes, three girls.&lt;br /&gt;
Mathematician #1: Wonderful!  What are their ages?&lt;br /&gt;
Mathematician #2: I'll give you a clue: the product of their ages is 36.&lt;br /&gt;
Mathematician #1: Hmmm, good clue but not nearly enough information.&lt;br /&gt;
Mathematician #2: Well, the sum of their ages is the number of people in this room.&lt;br /&gt;
Mathematician #1: (After looking about the room) That's still not enough information.&lt;br /&gt;
Mathematician #2: One more clue: my oldest daughter has a pet hamster with a wooden leg.&lt;br /&gt;
Mathematician #1: I have it now -- say, are the twins identical?&lt;br /&gt;
&lt;br /&gt;
Given that &lt;em&gt;&lt;b&gt;all&lt;/b&gt;&lt;/em&gt; of the information needed to solve the problem is &lt;em&gt;&lt;b&gt;in&lt;/b&gt;&lt;/em&gt; the problem, what are the ages of the three girls?&lt;br /&gt;
&lt;br /&gt;
The problem seems unsolvable at first glance but there is more information available than is originally seen.  Let's state what we know from the problem:&lt;br /&gt;
&lt;br /&gt;
1 -- There are three girls&lt;br /&gt;
2 -- Their ages, multiplied together, have a product of 36&lt;br /&gt;
3 -- The sum of their ages is (to us, anyway) an undisclosed number&lt;br /&gt;
4 -- The oldest daughter has a hamster with a wooden leg&lt;br /&gt;
&lt;br /&gt;
A strange collection of facts, some might say.  But, looking deeper into the problem we can find some logic and answers not obvious from casual inspection. Let's start with the product of the ages:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;  
SQL&gt; --
SQL&gt; -- Generate an age list for the girls
SQL&gt; --
SQL&gt; -- Maximum age is 36
SQL&gt; --
SQL&gt; with age_list as (
  2        select rownum age
  3        from all_objects
  4        where rownum &lt;= 36
  5  )
  6  select *
  7  from age_list;

       AGE                                                                      
----------                                                                 
1                                                                      
2                                                                      
3                                                                      
4                                                                      
5                                                                      
6                                                                      
7                                                                      
8                                                                      
9                                                                      
10                                                                      
11                                                                      
12                                                                      
13                                                                      
14                                                                      
15                                                                      
16                                                                      
17                                                                      
18                                                                      
19                                                                      
20                                                                      
21                                                                      
22                                                                      
23                                                                      
24                                                                      
25                                                                      
26                                                                      
27                                                                      
28                                                                      
29                                                                      
30                                                                      
31                                                                      
32                                                                      
33                                                                      
34                                                                      
35                                                                      
36                                                                      

36 rows selected.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Return only the age groupings whose product
SQL&gt; -- is 36
SQL&gt; --
SQL&gt; -- Return, also, the sum of the ages
SQL&gt; --
SQL&gt; -- This restricts the set of values needed to
SQL&gt; -- solve the problem
SQL&gt; --
SQL&gt; with age_list as (
  2        select rownum age
  3        from all_objects
  4        where rownum &lt;= 36
  5  ),
  6  product_check as (
  7        select
  8        age1.age as youngest,
  9        age2.age as middle,
 10        age3.age as oldest,
 11        age1.age+age2.age+age3.age as sum,
 12        age1.age*age2.age*age3.age as product
 13        from age_list age1, age_list age2, age_list age3
 14        where age2.age &gt;= age1.age
 15        and age3.age &gt;= age2.age
 16        and age1.age*age2.age*age3.age = 36
 17  )
 18  select *
 19  from product_check
 20  order by 1,2,3;

  YOUNGEST     MIDDLE     OLDEST        SUM    PRODUCT                          
---------- ---------- ---------- ---------- ----------                          
         1          1         36         38         36                          
         1          2         18         21         36                          
         1          3         12         16         36                          
         1          4          9         14         36                          
         1          6          6         13         36                          
         2          2          9         13         36                          
         2          3          6         11         36                          
         3          3          4         10         36                          

8 rows selected.

SQL&gt; 
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
Notice we return not only the product of the ages but also the sums of the various combinations, as we'll need this information later on in the problem.  Now another 'fact' emerges:&lt;br /&gt;
&lt;br /&gt;
5 -- Knowing the sum of the ages doesn't help matters much&lt;br /&gt;
&lt;br /&gt;
This reveals that there is more than one combination of ages which produce the same sum:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;  
QL&gt; --
SQL&gt; -- Find, amongst the acceptable values,
SQL&gt; -- those sets where the summed value is
SQL&gt; -- the same
SQL&gt; --
SQL&gt; -- This is necessary as providing the sum
SQL&gt; -- was of little direct help in solving the
SQL&gt; -- problem
SQL&gt; --
SQL&gt; with age_list as (
  2        select rownum age
  3        from all_objects
  4        where rownum &lt;= 36
  5  ),
  6  product_check as (
  7        select
  8        age1.age as youngest,
  9        age2.age as middle,
 10        age3.age as oldest,
 11        age1.age+age2.age+age3.age as sum,
 12        age1.age*age2.age*age3.age as product
 13        from age_list age1, age_list age2, age_list age3
 14        where age2.age &gt;= age1.age
 15        and age3.age &gt;= age2.age
 16        and age1.age*age2.age*age3.age = 36
 17  ),
 18  summed_check as (
 19        select youngest, middle, oldest, sum, product
 20        from (
 21         select youngest, middle, oldest, sum, product,
 22         count(*) over (partition by sum) ct
 23         from product_check
 24        )
 25        where ct &gt; 1
 26  )
 27  select *
 28  from summed_check;

  YOUNGEST     MIDDLE     OLDEST        SUM    PRODUCT                          
---------- ---------- ---------- ---------- ----------                          
         2          2          9         13         36                          
         1          6          6         13         36                          

SQL&gt; 
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
Now we know the number of people in the room and why the sum wasn't enough information to solve the problem.  The final 'nail in the coffin' (so to speak) is the owner of the hamster with the wooden leg; the problem states:&lt;br /&gt;
&lt;br /&gt;
Mathematician #2: One more clue: my oldest daughter has a pet hamster with a wooden leg.&lt;br /&gt;
&lt;br /&gt;
It's not the hamster, it is the fact that the oldest daughter (there's only one) exists.  Knowing that last piece of information provides the final answer:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;  
SQL&gt; 
SQL&gt; --
SQL&gt; -- Return the one set of values meeting all of
SQL&gt; -- the criteria:
SQL&gt; --
SQL&gt; -- Product of 36
SQL&gt; -- Sum of some unknown number
SQL&gt; -- Oldest child exists
SQL&gt; --
SQL&gt; with age_list as (
  2        select rownum age
  3        from all_objects
  4        where rownum &lt;= 36
  5  ),
  6  product_check as (
  7        select
  8        age1.age as youngest,
  9        age2.age as middle,
 10        age3.age as oldest,
 11        age1.age+age2.age+age3.age as sum,
 12        age1.age*age2.age*age3.age as product
 13        from age_list age1, age_list age2, age_list age3
 14        where age2.age &gt;= age1.age
 15        and age3.age &gt;= age2.age
 16        and age1.age*age2.age*age3.age = 36
 17  ),
 18  summed_check as (
 19        select youngest, middle, oldest, sum, product
 20        from (
 21         select youngest, middle, oldest, sum, product,
 22         count(*) over (partition by sum) ct
 23         from product_check
 24        )
 25        where ct &gt; 1
 26  )
 27  select *
 28  from summed_check
 29  where oldest &gt; middle;

  YOUNGEST     MIDDLE     OLDEST        SUM    PRODUCT                          
---------- ---------- ---------- ---------- ----------                          
         2          2          9         13         36                          

SQL&gt; 
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
The ages of the girls are 9, 2 and 2 which also clarifies the question of identical twins.&lt;br /&gt;
&lt;br /&gt;
The problem was solved in a systematic and (to me, at least) logical way by breaking the problem down into workable pieces.&lt;br /&gt;
&lt;br /&gt;
So you don't encounter such problems at college reunions or parties (what a dull life that must be); you may encounter them at work.  This next problem was presented in the Oracle PL/SQL group:&lt;br /&gt;
&lt;br /&gt;
Hi, &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I have 3 columns of data &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Column 1:subscription &lt;br /&gt;
Column 2: invoice number &lt;br /&gt;
Column 3: Service &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I need to seperate the subscription types into new, renewals and additional &lt;br /&gt;
which is fine but the next bit i am having trouble &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Each invoice number can have 1 or more service &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
e.g. &lt;br /&gt;
Invoice Number              Service &lt;br /&gt;
123                               Photocopying &lt;br /&gt;
123                               Printing &lt;br /&gt;
123                               Scan &amp; Store &lt;br /&gt;
234                               Photocopying &lt;br /&gt;
234                               Scan &amp; Store &lt;br /&gt;
345                               Photocopying &lt;br /&gt;
345                               Printing &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I apply a rate for each service e.g. &lt;br /&gt;
photocopying = 1.5 &lt;br /&gt;
printing = 1.7 &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
but if Scan and store is in an invoice with photocopying we charge an extra &lt;br /&gt;
1.5 &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
but if printing is a service with the scan and store a different rate &lt;br /&gt;
applies 1.7 &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
so i can't just count scan and store and apply a rate i have to figure out &lt;br /&gt;
if it is with photocopying or with printing and then apply the rate &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
What I want to be able to do is creat a table with columns that calculates &lt;br /&gt;
this &lt;br /&gt;
so i get a 4 columns: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Service                       usage       rate      total &lt;br /&gt;
photocopying                3          1.5         4.5 &lt;br /&gt;
Printing                        2          1.7         3.4 &lt;br /&gt;
Scan &amp; Store               1          1.5          1.5 &lt;br /&gt;
Scan &amp; Store w/Print    1          1.7          1.7 &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The problem comes in when i'm trying to count scan and store wit/without &lt;br /&gt;
printing. I can't figure it out. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I import the report from an excel spreadsheet into acces and want to run a &lt;br /&gt;
query that does all this... &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
thanks in advance, &lt;br /&gt;
ainese &lt;br /&gt;
&lt;br /&gt;
With this problem I decided to change the table a bit and add a numeric SERVICE_CD column:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;  
SQL&gt; Create table subscription(
  2          subscr_type varchar2(15),
  3          invoice number,
  4          service varchar2(40),
  5          service_cd number
  6  );

Table created.

SQL&gt;
SQL&gt; insert all
  2  into subscription
  3  values('RENEWAL',123,'Photocopying',0)
  4  into subscription
  5  values('RENEWAL',123,'Printing',2)
  6  into subscription
  7  values('RENEWAL',123,'Scan '||chr(38)||' Store',5)
  8  into subscription
  9  values('ADDITIONAL',234,'Photocopying',0)
 10  into subscription
 11  values('ADDITIONAL',234,'Scan '||chr(38)||' Store',5)
 12  into subscription
 13  values('NEW',345,'Photocopying',0)
 14  into subscription
 15  values('NEW',345,'Printing',2)
 16  select * From dual;

7 rows created.

SQL&gt;
SQL&gt; commit;

Commit complete.

SQL&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
Using the wm_concat() function and the BITAND operator produced results that will make the final solution easier to code; using BITAND allows Oracle to generate a result based upon the sum of the SERVICE_CD values and by properly choosing those SERVICE_CD entries make it easier to isolate the various combinations:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;  
SQL&gt; select subscr_type, invoice, services,
  2         bitand(service_cds, 0) col1,
  3         bitand(service_cds, 2) col2,
  4         bitand(service_cds, 7) col3
  5  from
  6  (select subscr_type, invoice, wm_concat(service) services, sum(service_cd) service_cds
  7  from subscription
  8  group by subscr_type,invoice);

SUBSCR_TYPE        INVOICE SERVICES                                       COL1       COL2       COL3
--------------- ---------- ---------------------------------------- ---------- ---------- ----------
NEW                    345 Photocopying,Printing                             0          2          2
RENEWAL                123 Photocopying,Printing,Scan &amp; Store                0          2          7
ADDITIONAL             234 Photocopying,Scan &amp; Store                         0          0          5


SQL&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
Knowing which BITAND results indicate which chargeable combinations allows using DECODE to produce a version of the desired results:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;  

SQL&gt; column services format a40
SQL&gt; break on report skip 1
SQL&gt; compute sum  of photocopy printing scan_and_store scan_and_store_w_prt on report
SQL&gt;
SQL&gt; select subscr_type, invoice, services,
  2         decode(bitand(service_cds, 0), 0, 1.5, 0) photocopy,
  3         decode(bitand(service_cds, 2), 2, 1.7, 0) printing,
  4         decode(bitand(service_cds, 7), 5, 1.5, 0) scan_and_store,
  5         decode(bitand(service_cds, 7), 7, 1.7, 0) scan_and_store_w_prt
  6  from
  7  (select subscr_type, invoice, wm_concat(service) services, sum(service_cd) service_cds
  8  from subscription
  9  group by subscr_type,invoice);

SUBSCR_TYPE INVOICE SERVICES                           PHOTOCOPY PRINTING SCAN_STORE SCAN_STORE_PRT
----------- ------- ---------------------------------- --------- -------- ---------- --------------
NEW             345 Photocopying,Printing                    1.5      1.7          0              0
RENEWAL         123 Photocopying,Printing,Scan &amp; Store       1.5      1.7          0            1.7
ADDITIONAL      234 Photocopying,Scan &amp; Store                1.5        0        1.5              0
                                                       --------- -------- ---------- --------------
sum                                                          4.5      3.4        1.5            1.7

SQL&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
All services in this example are charged the appropriate rates, including the adjustments made for certain combinations of service.&lt;br /&gt;
&lt;br /&gt;
One last problem is one found often on the web:&lt;br /&gt;
&lt;br /&gt;
Display the second highest salary in the employee table&lt;br /&gt;
Display the employee id, first name, last name and salary for employees earning the second highest salary&lt;br /&gt;
&lt;br /&gt;
Depending on which question is asked several solutions present themselves.  The first is the 'obvious' solution:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;  
SQL&gt; select salary
  2  from
  3  (select salary from employees order by 1 desc)
  4  where rownum = 2;

no rows selected

SQL&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
which doesn't work because ROWNUM is never set to 1 so it can't get to 2.  A modest rewrite produces:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;  
SQL&gt; select salary
  2  from
  3  (select rownum rn, salary from
  4  (select salary from employees order by 1 desc))
  5  where rn = 2;

    SALARY
----------
     17000


1 row selected.

SQL&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
which still might not be the correct answer as more than one person may have the same salary, including the highest. Another rewrite, using DENSE_RANK() provides the solution:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;  
SQL&gt; select salary from
  2  (select salary, dense_rank() over (order by salary desc) rk
  3          from employees)
  4  where rk=2;

    SALARY
----------
     17000
     17000


2 rows selected.

SQL&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
To produce more information the above query needs a small modification:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;  
SQL&gt; select employee_id, first_name, last_name, salary
  2  from
  3  (select employee_id, first_name, last_name, salary, rank() over (order by salary desc) rk
  4   from employees)
  5  where rk =2;

EMPLOYEE_ID FIRST_NAME           LAST_NAME                     SALARY
----------- -------------------- ------------------------- ----------
        101 Neena                Kochhar                        17000
        102 Lex                  De Haan                        17000


2 rows selected.

SQL&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
RANK() and DENSE_RANK() do just what they're named -- rank the requested values -- but only DENSE_RANK() will not skip ranking numbers when duplicate values exist:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;  
SQL&gt; select salary, rank() over (order by salary desc) rk
  2          from employees;

    SALARY         RK
---------- ----------
     24000          1
     17000          2
     17000          2
     14000          4
     13500          5
     13000          6
     12000          7
     12000          7
     12000          7
     11500         10
...

&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
Notice that the third highest salary is ranked 4 with RANK(); not so with DENSE_RANK():&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;  
SQL&gt; select salary, dense_rank() over (order by salary desc) rk
  2          from employees;

    SALARY         RK
---------- ----------
     24000          1
     17000          2
     17000          2
     14000          3
     13500          4
     13000          5
     12000          6
     12000          6
     12000          6
     11500          7
     11000          8
     11000          8
     11000          8
     10500          9
     10500          9
     10000         10
     10000         10
     10000         10
     10000         10
...
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
which is why DENSE_RANK() was used to solve the problem.&lt;br /&gt;
&lt;br /&gt;
Problem solving, when given a little thought, isn't a terrible chore if you know how to read the problem and extract known information.  From that you can eventually arrive at a solution (and, yes, multiple solutions can exist depending upon how you think about the problem).  The above are examples to get you started thinking in the 'right' direction.  As always, some practice at solving problems is recommended so take these problems, work them through, change data, work them through again (and  you may find holes in my solutions that I didn't consider).  The more you practice, the more you learn.&lt;br /&gt;
&lt;br /&gt;
A train leaves station A at 3:30 PM and travels west at 50 miles per hour ...&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=_DWKGm5CRN8:Meqx2ul5Ek8:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=_DWKGm5CRN8:Meqx2ul5Ek8:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://oratips-ddf.blogspot.com/feeds/5289398491509978769/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=5289398491509978769" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/5289398491509978769?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/5289398491509978769?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2011/10/thats-your-problem.html" title="That's Your Problem" /><author><name>David Fitzjarrell</name><uri>https://plus.google.com/102215427046411604711</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-y2hTTVpTe4o/AAAAAAAAAAI/AAAAAAAAAGw/KabdyVeaX9M/s512-c/photo.jpg" /></author><thr:total>4</thr:total></entry><entry gd:etag="W/&quot;A08GR3k6cCp7ImA9WhRVE0w.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-7784616094228893840</id><published>2011-08-26T01:09:00.006+01:00</published><updated>2012-01-11T22:10:26.718Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-11T22:10:26.718Z</app:edited><title>Parallel Universe</title><content type="html">An oft-used (and subsequently oft-abused) execution path is parallel execution, usually 'instigated' by some sort of parallel hint.  Developers, albeit with the best intentions, misuse and abuse this mechanism because of faulty logic, that 'logic' being that if one process executes in X amount of time then Y parallel processes will execute in X/Y amount of time, and nothing could be further from the truth in many cases.  Yes, there are opportunities to use parallelism to speed up processing but in most of the cases I've seen it's doing more harm than good.&lt;br /&gt;
&lt;br /&gt;
To illustrate this in a 'real world' situation say you're in a restaurant and the service appears to be slow; you think 'if I had three waitresses instead of one I'd get my food faster', but let's look at that  from a different point of view.  Three waitresses for one table means that all three waitresses need to communicate with each other to avoid repeating work done by the others (this, of course, takes time).  Each waitress needs to be assigned specific duties but also must be available to take over for another if something happens (one broke her toe, for instance); such an occurrence requires reassignment of duties and adjusting the schedule to accomodate the change.  Eventually you get your order but it will take MORE time than if a single waitress performed all of the necessary tasks.&lt;br /&gt;
&lt;br /&gt;
Parallel processing does more than simply 'divide and conquer' as it requires several steps most developers may not know about or may ignore entirely.  DSS and DW systems, running with large numbers of CPUs, can benefit from parallel processing as the load can be distributed among the CPUs reducing the load on a single processor.  OLTP systems, on the other hand, usually involve operations which are quick to begin with and the overhead of implementing parallel processing is quite large compared to the overall execution time; additionally it may take longer to complete the parallel execution than it would to properly tune the query for single-threaded processing and eliminate the parallelism altogether.&lt;br /&gt;
&lt;br /&gt;
So what does parallel processing bring to the table?  Obviously the &lt;b&gt;&lt;e&gt;possibility&lt;/e&gt;&lt;/b&gt;, but not the guarantee, of reduced execution time.  What does it cost?  That depends on the system but for OLTP systems it usually creates more work than it accomplishes as a parallel query coordinator process is spawned, along with X number of parallel slaves all 'talking' to that coordinator as well as a parallel-to-serial operation by the coordinator to assemble the final results.  Many OLTP systems aren't designed for parallel processing (and rightly so) as parallel execution is designed to utilize resources that may remain idle (CPU, memory) and are in sufficient quantity to warrant generating the additional overhead required.  Queries and statements should be tuned properly (there, I've said it again) for speedy execution and slapping a parallel hint on them isn't the correct way to go about the tuning process.  Looking at an example of the differences between parallel and serial execution might help clear the air.&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;  
SQL&gt; select id, txtval, status
  2  from para_tst pt
  3  where id between 9001 and 34001;

        ID TXTVAL                          STATUS
---------- ------------------------------ -------
      9389 ALL_SCHEDULER_RUNNING_JOBS       VALID
      9390 USER_SCHEDULER_RUNNING_JOBS      VALID
      9391 USER_SCHEDULER_RUNNING_JOBS      VALID
[...]
     20772 /130d52e2_JDK2Sorter             VALID
     20773 /130d52e2_JDK2Sorter             VALID
     20774 /4c28cb16_ToolLogOptions         VALID
     20775 /4c28cb16_ToolLogOptions         VALID
     20776 /cbd9a55f_AbortException         VALID
     20777 /cbd9a55f_AbortException         VALID

591960 rows selected.

Elapsed: 00:02:03.68

Execution Plan
----------------------------------------------------------
Plan hash value: 350193380

---------------------------------------------------------------------------------------------------
| Id | Operation                | Name     | Rows | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
---------------------------------------------------------------------------------------------------
|  0 | SELECT STATEMENT         |          |  593K|    20M|  1698   (2)| 00:00:21 |       |       |
|  1 |  PARTITION RANGE ITERATOR|          |  593K|    20M|  1698   (2)| 00:00:21 |    19 |    21 |
|* 2 |   TABLE ACCESS FULL      | PARA_TST |  593K|    20M|  1698   (2)| 00:00:21 |    19 |    21 |
---------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("ID"&lt;=34001)


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
      45396  consistent gets
        156  physical reads
          0  redo size
   21517854  bytes sent via SQL*Net to client
     434616  bytes received via SQL*Net from client
      39465  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
     591960  rows processed

SQL&gt; 
SQL&gt; select /*+ parallel(pt 2) pq_distribute(pt partition) */
  2        id, txtval, status
  3  from para_tst pt
  4  where id between 9001 and 34001;

        ID TXTVAL                           STATUS
---------- ------------------------------- -------
     18404 /79bc64f9_JvmMemoryMeta           VALID
     18405 /b80019c2_EnumJvmThreadContent    VALID
     18406 /b80019c2_EnumJvmThreadContent    VALID
     18407 /bcfa29b5_EnumJvmThreadCpuTime    VALID
     18408 /bcfa29b5_EnumJvmThreadCpuTime    VALID
     17997 /b3bd73eb_CommonClassObject       VALID
     17998 /b3bd73eb_CommonClassObject       VALID
     17999 /c5a69e17_ServerSchemaObject1     VALID
[...]
     20724 /4bd3ef8d_KnownOptions4           VALID
     20725 /4bd3ef8d_KnownOptions4           VALID
     20726 /75d2b0ba_KnownOptions5           VALID
     20727 /75d2b0ba_KnownOptions5           VALID
     20728 /75e9b2d4_KnownOptions            VALID
     20729 /75e9b2d4_KnownOptions            VALID

591960 rows selected.

Elapsed: 00:02:23.59

Execution Plan
----------------------------------------------------------
Plan hash value: 1393746857

-------------------------------------------------------------------------------------------------------------------------
| Id | Operation            | Name     | Rows | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |    TQ |IN-OUT| PQ Distr |
-------------------------------------------------------------------------------------------------------------------------
|  0 | SELECT STATEMENT     |          |  593K|    20M|   942   (1)| 00:00:12 |       |       |       |      |          |
|  1 |  PX COORDINATOR      |          |      |       |            |          |       |       |       |      |          |
|  2 |   PX SEND QC (RANDOM)| :TQ10000 |  593K|    20M|   942   (1)| 00:00:12 |       |       | Q1,00 | P-&gt;S | QC (RAND)|
|  3 |    PX BLOCK ITERATOR |          |  593K|    20M|   942   (1)| 00:00:12 |    19 |    21 | Q1,00 | PCWC |          |
|* 4 |     TABLE ACCESS FULL| PARA_TST |  593K|    20M|   942   (1)| 00:00:12 |    19 |    21 | Q1,00 | PCWP |          |
-------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   4 - filter("ID"&lt;=34001)


Statistics
----------------------------------------------------------
         11  recursive calls
          1  db block gets
       6396  consistent gets
       6154  physical reads
         96  redo size
   21534533  bytes sent via SQL*Net to client
     434616  bytes received via SQL*Net from client
      39465  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
     591960  rows processed

SQL&gt; 

&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
On a system running 11.2.0.2 with &lt;b&gt;&lt;e&gt;absolutely no load&lt;/e&gt;&lt;/b&gt; the parallel execution takes almost twenty seconds longer; on a heavily used OLTP system this difference will likely be greater as the overhead to run in parallel is greater than that for the 'straight' query.  Resources allocated in OLTP configurations are intended for multiple users performing concurrent processing thus there usually aren't 'extra' CPU cycles or memory to toss at parallel execution.  Since the parallel query slaves will be waiting for resources (just like their user session counterparts) this will increase the response time and delay processing until such resources are freed.  Notice also the execution path, which includes the parallel-to-serial operation that consolodates the outputs from the parallel query slaves into the final result set.  Depending upon the server configuration there could be many more parallel slaves than shown here, and 'funneling' all of that data into a single 'pipe' consumes time and resources you most likely won't have in the OLTP environment.&lt;br /&gt;
&lt;br /&gt;
When is it good practice to use parallel processing?  One situation which comes to mind is in creating/populating tables using complex queries that may return faster in parallel than with standard serial processing.  An application on which I worked was populating a table with a rather benign join but it was taking far too long to complete the load -- the elapsed time exceeded the batch processing window.  Using parallelism (along with regularly updated statistics) dramatically reduced the response time from over an hour to less than two minutes, well within the batch window allowing the rest of the processing to continue.  [Note that this was a batch process, run outside of the normal business day, which freed resources normally allocated to user sessions.]  There are others, outside of data warehousing applications, but they're exceptions and not the rule.&lt;br /&gt;
&lt;br /&gt;
So, we've learned that even though parallelism would appear to make things go faster in reality that's not often the case due to the extra overhead in managing additional processes and consolodating the individual results into the final result set.  We've also learned that simply slapping a /*+ parallel */ hint into a query doesn't constitute tuning and doing so can make performance worse instead of better.  The correct choice is to properly tune the query or queries in question using resources such as AWR and ASH reports, execution plans and wait statistics to pinpoint the problem area or areas to address.  Parallelism isn't a silver bullet and wasn't intended to be and should be used sparingly, if at all.&lt;br /&gt;
&lt;br /&gt;
Now, where's my sandwich?&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=XeCBN7nA_6Y:rCzzdXujDm4:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=XeCBN7nA_6Y:rCzzdXujDm4:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://oratips-ddf.blogspot.com/feeds/7784616094228893840/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=7784616094228893840" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/7784616094228893840?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/7784616094228893840?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2011/08/parallel-universe.html" title="Parallel Universe" /><author><name>David Fitzjarrell</name><uri>https://plus.google.com/102215427046411604711</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-y2hTTVpTe4o/AAAAAAAAAAI/AAAAAAAAAGw/KabdyVeaX9M/s512-c/photo.jpg" /></author><thr:total>1</thr:total></entry><entry gd:etag="W/&quot;A0YHSH4-eip7ImA9WhZWE04.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-5503420324870865832</id><published>2011-05-13T17:44:00.001+01:00</published><updated>2011-05-14T03:12:19.052+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-05-14T03:12:19.052+01:00</app:edited><title>HA HA  HA</title><content type="html">Apparently there is still confusion over which Oracle feature provides high-availability and which provides disaster recovery.  This &lt;a href="http://www.oracle-dba-database-administration.com/oracle-data-guard.html"&gt;DBA&lt;/a&gt; seems to believe that Data Guard is a high-availability (HA) solution; I don't consider it so as we'll soon discover.  Let's define what high-availability is then see which product and/or feature satisfies that definition.&lt;br /&gt;
&lt;br /&gt;
To be a high-availability solution it must &lt;b&gt;&lt;e&gt;provide relatively uninterrupted access to the production system by implementing a mechanism where failures are handled in a transparent manner (the user community is unaware of failures which could affect access)&lt;/e&gt;&lt;/b&gt;.&lt;br /&gt;
&lt;br /&gt;
Data Guard, in all of its glory, does not provide such access in my opinion, although the Oracle documentation says otherwise; Real Application Clusters does, as does an older Oracle product called FailSafe and an even older product that was cumbersome to configure and use, Oracle Parallel Server.  Still there are DBAs in the workforce who firmly believe that Data Guard is a valid high-availability solution, even knowing that a failover involves time where users have &lt;b&gt;&lt;e&gt;no&lt;/e&gt;&lt;/b&gt; access the database.  [Apparently my idea of HA and Oracle's differs.]  Given the criteria listed above Data Guard does not, in my mind, fit the bill.  So why do some DBAs consider it high-availability?  Let's see what Data Guard does do and maybe we'll see why I don't consider it that way.&lt;br /&gt;
&lt;br /&gt;
Data Guard provides a mechanism whereby Oracle will keep one or more databases synchronized with the primary database.  For a physical standby configuration in release 10.2 three protection modes are available&lt;br /&gt;
&lt;br /&gt;
Maximum Protection&lt;br /&gt;
Maximum Availability&lt;br /&gt;
Maximum Performance&lt;br /&gt;
&lt;br /&gt;
Maximum Protection mode guarantees that the standby will be in 'lock step' with the primary as all transactions are written to both the primary redo logs and the standby redo logs with the caveat that if Oracle cannot write a transaction to the standby redo logs the primary will suspend activity until the error causing the write issue is corrected.  No transasction can commit until all local and remote redo has been written successfully.  This ensures a seamless cutover should a disaster strike, but it also inconveniences production users should problems in standby redo log writes occur.  Maximum Availability mode works like Maximum Protection mode until a standby redo log write problem occurs, when Oracle switches to Maximum Performance mode until the standby redo log write issue is resolved, at which time the standby redo log writes catch up with the primary.  Maximum Performance mode allows a transaction to commit after successfully writing the redo entries to the local redo logs regardless of whether the standby redo log writes have completed.  [In 11.2 a snapshot mode is available which can be converted to a physical standby at any time, and allows for read/write access to the data.  Also available in that release is support for redo apply to a physical standby database open for read access.]  A logical standby configuration is also available which relies upon log shipping to the standby where Log Miner is used to extract and apply DDL and DML changes, although there are a few data types (listed in the online documentation) which won't replicate in such a setup.  For the purposes of this discussion only the physical standby configuration will be considered as it will replicate all changes made to the primary thus providing a byte-for-byte replica of the primary.&lt;br /&gt;
&lt;br /&gt;
A physical standby can provide (depending upon the protection mode) an exact 'point in time' copy of the primary so that no transactions are 'pending' due to archive log transfers.  [In Maximum Performance mode, if standby redo logs are not configured then the standby is synchronized to the last log transferred from the primary leaving a gap of several minutes worth of transactions at the standby site.]  This does NOT provide a high-availability configuration as failover tasks consume time and take the database out of service until the failover is complete.  Since high-availability is defined as &lt;b&gt;&lt;e&gt;relatively uninterrupted&lt;/e&gt;&lt;/b&gt; access to the database even during failure of some resources Data Guard cannot, and should not, be used if high-availability (meaning no downtime as RAC provides it) is desired or required.  It is a Disaster Recovery (DR) solution and DR and HA are not the same in my book.  [Golden Gate provides both DR (with Active Data Guard) and real-time replication solutions through the same interface, neither of which are high-availability offerings even though many 'experts', and Oracle Corporation, offer the product as a high-availability configuration.]&lt;br /&gt;
&lt;br /&gt;
Real Application Clusters, or RAC as it's known industry-wide, &lt;b&gt;&lt;e&gt;is&lt;/e&gt;&lt;/b&gt; an HA solution as it provides the uninterrupted access required.  Unless this is a single-node RAC (a configuration available in release 11.2 which provides for expansion and is primarily designed for development and testing purposes) this option is configurable to transparently failover to a known good node should one node fail, the key term being 'transparently'.  No user interaction or intervention is necessary as RAC seamlessly transfers work to a good node and continues without inconveniencing the users.  The database is available as long as at least one node remains operational; that, of course, could slow down transactional activity depending on the available memory on that node but there is &lt;b&gt;&lt;e&gt;no&lt;/e&gt;&lt;/b&gt; loss of service.  Contrast that to Data Guard, where the primary database is no longer functioning and a secondary database, in a physically separate location, must be converted from being the standby to being the primary before users can resume work.  Also add the time to reconfigure the old primary to being the new standby and it's clear this is not high-availability.&lt;br /&gt;
&lt;br /&gt;
As another aspect of this discussion a RAC configuration involves one database and two or more clustered instances accessing that database, and Data Guard involves two or more separate databases, usually found in two or more physical locations.  Yes, the tnsnames.ora files can be configured to 'fail over' to the first active production site so that users need not reconfigure SQL*Net to access the former standby database should it be needed but that isn't the issue with Data Guard; the issue is the failover time required to exchange the roles of primary and standby which interrupts service until the transition is complete.  Improvements in Data Guard may have decreased the downtime considerably although I would have a difficult time recommending Data Guard as an HA offering.&lt;br /&gt;
&lt;br /&gt;
Don't get the wrong idea about Data Guard; it's an excellent technology that provides data protection in the event of a catastrophic disaster at the primary data center and it is often used in conjunction with RAC to provide an environment resilient to node failure and complete disaster.  If you want HA (in my opinion) then you need to consider RAC as the 'out of the box' solution provided by Oracle as it handles node failures with grace (and possibly style) and keeps the work flowing seamlessly.  Know that data protection and high-availability are different, but compatible, areas which need to be considered when constructing a robust database configuration and that the former cannot replace the latter (and, again, this is my opinion).&lt;br /&gt;
&lt;br /&gt;
[The Oracle documentation, at first blush, agrees with my definition but later on in the &lt;a href="http://download.oracle.com/docs/cd/E11882_01/server.112/e17157/overview.htm#HAOVW001"&gt;depths of the HA discussion&lt;/a&gt; clearly states, without question, that Oracle considers Data Guard a high-availability solution.  Far be it from me to argue with Oracle.]&lt;br /&gt;
&lt;br /&gt;
Data Guard and RAC are both well-tested and reliable options to consider when designing and implementing a fault-tolerant configuration, but of the two only RAC, in my estimation, provides high-availability.&lt;br /&gt;
&lt;br /&gt;
Unless you like explaining to upper management why your 'HA solution' required an outage.&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=rQPGdqz3oCI:A9zL8oxj6Z0:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=rQPGdqz3oCI:A9zL8oxj6Z0:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://oratips-ddf.blogspot.com/feeds/5503420324870865832/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=5503420324870865832" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/5503420324870865832?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/5503420324870865832?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2011/05/ha-ha-ha.html" title="HA HA  HA" /><author><name>David Fitzjarrell</name><uri>https://plus.google.com/102215427046411604711</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-y2hTTVpTe4o/AAAAAAAAAAI/AAAAAAAAAGw/KabdyVeaX9M/s512-c/photo.jpg" /></author><thr:total>2</thr:total></entry><entry gd:etag="W/&quot;D0IARXk9cSp7ImA9WhZUFEs.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-2715615392346839461</id><published>2011-05-08T23:22:00.003+01:00</published><updated>2011-06-07T17:52:24.769+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-06-07T17:52:24.769+01:00</app:edited><title>"Sherman, set the WAYBAC machine for ..."</title><content type="html">Archiving older data is a complex task; local, national and sometimes international regulations dictate when, how and for how long the archived data must remain available.  Add to that the seemingly insurmountable task of storing all of this data electronically and what appears, from those outside the IT arena, to be a simple act can end up as anything but simple.  Within the context of an Oracle database there are methods of archiving data, some simple, some a bit more complex but still within the realm of possibility.  Let's look at those options and what they can, and cannot, offer.&lt;br /&gt;
&lt;br /&gt;
The first option which comes to mind (mine, anyway) involves partitioning, an Enterprise Edition option (which should not be a surprise since companies who generate reams of data to archive usually install this edition).  Archiving in this scenario is fairly easy: convert the relevant partitions to stand-alone tables in their own tablespace, separate from the 'live' production data.  If this data is now on its own storage it can even be moved to another database server to facilitate access and not impact daily production.  Let's look at the steps involved with this option.  First let's create a partitioned table:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt; CREATE TABLE archive_test
( 
        dusty DATE,
        vol VARCHAR2(60),
        info NUMBER
)
PARTITION  BY RANGE ( dusty ) 
(
PARTITION really_old 
   VALUES LESS THAN ( TO_DATE('01-apr-1999','dd-mon-yyyy'))
   TABLESPACE older_than_dirt,
PARTITION quite_old 
   VALUES LESS THAN ( TO_DATE('01-jul-2004','dd-mon-yyyy'))
   TABLESPACE old_as_dirt,
PARTITION sorta_new
   VALUES LESS THAN ( TO_DATE('01-oct-2009','dd-mon-yyyy'))
   TABLESPACE newer,
PARTITION really_new
   VALUES LESS THAN ( TO_DATE('01-jan-2012','dd-mon-yyyy'))
   TABLESPACE newest
);


-- 
-- Create local prefixed index
--

CREATE INDEX i_archives_l ON archive_test ( dusty,vol ) 
LOCAL ( 
PARTITION i_otd_one TABLESPACE i_otd_one,
PARTITION i_oad_two TABLESPACE i_oad_two,
PARTITION i_nwr_three TABLESPACE i_nwr_three,
PARTITION i_nwst_four TABLESPACE i_nwst_four
);

&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
The last partition of our table is set to accept all data through 01/01/2012 so archiving data simply involves converting the desired partition to a stand-alone table, preferably stored on a different diskgroup or array than the current production data.  [Sometimes a new partition is created prior to archiving the old partition (or partitions) to keep data flowing into the partitioned table.  We'll presume we have enough 'room' to avoid creating a new partition at archive time.]  For the sake of illustration let's put the destination tablespace, ARCHIVED_TS,  in a separate ASM diskgroup (doing this allows for the movement of the diskgroup to another physical server for use by a separate Oracle instance).  To archive the partition REALLY_OLD to a stand-alone table named REALLY_OLD_TBL:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;--
-- Create empty table matching partition definition
--
create table really_old_tbl
( 
        dusty date,
        vol varchar2(60),
        info number
) tablespace archived_ts;   -- Tablespace created in separate ASM diskgroup or on separate storage

--
-- Check row count in desired partition
--

select count(*)
from archive_test partition(really_old);

--
-- Move partition data to stand-alone table
--
alter table archive_test
exchange partition really_old with table really_old_tbl with validation;

--
-- Verify all rows written to destination table
--

select count(*)
from really_old_tbl;

--
-- Drop now-empty partition presuming row counts match
--
alter table archive_test drop partition really_old;

&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
The data is now archived to a separate table and will no longer be available in ARCHIVE_TEST; this, however, makes partition QUITE_OLD the first partition resulting in any DUSTY value less than the upper partition limit being stored there, including values which should have been in REALLY_OLD.  This may not be an issue as values that old may no longer be generated but it is an aspect to consider when archiving older data from a partitioned table.&lt;br /&gt;
&lt;br /&gt;
A second method is available for those not using partitioning which involves creating an archive table from the source table by selecting the desired data (this will also work for partitioned tables and may be the option of choice if a single archive table is desired as the above illustrated method creates a new table for each partition to be archived):&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;
--
-- Create table and copy data
--
create table really_old_tbl
tablespace archived_ts
as select *
from archive_test
where dusty &lt;= [some date here];

--
-- Verify all data copied successfully
--
select *
from archive_test
where (dusty,vol,info) not in (select * from really_old_tbl)
and dusty &lt;= [some date here];

--
-- Delete from source table
--
delete
from archive_test
where dusty &lt;= [some date here];

commit;

&lt;/span&gt;&lt;/pre&gt;The data is now archived to a separate table.  Changing the create table statement to an insert statement can allow for 'newer' archived data to be stored in the same archive table; again a similar condition exists as any data within the archived range can still be inserted into the source table as no date limits may exist to restrict inserts.  A trigger can be used to restrict such inserts as shown below:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;
SQL&gt; create or replace trigger ins_chk_trg
  2  before insert on archive_test
  3  for each row
  4  declare
  5          mindt date;
  6  begin
  7          select max(dusty) into mindt from really_old_tbl;
  8          if :new.dusty &lt;= mindt then
  9                  raise_application_error(-20987,'Data ('||:new.dusty||') outside of acceptable date range', true);
 10          end if;
 11  end;
 12  /

Trigger created.

SQL&gt;
SQL&gt; insert into archive_test
  2  values (to_date('&amp;mindt', 'RRRR-MM-DD HH24:MI:SS') - 1, 'Test value x', -1,to_date('&amp;mindt', 'RRRR-MM-DD HH24:MI:SS') );
old   2: values (to_date('&amp;mindt', 'RRRR-MM-DD HH24:MI:SS') - 1, 'Test value x', -1,to_date('&amp;mindt', 'RRRR-MM-DD HH24:MI:SS') )
new   2: values (to_date('1999-03-08 14:17:40', 'RRRR-MM-DD HH24:MI:SS') - 1, 'Test value x', -1,to_date('1999-03-08 14:17:40', 'RRRR-MM-DD HH24:MI:SS') )
insert into archive_test
            *
ERROR at line 1:
ORA-20987: Data (07-MAR-99) outside of acceptable date range
ORA-06512: at "BLORPO.INS_CHK_TRG", line 6
ORA-04088: error during execution of trigger 'BLORPO.INS_CHK_TRG'


SQL&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
Such a trigger can be used on partitioned and non-partitioned tables to police the inserts and reject those bearing dates present in the archive table.  As the archive table data increases (due to subsequent inserts) the trigger will recognize the new maximum and use it to reject inserts.&lt;br /&gt;
&lt;br /&gt;
Lest we forget the external utilities both exp/imp and expdp/impdp can be used to archive data; the QUERY option to both exp and expdp allows extraction of specific data from a given table so that only the oldest data will be exported.  Oracle recommends using a parameter file when using the QUERY option to avoid operating system specific escape characters.  Additionally expdp allows for one query per table and multiple table:query pairs when specified with the schema.table:query format.  A sample parameter file is shown below:&lt;br /&gt;
&lt;br /&gt;
&lt;span style="color:#009900;"&gt; &lt;br /&gt;
TABLES=employees, departments&lt;br /&gt;
QUERY=employees:'"WHERE department_id &gt; 10 AND salary &gt; 10000"'&lt;br /&gt;
QUERY=departments:'"WHERE department_id &gt; 10"'&lt;br /&gt;
NOLOGFILE=y &lt;br /&gt;
DIRECTORY=dpump_dir1 &lt;br /&gt;
DUMPFILE=exp1.dmp &lt;br /&gt;
&lt;br /&gt;
&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
This creates tables with the source names and a limited subset of the source data which can be imported into a different schema or different database.  The imported tables can be renamed with the usual command (in releases 10g and later):&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;
rename employees to employees_arch;
rename departments to departments_arch;
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
or in 11gR2 by using the REMAP_TABLE parameter to impdp:&lt;br /&gt;
&lt;br /&gt;
&lt;span style="color:#009900;"&gt; &lt;br /&gt;
...&lt;br /&gt;
REMAP_TABLE=employees:employees_arch&lt;br /&gt;
REMAP_TABLE=departments:departments_arch&lt;br /&gt;
...&lt;br /&gt;
&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
[REMAP_TABLE will fail if the source table has named constraints in the same schema as those constraints will need to be created when the destination table is created.  Constraints named SYS% will be created without error and the table or tables will be remapped.]&lt;br /&gt;
&lt;br /&gt;
The final step in this process is to delete the now-archived data from the source table, as illustrated in the previous example for non-partitioned tables.&lt;br /&gt;
&lt;br /&gt;
If you're using a release older than 10g the process is a bit more time consuming, involving creating a new table with the desired name from the imported table then copying any index/constraint definitions to the new table, finally dropping the imported table once you're certain the 'renamed' table has all necessary indexes and constraints in place.&lt;br /&gt;
&lt;br /&gt;
Archiving older data is not a terribly difficult task (at least in an Oracle database) but it does take planning and attention to detail to ensure all of the desired data is properly archived and available for the end users.  Maintaining the archived table (or tables) also takes planning as applications may need to be written to directly access the archive and, in the case of multiple archive tables, be 'smart' enough to be able to access the newer additions as they arrive.  Remember, too, that the specifications for the archiving revolve around local, state, federal (in the U.S.) and possibly international regulations and the archiving scheme must be flexible enough to provide the required 'window' of access.  It's also true that archived data may outlast the regulations which established it (unless legal issues preclude maintaining the archive beyond the prescribed date range); in such cases a sound storage strategy is a must and it's not unusual for archived data to go from Tier II (slower, cheaper disk) storage to Tier I (tape) as long as the data is still accessible as access speed is not a requirement for archived data.&lt;br /&gt;
&lt;br /&gt;
"Sherman, set the WAYBAC machine for ..."&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=Hz5tvmnFrIA:CdWBP5isfog:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=Hz5tvmnFrIA:CdWBP5isfog:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://oratips-ddf.blogspot.com/feeds/2715615392346839461/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=2715615392346839461" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/2715615392346839461?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/2715615392346839461?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2011/05/sherman-set-waybac-machine-for.html" title="&quot;Sherman, set the WAYBAC machine for ...&quot;" /><author><name>David Fitzjarrell</name><uri>https://plus.google.com/102215427046411604711</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-y2hTTVpTe4o/AAAAAAAAAAI/AAAAAAAAAGw/KabdyVeaX9M/s512-c/photo.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DkAERX46cCp7ImA9Wx9UEU0.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-9152247335654387714</id><published>2010-12-11T21:45:00.010Z</published><updated>2011-02-07T18:51:44.018Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-02-07T18:51:44.018Z</app:edited><title>If Memory Serves ...</title><content type="html">It is possible to run afoul of an ORA-04031 error for no discernable reason (at least at the time the error presents itself).  Being that ORA-04031 errors can have a multitude of causes and, as a result, a multitude of solutions it's difficult to address the entire situation in one post.  One particular situation, which presented two possible initial interpretations, will be discussed including the investigation and eventual solution.  So, let's dig in and see what brought forth such an investigation and where it led.&lt;br /&gt;
&lt;br /&gt;
ORA-04031 errors are not usually written to the alert log; the exception to this is if a background database process is affected.  From 10gR1 onwards trace files are written to the user_dump_dest; these files provide information for Oracle support in diagnosing these errors.  And those same trace files can be a help to you in discovering why your database is suddenly throwing ORA-04031 errors.&lt;br /&gt;
&lt;br /&gt;
Since 10gR1 Oracle has subdivided the shared pool into subheaps (actually since 9i but the algorithm has been expanded and improved from 10gR1 onwards), the number of which is determined by the SGA size and the number of processors available.  An ‘extra’ subheap, subheap 0, may also be available should there be free shared pool space unallocated to any of the other subheaps (found in the shared pool reserve, usually configured to be 10% of the shared pool allocation); this subheap contains memory available for allocation to any of the active subheaps in the shared pool.  Since subheap 0 does not appear in any of the SGA stat reports no unallocated memory exists; this is not to say these subheaps do not have free memory in them, just that no additional memory is available for subheap expansion.  &lt;br /&gt;
&lt;br /&gt;
The trace files report. in this instance, that subheap 7 is the subheap generating the ORA-04031 errors:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;
HEAP DUMP heap name="sga heap(7,0)"  desc=380079e88
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
The subheap affected is the first number in the parenthesised list with the second number indicating the sub-subheap (and, since that is 0 the subheaps are not further subdivided). Using Tanel Poder's &lt;a href="http://blog.tanelpoder.com/2009/06/04/ora-04031-errors-and-monitoring-shared-pool-subpool-memory-utilization-with-sgastatxsql"&gt;sgastatx.sql&lt;/a&gt; script the overall subheap allocations can be displayed; notice that subheap 7 has a smaller allocation than the rest:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; @sgastatx %

-- All allocations:

SUBPOOL                             BYTES         MB                            
------------------------------ ---------- ----------                            
shared pool (1):                318767456        304                            
shared pool (2):                352326152        336                            
shared pool (3):                318767528        304                            
shared pool (4):                352321896        336                            
shared pool (5):                318767672        304                            
shared pool (6):                503317024        480                            
shared pool (7):                302046408     &lt;span style="color:#990000;"&gt;288.05&lt;/span&gt;                            
shared pool (Total):           2466314136    2352.06                            

8 rows selected.

-- Allocations matching "%":
old  15:     AND LOWER(ksmssnam) LIKE LOWER('%&amp;1%')
new  15:     AND LOWER(ksmssnam) LIKE LOWER('%%%')

SUBPOOL                        NAME                       SUM(BYTES)         MB 
------------------------------ -------------------------- ---------- ---------- 
shared pool (1):               free memory                  57472568      54.81 
                               db_block_hash_buckets        53327376      50.86 
                               library cache                43470152      41.46 
...
shared pool (2):               free memory                  77810608      74.21 
                               db_block_hash_buckets        53327576      50.86 
                               library cache                43899072      41.87 
                               sql area                     22086320      21.06 
...
shared pool (4):               free memory                  92167560       87.9 
                               db_block_hash_buckets        53327576      50.86 
                               library cache                42164432      40.21 
                               Checkpoint queue             21498432       20.5 
...
shared pool (5):               free memory                  64019296      61.05 
                               db_block_hash_buckets        53327888      50.86 
                               library cache                37621344      35.88 
...
shared pool (6):               free memory                 230536936     219.86 
                               db_block_hash_buckets        57521680      54.86 
                               library cache                39577624      37.74 
                               sql area                     22639112      21.59 
...
shared pool (7):               db_block_hash_buckets        53327376      50.86 
                               library cache                40609216      38.73 
                               free memory                  39128512      37.32 
                               sql area                     24575688      23.44 
                               Checkpoint queue             21760608      20.75 
                               FileOpenBlock                18539544      17.68 
                               ASM extent pointer array     15474656      14.76 
                               ASH buffers                  10485760         10 
                               kglsim heap                   5306112       5.06 
                               CCursor                       4617920        4.4 
                               trace buffer                  4210688       4.02 
                               KCB Table Scan Buffer         4198400          4 
                               PCursor                       4048272       3.86 
                               event statistics per sess     3804800       3.63 
                               XDB Schema Cac                3671336        3.5 
                               Sort Segment                  3594240       3.43 
                               private strands               3434496       3.28 
                               Heap0: KGL                    3413608       3.26 
                               parameter table block         2479640       2.36 
                               simulator hash buckets        2404928       2.29 
                               transaction                   2336176       2.23 
                               PX subheap                    2291080       2.18 
                               sessions                      2264240       2.16 
                               dbwriter coalesce buffer      2105344       2.01 
                               object queue                  1864800       1.78 
                               KTI-UNDO                      1831024       1.75 
                               enqueue                       1336592       1.27 
                               state objects                 1313720       1.25 
                               KGLS heap                     1273704       1.21 
                               kglsim object batch           1078056       1.03 
                               row cache                     1073184       1.02 
                               FileIdentificatonBlock        1037624        .99 
                               partitioning d                 949920        .91 
                               krbmror                        946400         .9 
                               procs: ksunfy                  752928        .72 
                               MTTR advisory                  673920        .64 
                               object queue hash buckets      673056        .64 
                               buffer handles                 609104        .58 
                               db_files                       599104        .57 
                               kglsim hash table bkts         598016        .57 
                               call                           553608        .53 
                               obj stat memo                  426816        .41 
                               type object de                 371224        .35 
                               DML lock                       351232        .33 
                               ksfqpar                        332072        .32 
... 

1602 rows selected.

SQL&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
The free memory in subheap 7 is reported as 37.32 MB where the free memory in the remaining 6 subheaps ranges from around 55MB to almost 220MB:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;
shared pool (1):               free memory                  57472568      54.81
shared pool (2):               free memory                  77810608      74.21
shared pool (3):               free memory                  59792512      57.02
shared pool (4):               free memory                  92167560       87.9
shared pool (5):               free memory                  64019296      61.05
shared pool (6):               free memory                 230536936     219.86
shared pool (7):               free memory                  39128512      &lt;span style="color:#990000;"&gt;37.32&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
Once memory is allocated to a subheap it cannot be reallocated to another subheap which may need it, thus subheap 7 cannot benefit from the ‘excess’ space in subheap 6 and throws the ORA-04031 error when it tries to expand &lt;b&gt;&lt;i&gt;after&lt;/i&gt;&lt;/b&gt; consuming the 37+ MB of free space it has available.  If there is over 37 MB of free space left why is the allocation failing?  Let's look at V$SHARED_POOL_RESERVED and report on the total number of ORA-04031 errors generated since startup and the size of the last failing expansion attempt:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;
SQL&gt; select request_failures "ORA-04031 Count",
   2        last_failure_size "Size",
   3        to_Char(sysdate, 'DD-MON-RRRR HH24:MI:SS') curr_dt
   4 from v$shared_pool_reserved
   5 /

ORA-04031 Count       Size CURR_DT
--------------- ---------- --------------------
          10871       4192 07-JUN-2010 14:17:34

&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;span style="color:#3333ff;"&gt;[The data in these columns is available whether or not shared_pool_reserved_size is set, so should you have this parameter set to 0 you need not worry as the above query will still return the desired results.]&lt;/span&gt;  Notice the last failing allocation was for just over 4 KB of memory; since ASMM is in use memory is allocated in granules of 4 MB or 16 MB in size (depending upon the size of the SGA) and in this case the granule size is 16 MB.  And since the failing allocation is just over 4 KB it points to a lack of available resource rather than fragmentation of the shared pool.  &lt;br /&gt;
&lt;br /&gt;
If ASMM is in use (sga_target and sga_max_size are set) then increasing the value for sga_target may correct the situation by allocating more memory to the shared pool and the shared pool reserve.  If manual shared memory management is in place then increasing the shared_pool_size and bouncing the database would possibly solve the problem.&lt;br /&gt;
&lt;br /&gt;
Yet another option exists, which can be used in conjunction with increasing the shared pool size: setting an undocumented parameter, _kghdsidx_count, to a lower value to reduce the number of shared pool subheaps.  Since this is not a dynamic parameter it will need to be set in the init.ora file or with the &lt;br /&gt;
&lt;br /&gt;
alter system set "_kghdsidx_count"=[some number] scope=spfile;&lt;br /&gt;
&lt;br /&gt;
statement and then 'bounce' the database.  Of course one should not fiddle with undocumented parameters without Oracle Support's blessings so if this situation ever affects you an SR should be created to ensure you're performing correct and supported actions to resolve the issue.  As a side note Oracle support would suggest such an adjustment in 9i databases when similar situations arose.&lt;br /&gt;
&lt;br /&gt;
Oracle also states that Bugs 4467058, 5552515, and 6981690 can also throw ORA-04031 errors; these bugs still plague 10.2.0.4 (if you're still on this release) as they are fixed in 11.1 and/or 11.2.  Bug 7340448 affects 10.2.0.4 as well, increasing the shared pool memory growth from ‘create table … as select …’ statements and use of the REGEXP_LIKE functionality.   These bugs are described in My Oracle Support document 396940.1.&lt;br /&gt;
&lt;br /&gt;
ORA-04031 errors can be frustrating to investigate as what seems like the correct route to a solution sometimes ends up at a dead end.  There are plenty of resources for this error, though (check google.com and see how many results come back), thus plenty of help is available for those who take the time to carefully examine all of the evidence presented to them via trace files and data dictionary views.&lt;br /&gt;
&lt;br /&gt;
The answer is out there.&lt;br /&gt;
&lt;br /&gt;
Maybe.&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=xfHZ7pixcmk:v_BhYP6yU0c:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=xfHZ7pixcmk:v_BhYP6yU0c:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://oratips-ddf.blogspot.com/feeds/9152247335654387714/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=9152247335654387714" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/9152247335654387714?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/9152247335654387714?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2010/12/if-memory-serves.html" title="If Memory Serves ..." /><author><name>David Fitzjarrell</name><uri>https://plus.google.com/102215427046411604711</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-y2hTTVpTe4o/AAAAAAAAAAI/AAAAAAAAAGw/KabdyVeaX9M/s512-c/photo.jpg" /></author><thr:total>2</thr:total></entry><entry gd:etag="W/&quot;A0ECQnc9fSp7ImA9Wx9VGE8.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-2372915015565874699</id><published>2010-09-23T08:11:00.004+01:00</published><updated>2011-02-04T14:27:43.965Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-02-04T14:27:43.965Z</app:edited><title>"Confidential" Sources</title><content type="html">As I've mentioned before some of the more interesting questions come out of the google.com search engine, like this one:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;
oracle user_source returns nothing
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
Okay, that's really, really brief but there is a valid question therein -- "What does 'no rows selected' mean when querying USER_SOURCE?'  To answer that one we need to discuss the _SOURCE views and what they contain.&lt;br /&gt;
&lt;br /&gt;
There are three views in an Oracle database that list the source text of objects such as packages, procedures, types, triggers and functions: DBA_SOURCE, ALL_SOURCE and USER_SOURCE.  These are listed in order of decreasing scope where DBA_SOURCE contains source for every procedure, etc. from every user and is accessible only to DBA-privileged accounts.  ALL_SOURCE contains source for all objects the currently connected user can access, whether or not that user owns any of the objects.  USER_SOURCE, the most restrictive of the three, lists source for only those objects the connected user owns.  Describing ALL_SOURCE reveals the same view structure as DBA_SOURCE; the definition of the view makes the difference.  USER_SOURCE looks almost like ALL_SOURCE; the one difference is that the OWNER column is missing.  The listing for ALL_SOURCE is shown below:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; desc all_source
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 OWNER                                              VARCHAR2(30)
 NAME                                               VARCHAR2(30)
 TYPE                                               VARCHAR2(12)
 LINE                                               NUMBER
 TEXT                                               VARCHAR2(4000)
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
Let's now look at the output from USER_SOURCE for the connected user:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;NAME            TYPE               LINE TEXT
--------------- ------------ ---------- --------------------------------------------------------------------------------
COND_INSRT      PROCEDURE             1 procedure cond_insrt(p_empno IN varchar2)
COND_INSRT      PROCEDURE             2 is
COND_INSRT      PROCEDURE             3         v_empname emp.empname%type;
COND_INSRT      PROCEDURE             4 begin
COND_INSRT      PROCEDURE             5         select empname into v_empname from table_1 where empno = p_empno;
COND_INSRT      PROCEDURE             6
COND_INSRT      PROCEDURE             7         if v_empname is not null then
COND_INSRT      PROCEDURE             8                 insert into table_2 (empname, empno)
COND_INSRT      PROCEDURE             9                 values (v_empname, p_empno);
COND_INSRT      PROCEDURE            10         end if;
COND_INSRT      PROCEDURE            11 exception

NAME            TYPE               LINE TEXT
--------------- ------------ ---------- --------------------------------------------------------------------------------
COND_INSRT      PROCEDURE            12         when no_data_found then
COND_INSRT      PROCEDURE            13                 insert into table_1 (empname, empno)
COND_INSRT      PROCEDURE            14                 values ('AAAA', p_empno);
COND_INSRT      PROCEDURE            15         when others then
COND_INSRT      PROCEDURE            16             dbms_output.put_line('Displaying the error stack:');
COND_INSRT      PROCEDURE            17             dbms_output.put(dbms_utility.format_error_stack);
COND_INSRT      PROCEDURE            18             dbms_output.put_line(dbms_utility.format_error_backtrace);
COND_INSRT      PROCEDURE            19 end;
SPELL_NUMBER    FUNCTION              1 function spell_number( p_number in number )
SPELL_NUMBER    FUNCTION              2 return varchar2
SPELL_NUMBER    FUNCTION              3 as

NAME            TYPE               LINE TEXT
--------------- ------------ ---------- --------------------------------------------------------------------------------
SPELL_NUMBER    FUNCTION              4     type myArray is table of varchar2(255);
SPELL_NUMBER    FUNCTION              5     l_str    myArray := myArray( '',
SPELL_NUMBER    FUNCTION              6                            ' thousand ', ' million ',
SPELL_NUMBER    FUNCTION              7                            ' billion ', ' trillion ',
SPELL_NUMBER    FUNCTION              8                            ' quadrillion ', ' quintillion ',
SPELL_NUMBER    FUNCTION              9                            ' sextillion ', ' septillion ',
SPELL_NUMBER    FUNCTION             10                            ' octillion ', ' nonillion ',
SPELL_NUMBER    FUNCTION             11                            ' decillion ', ' undecillion ',
SPELL_NUMBER    FUNCTION             12                            ' duodecillion ' );
SPELL_NUMBER    FUNCTION             13
SPELL_NUMBER    FUNCTION             14     l_num   varchar2(50) default trunc( p_number );

NAME            TYPE               LINE TEXT
--------------- ------------ ---------- --------------------------------------------------------------------------------
SPELL_NUMBER    FUNCTION             15     l_return varchar2(4000);
SPELL_NUMBER    FUNCTION             16 begin
SPELL_NUMBER    FUNCTION             17     for i in 1 .. l_str.count
SPELL_NUMBER    FUNCTION             18     loop
SPELL_NUMBER    FUNCTION             19         exit when l_num is null;
SPELL_NUMBER    FUNCTION             20
SPELL_NUMBER    FUNCTION             21         if ( substr(l_num, length(l_num)-2, 3) &lt;&gt; 0 )
SPELL_NUMBER    FUNCTION             22         then
SPELL_NUMBER    FUNCTION             23            l_return := to_char(
SPELL_NUMBER    FUNCTION             24                            to_date(
SPELL_NUMBER    FUNCTION             25                             substr(l_num, length(l_num)-2, 3),

NAME            TYPE               LINE TEXT
--------------- ------------ ---------- --------------------------------------------------------------------------------
SPELL_NUMBER    FUNCTION             26                               'J' ),
SPELL_NUMBER    FUNCTION             27                        'Jsp' ) || l_str(i) || l_return;
SPELL_NUMBER    FUNCTION             28         end if;
SPELL_NUMBER    FUNCTION             29         l_num := substr( l_num, 1, length(l_num)-3 );
SPELL_NUMBER    FUNCTION             30     end loop;
SPELL_NUMBER    FUNCTION             31
SPELL_NUMBER    FUNCTION             32     return l_return;
SPELL_NUMBER    FUNCTION             33 end;

52 rows selected.
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
This user owns two functions, COND_INSRT and SPELL_NUMBER; the name and type are repeated for each line of source code each distinct object has.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="color:#3333ff;"&gt;[As a side note I found this from google.com:&lt;br /&gt;
&lt;br /&gt;
"oracle user can't execute own function"&lt;br /&gt;
&lt;br /&gt;
which can never be true for valid functions; if the user can successfully create a function without errors then he or she can execute it.]&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
It's quite likely an application owner will have hundreds of distinct objects listed; it's also quite likely that a user may own nothing, not even the tables he or she uses.  In such cases a query against USER_SOURCE will return the glorious message indicating the view is empty: &lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; select * from user_source;

no rows selected

SQL&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
"But, I can execute procedures, functions and packages; don't I own them?"  In a word, no, which doesn't mean you can't execute other users procedures, packages and functions.  Query ALL_SOURCE in place of USER_SOURCE and you'll see whose code you can execute; note that you have been granted execute on such things by the owner else you'd not likely be able to access them much less execute them  (DBA-privileged accounts are different as they have execute any procedure privilege, among others).  And there are most likely synonyms created to 'hide' the ownership of those procedures, packages and functions so you can call them by name and not raise an error (query USER_SYNONYMS or ALL_SYNONYMS to verify that).&lt;br /&gt;
&lt;br /&gt;
It's not a problem to not own anything executable in a database, meaning it's okay to get&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;
no rows selected
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
when querying USER_SOURCE.  But it's also nice to know who owns those packages, procedures, triggers, functions, etc. you use on a daily basis.  Since you now know how to find that information you can probably rest easier at night.  I know that I do.&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=nxFMQ5BJ1q0:x94zZ2CM9uU:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=nxFMQ5BJ1q0:x94zZ2CM9uU:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://oratips-ddf.blogspot.com/feeds/2372915015565874699/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=2372915015565874699" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/2372915015565874699?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/2372915015565874699?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2010/09/confidential-sources.html" title="&quot;Confidential&quot; Sources" /><author><name>David Fitzjarrell</name><uri>https://plus.google.com/102215427046411604711</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-y2hTTVpTe4o/AAAAAAAAAAI/AAAAAAAAAGw/KabdyVeaX9M/s512-c/photo.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;CE4MSX87fip7ImA9Wx5bGEw.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-6406506822378559399</id><published>2010-08-23T16:11:00.010+01:00</published><updated>2010-11-03T19:36:28.106Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-11-03T19:36:28.106Z</app:edited><title>Corruption And Mayhem</title><content type="html">It may happen that at some point you could encounter block corruption in empty blocks that prevents you from performing either a 'delete from &amp;lt;some table name&amp;gt;;' or a 'truncate table &amp;lt;some table name&amp;gt;;' because you are met with the following error:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;
ERROR at line 1: 
ORA-01578: ORACLE data block corrupted (file # 5, block # 4290) 
&lt;/span&gt;&lt;/pre&gt;These are empty blocks; why on earth are they causing you grief?  The explanation for that is these blocks fall below the highwater mark because they once contained data and the blanket delete and truncate statements process every block up to the highwater mark regardless of whether they are empty or not.&lt;br /&gt;
&lt;br /&gt;
So you cannot truncate the table or delete everything from it; what do you do to fix this?  The first possibility which comes to mind is to use the DBMS_REPAIR package:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;DBMS_REPAIR Procedures

Procedure Name      Description 

ADMIN_TABLES        Provides administrative functions (create, drop, purge) for
                    repair or orphan key tables. 
                    &lt;span style="color:#009900;"&gt;Note: These tables are always created in the SYS schema&lt;/span&gt;

CHECK_OBJECT        Detects and reports corruptions in a table or index 

DUMP_ORPHAN_KEYS    Reports on index entries that point to rows in corrupt data
                    blocks 

FIX_CORRUPT_BLOCKS  Marks blocks as software corrupt that have been previously
                    identified as corrupt by the CHECK_OBJECT procedure 

REBUILD_FREELISTS   Rebuilds the free lists of the object 

SEGMENT_FIX_STATUS  Provides the capability to fix the corrupted state of a 
                    bitmap entry when segment space management is AUTO
 
SKIP_CORRUPT_BLOCKS When used, ignores blocks marked corrupt during table and 
                    index scans. If not used, you get error ORA-1578 when
                    encountering blocks marked corrupt. 

&lt;/span&gt;&lt;/pre&gt;The procedures of interest are ADMIN_TABLES, CHECK_OBJECT, FIX_CORRUPT_BLOCKS, DUMP_ORPHAN_KEYS and possibly SKIP_CORRUPT_BLOCKS, in that order.  Presuming you have never before used DBMS_REPAIR it will be necessary to execute the ADMIN_TABLES procedure to create the objects and tables necessary for the package to do its job:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;BEGIN
DBMS_REPAIR.ADMIN_TABLES (
     TABLE_NAME =&gt; 'REPAIR_TABLE',
     TABLE_TYPE =&gt; dbms_repair.repair_table,
     ACTION     =&gt; dbms_repair.create_action,
     TABLESPACE =&gt; 'TOOLS');
END;
/
&lt;/span&gt;&lt;/pre&gt;This creates a table named REPAIR_TABLE in the TOOLS tablespace (not providing a tablespace name the procedure creates these tables in the SYSTEM tablespace). In addition the procedure creates an associated view (named DBA_&lt;repair table name&gt;) that filters any rows referencing objects which no longer exist.  Let's look at the table definition for our REPAIR_TABLE:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;DESC REPAIR_TABLE

 Name                         Null?    Type
 ---------------------------- -------- --------------
 OBJECT_ID                    NOT NULL NUMBER
 TABLESPACE_ID                NOT NULL NUMBER
 RELATIVE_FILE_ID             NOT NULL NUMBER
 BLOCK_ID                     NOT NULL NUMBER
 CORRUPT_TYPE                 NOT NULL NUMBER
 SCHEMA_NAME                  NOT NULL VARCHAR2(30)
 OBJECT_NAME                  NOT NULL VARCHAR2(30)
 BASEOBJECT_NAME                       VARCHAR2(30)
 PARTITION_NAME                        VARCHAR2(30)
 CORRUPT_DESCRIPTION                   VARCHAR2(2000)
 REPAIR_DESCRIPTION                    VARCHAR2(200)
 MARKED_CORRUPT               NOT NULL VARCHAR2(10)
 CHECK_TIMESTAMP              NOT NULL DATE
 FIX_TIMESTAMP                         DATE
 REFORMAT_TIMESTAMP                    DATE
&lt;/span&gt;&lt;/pre&gt;These columns will be populated by the CHECK_OBJECT procedure:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SET SERVEROUTPUT ON
DECLARE num_corrupt INT;
BEGIN
num_corrupt := 0;
DBMS_REPAIR.CHECK_OBJECT (
     SCHEMA_NAME =&gt; 'ORDER_APP',
     OBJECT_NAME =&gt; 'BACKORDERS',
     REPAIR_TABLE_NAME =&gt; 'REPAIR_TABLE',
     CORRUPT_COUNT =&gt;  num_corrupt);
DBMS_OUTPUT.PUT_LINE('number corrupt: ' || TO_CHAR (num_corrupt));
END;
/
&lt;/span&gt;&lt;/pre&gt;The output from that PL/SQL block is one line reporting the total number of corrupt blocks in that table:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;number corrupt: 1&lt;/span&gt;&lt;/pre&gt;Querying REPAIR_TABLE will provide information on the type of corruption and a suggested repair action:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SELECT OBJECT_NAME, BLOCK_ID, CORRUPT_TYPE, MARKED_CORRUPT,
       CORRUPT_DESCRIPTION, REPAIR_DESCRIPTION
     FROM REPAIR_TABLE;

OBJECT_NAME                      BLOCK_ID CORRUPT_TYPE MARKED_COR
------------------------------ ---------- ------------ ----------
CORRUPT_DESCRIPTION
------------------------------------------------------------------------------
REPAIR_DESCRIPTION
------------------------------------------------------------------------------
DEPT                                 4290            1 FALSE
kdbchk: row locked by non-existent transaction
        table=0   slot=0
        lockid=32   ktbbhitc=1
mark block software corrupt

&lt;/span&gt;&lt;/pre&gt;Since the reported block has not yet been marked as corrupt now is the time to extract any data to minimize loss using 'alter system dump datafile &lt;file_id&gt; block &lt;block_id&gt;;'.  For this example the file_id is 5 and the block_id is 4290:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;alter session set tracefile_identifier = 'blockdump';

Session altered.

alter system dump datafile 5 block 4290;

System altered.

&lt;/span&gt;&lt;/pre&gt;The data will be found in a tracefile in the defined user_dump_dest and will have the tracefile_identifier text in the file name (this makes the file easier to find).  This should allow you to mark the block corrupt and re-insert the data.  Depending upon what is corrupting the block the block dump may not be successful thus making data replacement difficult with this procedure; there may be an available export from before the block corruption occurred which will allow the data to be imported into a different schema so that any missing data can be replaced.  As this would be a basic 'insert into .. select ... from ...' operation I will not describe it here.&lt;br /&gt;
&lt;br /&gt;
Repairing the block is also a fairly simple task:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SET SERVEROUTPUT ON
DECLARE num_fix INT;
BEGIN 
num_fix := 0;
DBMS_REPAIR.FIX_CORRUPT_BLOCKS (
     SCHEMA_NAME =&gt; 'ORDER_APP',
     OBJECT_NAME=&gt; 'BACKORDERS',
     OBJECT_TYPE =&gt; dbms_repair.table_object,
     REPAIR_TABLE_NAME =&gt; 'REPAIR_TABLE',
     FIX_COUNT=&gt; num_fix);
DBMS_OUTPUT.PUT_LINE('num fix: ' || TO_CHAR(num_fix));
END;
/
&lt;/span&gt;&lt;/pre&gt;This PL/SQL block also reports one line of output:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;num fix: 1
&lt;/span&gt;&lt;/pre&gt;indicating that all of the blocks marked corrupt have been marked as such and are no longer available.  Querying REPAIR_TABLE again proves that:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SELECT OBJECT_NAME, BLOCK_ID, MARKED_CORRUPT
     FROM REPAIR_TABLE;

OBJECT_NAME                      BLOCK_ID MARKED_COR
------------------------------ ---------- ----------
BACKORDERS                           4290 TRUE
&lt;/span&gt;&lt;/pre&gt;What if DBMS_REPAIR.FIX_CORRUPT_BLOCKS returns a number less than the total number of corrupt blocks reported?  It's likely that you have another underlying problem causing the corruption which needs to be addressed, such has a hardware or firmware issue.  In such cases DBMS_REPAIR.FIX_CORRUPT_BLOCKS will fail to repair those blocks.&lt;br /&gt;
&lt;br /&gt;
Now it's time to find any orphan keys in the index BACKORDER_IDX; these are entries pointing to rows in the corrupt data block.  First is to create the ORPHAN_KEY_TABLE:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;BEGIN
DBMS_REPAIR.ADMIN_TABLES (
     TABLE_NAME =&gt; 'ORPHAN_KEY_TABLE',
     TABLE_TYPE =&gt; dbms_repair.orphan_table,
     ACTION     =&gt; dbms_repair.create_action,
     TABLESPACE =&gt; 'USERS');
END;
/
&lt;/span&gt;&lt;/pre&gt;The orphan key table has the following columns:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;DESC ORPHAN_KEY_TABLE

 Name                         Null?    Type
 ---------------------------- -------- -----------------
 SCHEMA_NAME                  NOT NULL VARCHAR2(30)
 INDEX_NAME                   NOT NULL VARCHAR2(30)
 IPART_NAME                            VARCHAR2(30)
 INDEX_ID                     NOT NULL NUMBER
 TABLE_NAME                   NOT NULL VARCHAR2(30)
 PART_NAME                             VARCHAR2(30)
 TABLE_ID                     NOT NULL NUMBER
 KEYROWID                     NOT NULL ROWID
 KEY                          NOT NULL ROWID
 DUMP_TIMESTAMP               NOT NULL DATE
&lt;/span&gt;&lt;/pre&gt;With this table in place we can now discover any orphan keys:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SET SERVEROUTPUT ON
DECLARE num_orphans INT;
BEGIN
num_orphans := 0;
DBMS_REPAIR.DUMP_ORPHAN_KEYS (
     SCHEMA_NAME =&gt; 'ORDER_APP',
     OBJECT_NAME =&gt; 'BACKORDER_IDX',
     OBJECT_TYPE =&gt; dbms_repair.index_object,
     REPAIR_TABLE_NAME =&gt; 'REPAIR_TABLE',
     ORPHAN_TABLE_NAME=&gt; 'ORPHAN_KEY_TABLE',
     KEY_COUNT =&gt; num_orphans);
DBMS_OUTPUT.PUT_LINE('orphan key count: ' || TO_CHAR(num_orphans));
END;
/
&lt;/span&gt;&lt;/pre&gt;The output reports the number of orphan keys in the index:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;orphan key count: 3
&lt;/span&gt;&lt;/pre&gt;Rebuilding the index is the necessary action to match the index entries to the table data.  Of course if you can replace the now-missing data that should be done first, and then perform an index rebuild.&lt;br /&gt;
&lt;br /&gt;
Skipping corrupt blocks will prevent any errors from surfacing reporting corrupted blocks and DBMS_REPAIR provides such a procedure:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;BEGIN
DBMS_REPAIR.SKIP_CORRUPT_BLOCKS (
     SCHEMA_NAME =&gt; 'ORDER_APP',
     OBJECT_NAME =&gt; 'BACKORDERS',
     OBJECT_TYPE =&gt; dbms_repair.table_object,
     FLAGS =&gt; dbms_repair.skip_flag);
END;
/
&lt;/span&gt;&lt;/pre&gt;Querying DBA_TABLES for the BACKORDERS table shows that SKIP_CORRUPT is enabled:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SELECT OWNER, TABLE_NAME, SKIP_CORRUPT FROM DBA_TABLES
    WHERE OWNER = 'ORDER_APP'
    AND TABLE_NAME = 'BACKORDERS';

OWNER                          TABLE_NAME                     SKIP_COR
------------------------------ ------------------------------ --------
ORDER_APP                      BACKORDERS                     ENABLED

&lt;/span&gt;&lt;/pre&gt;and now table scans and index scans will skip over blocks marked corrupt and not report the errors shown at the beginning of this post.&lt;br /&gt;
&lt;br /&gt;
The second possibility is it's a hardware/firmware issue and that would need to be addressed by your local Administrator (Windows or UNIX) and the storage vendor, if necessary. You can't fix the problem if the hardware can possibly create more damage; repairing such problems can result in creating a new database or restoring the current database from the most recent backup as the storage media may be reformatted once the controller/firmware issue is corrected (this depends upon how far the media corruption has spread and also on the actions necessary for the hardware/firmware fix). This is where having successfully tested your backup and recovery procedures can save you.  And, if for some reason you haven't a tested backup/restore procedure you DO have, at the very least, a recent export to use as any 'recovery' is better than no recovery at all.&lt;br /&gt;
&lt;br /&gt;
A third possibility is a power problem; incorrectly wired connections can destroy data on a disk as 'unfiltered' power can make its way to the server since the battery backup/power conditioner is being bypassed by the botched wiring scheme.  You'll need an electrician to fix this one and possibly replacement media as well which puts you at the 'create or recover the database' stage mentioned in the previous paragraph. I've personally seen this happen so don't think it's merely hypothetical.  I truly hope you never experience this sort of problem.&lt;br /&gt;
&lt;br /&gt;
In many cases of datafile corruption the DBMS_REPAIR package can mark and bypass the corrupted blocks and restore table and data access.  Yes, you'll most likely need to replace the data in the corrupted blocks but a recent export or a block dump can provide the means to do just that.  Believe me it's nice to know that package exists for you never know when you might need it.&lt;br /&gt;
&lt;br /&gt;
Now, if only all corruption could be fixed this easily...&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=DgjPaK_Vwq0:6p9ztWa4b2w:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=DgjPaK_Vwq0:6p9ztWa4b2w:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://oratips-ddf.blogspot.com/feeds/6406506822378559399/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=6406506822378559399" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/6406506822378559399?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/6406506822378559399?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2010/08/corruption-and-mayhem.html" title="Corruption And Mayhem" /><author><name>David Fitzjarrell</name><uri>https://plus.google.com/102215427046411604711</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-y2hTTVpTe4o/AAAAAAAAAAI/AAAAAAAAAGw/KabdyVeaX9M/s512-c/photo.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DUAFQnYycSp7ImA9Wx5QFUo.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-1543361888944659066</id><published>2010-06-11T16:43:00.015+01:00</published><updated>2010-09-04T06:21:53.899+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-09-04T06:21:53.899+01:00</app:edited><title>Please Stand By</title><content type="html">There are several ways to create a standby database (besides having someone else do it for you), and one that isn't often used is to copy the datafiles from one standby database to create another which uses a manual log transfer/registration mechanism.  RMAN offers this capability (the file copy, not the manual log transfer/registration) and can be put to good use in this endeavor.  Let's see how this can be done.&lt;br /&gt;&lt;br /&gt;

RMAN can copy datafiles from filesystem to filesystem, from filesystem to ASM, from ASM to filesystem and, finally, from ASM to ASM.  The example provided here will use two ASM diskgroups.  MYBIGDG and MYEMPTYDG.  We're running a standby database using MYBIGDG so we know it's already mounted.  We need to mount MYEMPTYDG so we can use it:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;ASM&gt; alter diskgroup myemptydg mount;

Diskgroup altered.

ASM&gt; &lt;/span&gt;&lt;/pre&gt;

[The prompt is set to display ASM to avoid confusion between the ASM instance and any regular database.]  We have the empty destination diskgroup available, and it is very important that this diskgroup IS completely empty if you're using Oracle Managed Files and, if it is not, it needs to be dropped and recreated to ensure that the group does not run out of space.  &lt;span style="color:#3333ff;"&gt;[Oracle Managed File names are generated at the time the file is created, and will not be the same as existing files in the diskgroup for the same database, hence the requirement the diskgroup be completely empty.]&lt;/span&gt;  Now let's take a peek at the RMAN script to copy the datafiles:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;connect target /;
connect rcvcat 'RMAN/###########@rcovcat';

run{
allocate channel backup_ch1 DEVICE TYPE DISK;
allocate channel backup_ch2 DEVICE TYPE DISK;
allocate channel backup_ch3 DEVICE TYPE DISK;
allocate channel backup_ch4 DEVICE TYPE DISK;
allocate channel backup_ch5 DEVICE TYPE DISK;
allocate channel backup_ch6 DEVICE TYPE DISK;
allocate channel backup_ch7 DEVICE TYPE DISK;
allocate channel backup_ch8 DEVICE TYPE DISK;

copy
datafile 1 to '+MYEMPTYDG',
datafile 2 to '+MYEMPTYDG',
datafile 3 to '+MYEMPTYDG',
datafile 4 to '+MYEMPTYDG',
datafile 5 to '+MYEMPTYDG',
datafile 6 to '+MYEMPTYDG',
datafile 7 to '+MYEMPTYDG',
datafile 8 to '+MYEMPTYDG',
datafile 9 to '+MYEMPTYDG',
datafile 10 to '+MYEMPTYDG',
datafile 11 to '+MYEMPTYDG',
datafile 12 to '+MYEMPTYDG',
datafile 13 to '+MYEMPTYDG',
datafile 14 to '+MYEMPTYDG',
datafile 15 to '+MYEMPTYDG',
datafile 16 to '+MYEMPTYDG',
datafile 17 to '+MYEMPTYDG',
datafile 18 to '+MYEMPTYDG',
datafile 19 to '+MYEMPTYDG',
datafile 20 to '+MYEMPTYDG',
datafile 21 to '+MYEMPTYDG',
datafile 22 to '+MYEMPTYDG',
datafile 23 to '+MYEMPTYDG',
datafile 24 to '+MYEMPTYDG',
datafile 25 to '+MYEMPTYDG',
datafile 26 to '+MYEMPTYDG',
datafile 27 to '+MYEMPTYDG',
datafile 28 to '+MYEMPTYDG',
datafile 29 to '+MYEMPTYDG',
datafile 30 to '+MYEMPTYDG';
)

exit;&lt;/span&gt;&lt;/pre&gt;

We're making new copies of all of the datafiles in the source standby database; this doesn't create or copy the controlfile and the names generated if using Oracle Managed Files will differ from the source names, but we won't worry about that now.  Set the environment for the existing standby database then run the script using rman command-line syntax:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;nohup timex rman cmdfile='copy_standby_files.rcv' log='copy_standby_files.log' &amp;amp;&lt;/span&gt;&lt;/pre&gt;

Using nohup keeps the process going unattended and is a nice addition if connection problems plague you; timex (a UNIX utility) reports on the time required to complete the command.  I use it to gauge how long a task will take the next time I run it.  The &amp;amp; puts the command in the background so you can do other tasks while this is running, like fry fish, make raisin bread or fix that nagging temp space issue one of the developers reported a while back.  Depending on the size of the files this could take a while to complete so have patience.&lt;br /&gt;&lt;br /&gt;

Time passes, seasons change, your kids graduate from college and the copy process is complete.  [Okay, okay, it won't take THAT long but it may seem like it.]  It's now time to get this new standby started.  We'll need a standby controlfile and that can be generated from the primary database:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; alter database create standby controlfile as '/sneem/flurbor/my_standby.ctl';

Database altered.

SQL&gt; &lt;/span&gt;&lt;/pre&gt;

Of course if this new standby database is on another server some additional tasks will be necessary, such as dismounting the MYEMPTYDG diskgroup and having the Unix team dismount the storage from the standby server and mount it on the server for the new standby database.  Copy the standby controlfile that was just created to the server where the new standby database will call home.  Edit the init.ora file for the new standby database so it will use this controlfile.&lt;br /&gt;&lt;br /&gt;

Since this configuration is using manual log transfer the time has come to enable that process on the existing standby server; scp can be configured to connect to the new standby server without requiring a password (a nice feature in secure data centers) and thus can be used to transfer the log file via a cron job.  Talk with your friendly neighborhood UNIX administrator on how best to script this transfer.&lt;br /&gt;&lt;br /&gt;

Registering the logs in the new standby is a fairly simple task to script:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;#!/bin/ksh

. $HOME/.profile

cd /home/oracle/arch_log_xfer

cat /dev/null &amp;gt; register_arch_logs.sql

cd /u10/newstdby/arch

set - `ls -1tr *.arc`

cd /home/oracle/arch_log_xfer

while [ $# -gt 1 ]
do
        echo "alter database register logfile '//arch/"$1"';" &amp;gt;&amp;gt; register_sc_arch_logs.sql
        shift
done

sqlplus /nolog &amp;lt;&amp;lt;EOF
connect / as sysdba
@register_arch_logs
EOF

&lt;/span&gt;&lt;/pre&gt;

Notice the last log in the ls -tr listing is not processed; this prevents the script from registering a logfile still locked for writing.  That log will be picked up when the next log is being transferred, so the process is one log behind.  This is not a problem as it keeps Oracle from terminating the managed recovery because the file that's still being written is smaller than the header reports when Oracle tries to apply it.  After the logs are applied you'll need to delete them and that's another easy script to write:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;#!/bin/ksh

. $HOME/.profile

cd /home/oracle/arch_log_xfer

sqlplus /nolog &amp;lt;&amp;lt;EOF
connect / as sysdba
@rm_applied_logs.sql
EOF

&lt;/span&gt;&lt;/pre&gt;

The SQL script looks like this:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;set pagesize 0 linesize 200 trimspool on feedback ofF

select 'if [ -f '||name||' ] ; then rm '||name||' ; fi'
from v$archived_log
where applied = 'YES'

spool del_applied_logs.sh
/
spool off

!chmod 755 del_applied_logs.sh
!./del_applied_logs.sh


&lt;/span&gt;&lt;/pre&gt;

Start these cron jobs after the log transfer has started.&lt;br /&gt;&lt;br /&gt;

When everything is in place it's time to startup and mount the new standby database:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; connect / as sysdba

Connected.

SQL&gt; startup mount pfile="/u01/app/oracle/product/10.2.0/dbs/initNEWSTDBY.ora"

Oracle instance starting.

[The usual messages/memory areas/sizes displayed here]
Database mounted.
SQL&gt; &lt;/span&gt;&lt;/pre&gt;

It's time to use RMAN to 'fix' those datafile names.  This is done in two steps after connecting RMAN to the target &lt;span style="color:#3333ff;"&gt;WITHOUT&lt;/span&gt; using the catalog:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;$ rman target / nocatalog
[...]

RMAN&gt; catalog start with '+MYEMPTYDG/newstdby/datafiles';

[A list of datafiles in the diskgroup are listed and you are prompted to continue, requiring a YES or NO response.  Type YES to catalog these names in the controlfile.]

RMAN&gt;&lt;/span&gt;&lt;/pre&gt;

Once that task is complete the second step is to get RMAN to rename the files in the controlfile for you:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;RMAN&gt; switch database to copy;

[RMAN will display the progress of the switch and should, eventually, report successful completion.]

RMAN&gt;&lt;/span&gt;&lt;/pre&gt;

Presuming all went according to plan (you'll know if it didn't) it should now be possible to start managed recovery on the new standby database and verify the logs are being applied:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; alter database recover managed standby database disconnect from session;

Database altered.

SQL&gt; select process, status, sequence#
  2  from v$managed_standby
  3  /

PROCESS   STATUS        SEQUENCE#
--------- ------------ ----------
ARCH      CONNECTED             0
ARCH      CONNECTED             0
ARCH      CONNECTED             0
ARCH      CONNECTED             0
ARCH      CONNECTED             0
ARCH      CONNECTED             0
ARCH      CONNECTED             0
ARCH      CONNECTED             0
ARCH      CONNECTED             0
ARCH      CONNECTED             0
MRP0      APPLYING_LOG      76338

11 rows selected.

SQL&gt;

&lt;/span&gt;&lt;/pre&gt;


You now have a secondary standby database, getting its logs from the original standby database 'manually'.  And you didn't need to perform an RMAN duplicate and disrupt the production database. You can't ask for more than that.&lt;br /&gt;&lt;br /&gt;

Well, okay, you CAN, but odds are you won't get it.&lt;br /&gt;&lt;br /&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=H3oGuA4c7ZQ:t5_B12hX3Cs:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=H3oGuA4c7ZQ:t5_B12hX3Cs:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://oratips-ddf.blogspot.com/feeds/1543361888944659066/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=1543361888944659066" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/1543361888944659066?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/1543361888944659066?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2010/06/please-stand-by.html" title="Please Stand By" /><author><name>David Fitzjarrell</name><uri>https://plus.google.com/102215427046411604711</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-y2hTTVpTe4o/AAAAAAAAAAI/AAAAAAAAAGw/KabdyVeaX9M/s512-c/photo.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DEcNRHkzeCp7ImA9Wx5WF0o.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-7686442430763410908</id><published>2009-12-10T14:52:00.009Z</published><updated>2010-09-29T17:01:35.780+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-09-29T17:01:35.780+01:00</app:edited><title>In A Pickle</title><content type="html">It's entirely possible that a query execution plan includes the following peculiar entry:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;
COLLECTION ITERATOR (PICKLER FETCH)
&lt;/span&gt;&lt;/pre&gt;Intriguing, at the very least.  What, exactly, IS a pickler fetch?  To answer that we must answer another question:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;
What is pickled data?
&lt;/span&gt;&lt;/pre&gt;so let's start there and see where that leads us.&lt;br /&gt;
&lt;br /&gt;
Packed object data cannot be serialized directly, it requires some manipulation.  This manipulation involves converting an object-oriented data structure into a byte stream so it can be navigated by iterative methods.  Whew.  What that means is that by 'pickling' we can achieve a more 'palatable' data stream presented to the receiving process or interface.  As an example let's return the output from the dbms_xplan.display function without any additional 'magic':  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; select dbms_XPLAN.DISPLAY FROM DUAL;

DISPLAY(PLAN_TABLE_OUTPUT)
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
DBMS_XPLAN_TYPE_TABLE(DBMS_XPLAN_TYPE(' '), DBMS_XPLAN_TYPE('-------------------------------------------------------------------------------'), DBMS_XPLAN_TYPE('| Id  | Operation
|  Name        | Rows  | Bytes | Cost  |'), DBMS_XPLAN_TYPE('-------------------------------------------------------------------------------'), DBMS_XPLAN_TYPE('|   0 | SELECT STATEMENT
|              |  8169 |  1412K|   102 |'), DBMS_XPLAN_TYPE('|   1 |  VIEW                          | DBA_OBJECTS  |  8169 |  1412K|   102 |'), DBMS_XPLAN_TYPE('|   2 |   UNION-ALL
|              |       |       |       |'), DBMS_XPLAN_TYPE('|*  3 |    FILTER                      |              |       |       |       |'), DBMS_XPLAN_TYPE('|   4 |     NESTED LOOPS
|              |     1 |   235 |    18 |'), DBMS_XPLAN_TYPE('|*  5 |      TABLE ACCESS FULL         | OBJ$         |     1 |   205 |    17 |'), DBMS_XPLAN_TYPE('|   6 |      TABLE ACCESS CLUSTER
| USER$        |     1 |    30 |     1 |'), DBMS_XPLAN_TYPE('|*  7 |       INDEX UNIQUE SCAN        | I_USER#      |     1 |       |       |'), DBMS_XPLAN_TYPE('|*  8 |     TABLE ACCESS BY INDEX ROWID
| IND$         |     1 |    26 |     2 |'), DBMS_XPLAN_TYPE('|*  9 |      INDEX UNIQUE SCAN         | I_IND1       |     1 |       |     1 |'), DBMS_XPLAN_TYPE('|  10 |    NESTED LOOPS
|              |  8168 |   941K|    84 |'), DBMS_XPLAN_TYPE('|  11 |     TABLE ACCESS FULL          | LINK$        |    82 |  7216 |     2 |'), DBMS_XPLAN_TYPE('|  12 |     TABLE ACCESS CLUSTER
| USER$        |   100 |  3000 |     1 |'), DBMS_XPLAN_TYPE('|* 13 |      INDEX UNIQUE SCAN         | I_USER#      |     1 |       |       |'), DBMS_XPLAN_TYPE('---------------------------------------
----------------------------------------'), DBMS_XPLAN_TYPE(' '), DBMS_XPLAN_TYPE('Predicate Information (identified by operation id):'), DBMS_XPLAN_TYPE('---------------------------------------------
------'), DBMS_XPLAN_TYPE(' '), DBMS_XPLAN_TYPE('   3 - filter("SYS_ALIAS_1"."TYPE#"&lt;&gt;1 AND "SYS_ALIAS_1"."TYPE#"&lt;&gt;10 OR '), DBMS_XPLAN_TYPE('              "SYS_ALIAS_1"."TYPE#"=1 AND  (SELECT /*+ */
1 FROM "SYS"."IND$" "I" WHERE '), DBMS_XPLAN_TYPE('              "I"."OBJ#"=:B1 AND ("I"."TYPE#"=1 OR "I"."TYPE#"=2 OR "I"."TYPE#"=3 OR '), DBMS_XPLAN_TYPE('              "I"."TYPE#"=4 OR "I"."TYPE#"=
6 OR "I"."TYPE#"=7 OR "I"."TYPE#"=9))=1)'), DBMS_XPLAN_TYPE('   5 - filter("SYS_ALIAS_1"."LINKNAME" IS NULL AND '), DBMS_XPLAN_TYPE('              "SYS_ALIAS_1"."NAME"&lt;&gt;''_NEXT_OBJECT'' AND '), DBMS_X
PLAN_TYPE('              "SYS_ALIAS_1"."NAME"&lt;&gt;''_default_auditing_options_'')'), DBMS_XPLAN_TYPE('   7 - access("SYS_ALIAS_1"."OWNER#"="U"."USER#")'), DBMS_XPLAN_TYPE('   8 - filter("I"."TYPE#"=1 OR
"I"."TYPE#"=2 OR "I"."TYPE#"=3 OR "I"."TYPE#"=4 '), DBMS_XPLAN_TYPE('              OR "I"."TYPE#"=6 OR "I"."TYPE#"=7 OR "I"."TYPE#"=9)'), DBMS_XPLAN_TYPE('   9 - access("I"."OBJ#"=:B1)'), DBMS_XPLAN_T
YPE('  13 - access("L"."OWNER#"="U"."USER#")'), DBMS_XPLAN_TYPE(' '), DBMS_XPLAN_TYPE('Note: cpu costing is off'))


SQL&gt;
&lt;/span&gt;&lt;/pre&gt;We can read the result, but it doesn't look much like the usually formatted PLAN_TABLE output.  It's just ... there ... in all of its unformatted glory and liberally sprinkled with "DBMS_XPLAN_TYPE" strings, which makes it rather confusing to interpret.  Let's use the TABLE() function and see if it provides something more recognizable:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------
| Id  | Operation                      |  Name        | Rows  | Bytes | Cost  |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |              |  8169 |  1412K|   102 |
|   1 |  VIEW                          | DBA_OBJECTS  |  8169 |  1412K|   102 |
|   2 |   UNION-ALL                    |              |       |       |       |
|*  3 |    FILTER                      |              |       |       |       |
|   4 |     NESTED LOOPS               |              |     1 |   235 |    18 |
|*  5 |      TABLE ACCESS FULL         | OBJ$         |     1 |   205 |    17 |
|   6 |      TABLE ACCESS CLUSTER      | USER$        |     1 |    30 |     1 |

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------
|*  7 |       INDEX UNIQUE SCAN        | I_USER#      |     1 |       |       |
|*  8 |     TABLE ACCESS BY INDEX ROWID| IND$         |     1 |    26 |     2 |
|*  9 |      INDEX UNIQUE SCAN         | I_IND1       |     1 |       |     1 |
|  10 |    NESTED LOOPS                |              |  8168 |   941K|    84 |
|  11 |     TABLE ACCESS FULL          | LINK$        |    82 |  7216 |     2 |
|  12 |     TABLE ACCESS CLUSTER       | USER$        |   100 |  3000 |     1 |
|* 13 |      INDEX UNIQUE SCAN         | I_USER#      |     1 |       |       |
-------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------

   3 - filter("SYS_ALIAS_1"."TYPE#"&lt;&gt;1 AND "SYS_ALIAS_1"."TYPE#"&lt;&gt;10 OR
              "SYS_ALIAS_1"."TYPE#"=1 AND  (SELECT /*+ */ 1 FROM "SYS"."IND$" "I" WHERE
              "I"."OBJ#"=:B1 AND ("I"."TYPE#"=1 OR "I"."TYPE#"=2 OR "I"."TYPE#"=3 OR
              "I"."TYPE#"=4 OR "I"."TYPE#"=6 OR "I"."TYPE#"=7 OR "I"."TYPE#"=9))=1)
   5 - filter("SYS_ALIAS_1"."LINKNAME" IS NULL AND
              "SYS_ALIAS_1"."NAME"&lt;&gt;'_NEXT_OBJECT' AND
              "SYS_ALIAS_1"."NAME"&lt;&gt;'_default_auditing_options_')
   7 - access("SYS_ALIAS_1"."OWNER#"="U"."USER#")
   8 - filter("I"."TYPE#"=1 OR "I"."TYPE#"=2 OR "I"."TYPE#"=3 OR "I"."TYPE#"=4
              OR "I"."TYPE#"=6 OR "I"."TYPE#"=7 OR "I"."TYPE#"=9)

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------
   9 - access("I"."OBJ#"=:B1)
  13 - access("L"."OWNER#"="U"."USER#")

Note: cpu costing is off

37 rows selected.

SQL&gt;
&lt;/span&gt;&lt;/pre&gt;Wow, it does.  The TABLE() function "pickles", or converts, the object data into a byte stream and formats the output.  The TABLE() function introduces the "COLLECTION ITERATOR (PICKLER FETCH)" step as shown in the second execution plan (for the "select * from table(dbms_xplan.display)" query) generated by autotrace:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------
| Id  | Operation                      |  Name        | Rows  | Bytes | Cost  |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |              |  8169 |  1412K|   102 |
|   1 |  VIEW                          | DBA_OBJECTS  |  8169 |  1412K|   102 |
|   2 |   UNION-ALL                    |              |       |       |       |
|*  3 |    FILTER                      |              |       |       |       |
|   4 |     NESTED LOOPS               |              |     1 |   235 |    18 |
|*  5 |      TABLE ACCESS FULL         | OBJ$         |     1 |   205 |    17 |
|   6 |      TABLE ACCESS CLUSTER      | USER$        |     1 |    30 |     1 |

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------
|*  7 |       INDEX UNIQUE SCAN        | I_USER#      |     1 |       |       |
|*  8 |     TABLE ACCESS BY INDEX ROWID| IND$         |     1 |    26 |     2 |
|*  9 |      INDEX UNIQUE SCAN         | I_IND1       |     1 |       |     1 |
|  10 |    NESTED LOOPS                |              |  8168 |   941K|    84 |
|  11 |     TABLE ACCESS FULL          | LINK$        |    82 |  7216 |     2 |
|  12 |     TABLE ACCESS CLUSTER       | USER$        |   100 |  3000 |     1 |
|* 13 |      INDEX UNIQUE SCAN         | I_USER#      |     1 |       |       |
-------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------------------------------------------------------------

   3 - filter("SYS_ALIAS_1"."TYPE#"&lt;&gt;1 AND "SYS_ALIAS_1"."TYPE#"&lt;&gt;10 OR
              "SYS_ALIAS_1"."TYPE#"=1 AND  (SELECT /*+ */ 1 FROM "SYS"."IND$" "I" WHERE
              "I"."OBJ#"=:B1 AND ("I"."TYPE#"=1 OR "I"."TYPE#"=2 OR "I"."TYPE#"=3 OR
              "I"."TYPE#"=4 OR "I"."TYPE#"=6 OR "I"."TYPE#"=7 OR "I"."TYPE#"=9))=1)
   5 - filter("SYS_ALIAS_1"."LINKNAME" IS NULL AND
              "SYS_ALIAS_1"."NAME"&lt;&gt;'_NEXT_OBJECT' AND
              "SYS_ALIAS_1"."NAME"&lt;&gt;'_default_auditing_options_')
   7 - access("SYS_ALIAS_1"."OWNER#"="U"."USER#")
   8 - filter("I"."TYPE#"=1 OR "I"."TYPE#"=2 OR "I"."TYPE#"=3 OR "I"."TYPE#"=4
              OR "I"."TYPE#"=6 OR "I"."TYPE#"=7 OR "I"."TYPE#"=9)

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------------------------------
   9 - access("I"."OBJ#"=:B1)
  13 - access("L"."OWNER#"="U"."USER#")

Note: cpu costing is off

37 rows selected.


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=17 Card=8168 Bytes=16336)
   1    0   &lt;span style="color:#009900;"&gt;COLLECTION ITERATOR (PICKLER FETCH) OF 'DISPLAY'&lt;/span&gt;




Statistics
----------------------------------------------------------
         23  recursive calls
          0  db block gets
         51  consistent gets
          0  physical reads
          0  redo size
       3384  bytes sent via SQL*Net to client
        678  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
         15  sorts (memory)
          0  sorts (disk)
         37  rows processed

SQL&gt;

SQL&gt;&lt;/span&gt;&lt;/pre&gt;Selecting from a function usually involves a picker fetch (introduced by the TABLE() operator) to format the output into a 'user-friendly' form, and, usually, it doesn't introduce massive amounts of overhead with respect to query processing (the conversion and iteration are both efficient processes).  I have rarely seen such a step add significantly to the response time (both queries shown above ran in about the same time).  Yes, there may be situations where each added step degrades the response time but if that happens investigate the situation fully for you may find your assumptions in error.&lt;br /&gt;
&lt;br /&gt;
It's not so bad to be 'in a pickle' when it comes to queries.&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=L2DawYezCzM:ks3OSiyg1L0:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=L2DawYezCzM:ks3OSiyg1L0:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://oratips-ddf.blogspot.com/feeds/7686442430763410908/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=7686442430763410908" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/7686442430763410908?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/7686442430763410908?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2009/12/in-pickle.html" title="In A Pickle" /><author><name>David Fitzjarrell</name><uri>https://plus.google.com/102215427046411604711</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-y2hTTVpTe4o/AAAAAAAAAAI/AAAAAAAAAGw/KabdyVeaX9M/s512-c/photo.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;D0UFQ3Yyeip7ImA9WxJTFU0.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-6262276016578663152</id><published>2009-04-23T16:21:00.003+01:00</published><updated>2009-04-23T17:46:52.892+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-04-23T17:46:52.892+01:00</app:edited><title>You Can't Do That ... Or, Can You?</title><content type="html">Views are interesting constructs in Oracle.  They can be built on a single table, they can contain aggregate data, they can be built on two or more tables joined together, they can even be built on other views.  Regardless of all of that, sometimes users may want to update data through a view and, depending upon how the view is built those updates may or may not be allowed.  Short of making the attempt and receiving the following dismal message:&lt;br /&gt;&lt;br /&gt;

&lt;pre&gt;&lt;span style="color:#009900;"&gt;ERROR at line 2:
ORA-01779: cannot modify a column which maps to a non key-preserved table
&lt;/span&gt;&lt;/pre&gt;

how do you know if you can update a particular view?  Ask Oracle, of course.&lt;br /&gt;&lt;br /&gt;

Oracle, in its infinite wisdom, provides three views to reveal which views can be updated, and even which columns in those views have modifiable data.  These views are:

&lt;pre&gt;&lt;span style="color:#009900;"&gt;USER_UPDATABLE_COLUMNS
ALL_UPDATABLE_COLUMNS
DBA_UPDATABLE_COLUMNS
&lt;/span&gt;&lt;/pre&gt;

The level of access in the database determines which view will provide the desired information; the USER-named view reports on all tables/views owned by the connected user, the ALL-named view reports on all tables and views the connected user can access regardless of ownership, and the DBA-named view reports on all tables and views in the database.  Usually the USER_UPDATABLE_COLUMNS view should be used; a sample query is shown below:

&lt;pre&gt;&lt;span style="color:#009900;"&gt;SQL&gt; select owner, table_name, column_name, updatable
  2  from user_updatable_columns;

OWNER                          TABLE_NAME                     COLUMN_NAME                    UPD
------------------------------ ------------------------------ ------------------------------ ---
BING                           AA                             CUCD1                          YES
BING                           AA                             CVRA                           YES
BING                           AA                             CONV_RESULT                    YES
BING                           BONUS                          ENAME                          YES
BING                           BONUS                          JOB                            YES
BING                           BONUS                          SAL                            YES
BING                           BONUS                          COMM                           YES
BING                           BONUS                          DNAME                          YES
BING                           CPU_APPLY_VERSION              INSTANCE_NAME                  YES
BING                           CPU_APPLY_VERSION              HOST_NAME                      YES
BING                           CPU_APPLY_VERSION              VERSION                        YES
BING                           CPU_APPLY_VERSION              COMMENTS                       YES
BING                           CPU_APPLY_VERSION              ACTION_TIME                    YES
BING                           CPU_APPLY_VERSION_HOLD         INSTANCE_NAME                  YES
BING                           CPU_APPLY_VERSION_HOLD         HOST_NAME                      YES
BING                           CPU_APPLY_VERSION_HOLD         VERSION                        YES
BING                           DBAOBJS                        OWNER                          NO
BING                           DBAOBJS                        OBJECT_NAME                    NO
BING                           DBAOBJS                        SUBOBJECT_NAME                 NO
BING                           DBAOBJS                        OBJECT_ID                      NO
BING                           DBAOBJS                        DATA_OBJECT_ID                 NO
BING                           DBAOBJS                        OBJECT_TYPE                    NO
BING                           DBAOBJS                        CREATED                        NO
BING                           DBAOBJS                        LAST_DDL_TIME                  NO
BING                           DBAOBJS                        TIMESTAMP                      NO
BING                           DBAOBJS                        STATUS                         NO
BING                           DBAOBJS                        TEMPORARY                      NO
BING                           DBAOBJS                        GENERATED                      NO
BING                           DBAOBJS                        SECONDARY                      NO
BING                           DBAOBJS                        NAMESPACE                      NO
BING                           DBAOBJS                        EDITION_NAME                   NO
...
&lt;/span&gt;&lt;/pre&gt;

This, of course, returns rows for tables and views; restricting this to just the views is a simple task:

&lt;pre&gt;&lt;span style="color:#009900;"&gt;SQL&gt; select owner, table_name, column_name, updatable
  2  from user_updatable_columns
  3  where table_name in (select view_name from user_views);

OWNER                          TABLE_NAME                     COLUMN_NAME                    UPD
------------------------------ ------------------------------ ------------------------------ ---
BING                           EMP_VIEW                       EMPNO                          YES
BING                           EMP_VIEW                       ENAME                          YES
BING                           EMP_VIEW                       JOB                            YES
BING                           EMP_VIEW                       MGR                            YES
BING                           EMP_VIEW                       HIREDATE                       YES
BING                           EMP_VIEW                       SAL                            YES
BING                           EMP_VIEW                       COMM                           YES
BING                           EMP_VIEW                       DEPTNO                         YES
BING                           V_BASE                         OBJECT_NAME                    NO
BING                           V_BASE                         SUBOBJECT_NAME                 NO
BING                           V_BASE                         OBJECT_ID                      NO
BING                           V_BASE                         DATA_OBJECT_ID                 NO
BING                           V_BASE                         OBJECT_TYPE                    NO
BING                           V_BASE                         CREATED                        NO
BING                           V_BASE                         LAST_DDL_TIME                  NO
BING                           V_BASE                         TIMESTAMP                      NO
BING                           V_BASE                         STATUS                         NO
BING                           V_BASE                         TEMPORARY                      NO
BING                           V_BASE                         GENERATED                      NO
BING                           V_BASE                         SECONDARY                      NO
BING                           V_BASE                         NAMESPACE                      NO
BING                           V_BASE                         EDITION_NAME                   NO
BING                           YINGYONG                       SNERM                          NO
BING                           YINGYONG                       FLANG                          NO
BING                           YINGYONG                       GLERBIT                        NO
BING                           YINGYONG                       DRONK                          NO

26 rows selected.

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

In this example it's possible to reduce the output considerably since all columns in a given view report the same value for UPDATABLE:

&lt;pre&gt;&lt;span style="color:#009900;"&gt;SQL&gt; select distinct owner, table_name, updatable
  2  from user_updatable_columns
  3  where table_name in (select view_name from user_views);

OWNER                          TABLE_NAME                     UPD
------------------------------ ------------------------------ ---
BING                           EMP_VIEW                       YES
BING                           V_BASE                         NO
BING                           YINGYONG                       NO

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

Now we know that only the EMP_VIEW is updatable in this schema.  Updatable views across schemas can also be found using the following query:

&lt;pre&gt;&lt;span style="color:#009900;"&gt;select distinct owner, table_name, updatable
from all_updatable_columns
where table_name in (select view_name from all_views)
and updatable = 'YES';
&lt;/span&gt;&lt;/pre&gt;

The list generated by that query may be quite large.&lt;br /&gt;&lt;br /&gt;

There is another way around the problem of updating data in an otherwise non-updatable view, and that is the INSTEAD OF trigger, which fires instead of the coded action (insert, update, delete) and bypasses the view by performing the requested action on the base tables.  And that is a subject for another post.&lt;br /&gt;&lt;br /&gt;

So, it's easy to find the views a user can update because Oracle knows which views those are.  Neat.&lt;br /&gt;&lt;br /&gt; 

They call it Oracle for a reason.&lt;br /&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=lc58_OlMB7E:qZdiz-6hDfc:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=lc58_OlMB7E:qZdiz-6hDfc:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://oratips-ddf.blogspot.com/feeds/6262276016578663152/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=6262276016578663152" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/6262276016578663152?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/6262276016578663152?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2009/04/you-cant-do-that-or-can-you.html" title="You Can't Do That ... Or, Can You?" /><author><name>David Fitzjarrell</name><uri>https://plus.google.com/102215427046411604711</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-y2hTTVpTe4o/AAAAAAAAAAI/AAAAAAAAAGw/KabdyVeaX9M/s512-c/photo.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;A0INSXg5eyp7ImA9WxBREU0.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-8261598810235438202</id><published>2009-03-19T14:48:00.005Z</published><updated>2009-12-29T17:59:58.623Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-29T17:59:58.623Z</app:edited><title>Hanging With The Locals</title><content type="html">There are still databases in existence using dictionary-managed tablespaces, which means that the SYSTEM tablespaces in these databases are also dictionary-managed (which presents other issues besides the extent management, such as using the SYSTEM tablespace as a temporary tablespace and having a TEMP tablespace utilizing permanent files).  Converting these tablespaces 'in-place' &lt;em&gt;&lt;span style="color:#3333ff;"&gt;is&lt;/span&gt;&lt;/em&gt; possible with a packaged procedure provided by Oracle, but this method doesn't work very well in terms of making the dictionary-managed tablespace a true locally managed one. Let's look at the results of a conversion and see where this method falls short of the mark.&lt;br /&gt;&lt;br /&gt;

DBA_TABLESPACES contains information on the extent management, extent sizing, block_size, status and other various aspects of all of the tablespaces in a given database.  The current description is:

&lt;pre&gt;&lt;span style="color:#6600cc;"&gt;SQL&gt; desc dba_tablespaces
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 TABLESPACE_NAME                           NOT NULL VARCHAR2(30)
 BLOCK_SIZE                                NOT NULL NUMBER
 INITIAL_EXTENT                                     NUMBER
 NEXT_EXTENT                                        NUMBER
 MIN_EXTENTS                               NOT NULL NUMBER
 MAX_EXTENTS                                        NUMBER
 PCT_INCREASE                                       NUMBER
 MIN_EXTLEN                                         NUMBER
 STATUS                                             VARCHAR2(9)
 CONTENTS                                           VARCHAR2(9)
 LOGGING                                            VARCHAR2(9)
 FORCE_LOGGING                                      VARCHAR2(3)
 EXTENT_MANAGEMENT                                  VARCHAR2(10)
 ALLOCATION_TYPE                                    VARCHAR2(9)
 PLUGGED_IN                                         VARCHAR2(3)
 SEGMENT_SPACE_MANAGEMENT                           VARCHAR2(6)
 DEF_TAB_COMPRESSION                                VARCHAR2(8)
 RETENTION                                          VARCHAR2(11)
 BIGFILE                                            VARCHAR2(3)
 
SQL&gt;&lt;/span&gt;&lt;/pre&gt;

Let's look at some information about tablespaces created as locally managed:

&lt;pre&gt;&lt;span style="color:#6600cc;"&gt;TABLESPACE_NAME                INITIAL_EXTENT NEXT_EXTENT EXTENT_MAN ALLOCATIO MIN_EXTLEN
------------------------------ -------------- ----------- ---------- --------- ----------
UNDOTBS1                                65536             LOCAL      SYSTEM         65536
SYSAUX                                  65536             LOCAL      SYSTEM         65536
&lt;/span&gt;&lt;/pre&gt;

Notice that the ALLOCATION_TYPE is listed as SYSTEM, meaning these are created AUTOALLOCATE, with extents starting at 64K and systematically increasing in size.  Notice also that the next extent is NULL, and that the minimum extent length is 64 K.  Let's now look at tablespaces which were dictionary-managed that were converted with the &lt;a href="http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14258/d_spadmn.htm#i997300"&gt;DBMS_SPACE_ADMIN.TABLESPACE_MIGRATE_TO_LOCAL&lt;/a&gt; procedure:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;TABLESPACE_NAME                INITIAL_EXTENT NEXT_EXTENT EXTENT_MAN ALLOCATIO MIN_EXTLEN
------------------------------ -------------- ----------- ---------- --------- ----------
TOOLS                                   32768       32768 LOCAL      USER           32768
USERS                                  131072      131072 LOCAL      USER          131072
&lt;/span&gt;&lt;/pre&gt;

Here we see a different picture: the ALLOCATION_TYPE is still set to USER, the INITIAL_EXTENT and NEXT_EXTENT fields are populated, not necessarily with matching values and (although not shown here) the PCT_INCREASE values are retained (a true locally managed tablespace has a PCTINCREASE of NULL).  Such omissions can create problems later, as free space in these 'mongrel' tablespaces may not coalesce, even by brute force methods and next extent sizing can grow since the PCTINCREASE is not ignored.  The initial and next extents can also provide problems as they may not be multiples of 64K (as are the extent sizes of a locally managed, autoallocate tablespace) or they may not be uniform in size (due to the pctincrease).  Remember, too, that any objects in these tablespaces won't be rebuilt with the 'standard' extent sizes provided by true locally managed tablespaces, and as the free space coalescing may not be reliable in a converted tablespace free space fragmentation can be a serious issue.  In true locally managed tablespaces free space fragmentation isn't a problem because either the extents are all multiples of 64K or all of the extents are uniformly sized.  In either case the freed extents can be reused by any new object which needs them.  Not so with a converted tablespace, as extent sizes can vary and can be, well, 'interesting' sizes so that fragmentation can be an issue, especially when the coalesce functionality fails.&lt;/br &gt;&lt;br /&gt;

So, how best to convert dictionary-managed tablespaces to locally managed?  The most reliable method is to create a new tablespace, locally managed, and move the objects from the old dictionary-managed tablespace to the new, locally managed one and then drop the old dictionary-managed tablespace.   Of course you can't do this if it's the SYSTEM tablespace; you're then stuck using the DBMS_SPACE_ADMIN package to migrate this tablespace, and all other dictionary-managed tablespace, in place (if that's possible as there are restrictions for using DBMS_SPACE_ADMIN.TABLESPACE_MIGRATE_TO_LOCAL).  &lt;em&gt;&lt;span style="color:#3333ff;"&gt;No dictionary-managed tablespaces can exist in read/write mode in a database where the SYSTEM tablespace is locally managed, so any and all dictionary-managed tablespaces other than SYSTEM need to be migrated BEFORE the SYSTEM tablespace is migrated.&lt;/span&gt;&lt;/em&gt;  The only other way to do this is to create a new database with a locally managed SYSTEM tablespace and, using a full export, relocate the objects from the source database.&lt;br /&gt;&lt;br /&gt;

So hanging with the locals is possible, but taking shortcuts usually isn't the best way to get the job done because you may find yourself in worse shape than if you did nothing at all.&lt;br /&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=wS430yIeDlE:6FLBkPi0BkQ:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=wS430yIeDlE:6FLBkPi0BkQ:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://oratips-ddf.blogspot.com/feeds/8261598810235438202/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=8261598810235438202" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/8261598810235438202?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/8261598810235438202?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2009/03/hanging-with-locals.html" title="Hanging With The Locals" /><author><name>David Fitzjarrell</name><uri>https://plus.google.com/102215427046411604711</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-y2hTTVpTe4o/AAAAAAAAAAI/AAAAAAAAAGw/KabdyVeaX9M/s512-c/photo.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;CU4BRHc_fyp7ImA9WxFUFkw.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-6585975616928746913</id><published>2009-03-11T14:46:00.011Z</published><updated>2010-06-27T05:45:55.947+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-27T05:45:55.947+01:00</app:edited><title>How Much Wood Could A Woodchuck Chuck ...</title><content type="html">Some of the most interesting questions pass through www.google.com, like this one:&lt;br /&gt;&lt;br /&gt;

"how to know how much table space is allocated for a user in oracle"&lt;br /&gt;&lt;br /&gt;

How DO we know how much space a given user account can consume in a tablespace?  Two views can produce that report, DBA_TS_QUOTAS and DBA_DATA_FILES.  Let's see how that can be done.&lt;br /&gt;&lt;br /&gt;

DBA_TS_QUOTAS provides information on which tablespaces a given user can use and how much space they can consume:

&lt;pre&gt;&lt;span style="color:#009900;"&gt;SQL&gt; desc dba_ts_quotas
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 TABLESPACE_NAME                           NOT NULL VARCHAR2(30)
 USERNAME                                  NOT NULL VARCHAR2(30)
 BYTES                                              NUMBER
 MAX_BYTES                                          NUMBER
 BLOCKS                                             NUMBER
 MAX_BLOCKS                                         NUMBER
 DROPPED                                            VARCHAR2(3) (available in 11g)

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

This view lists the tablespaces for which a user has an assigned quota, so not all of the tablespaces in the database will be listed for non-DBA users.  MAX_BYTES reports the actual size of the granted quota and if that quota is UNLIMITED the value of MAX_BYTES is -1.  Likewise for MAX_BLOCKS, which translates the MAX_BYTES column into blocks based upon the db_block_size parameter set at database creation.&lt;br /&gt;&lt;br /&gt;

Onward and upward to DBA_DATA_FILES:

&lt;pre&gt;&lt;span style="color:#009900;"&gt;SQL&gt; desc dba_data_files
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 FILE_NAME                                          VARCHAR2(513)
 FILE_ID                                            NUMBER
 TABLESPACE_NAME                                    VARCHAR2(30)
 BYTES                                              NUMBER
 BLOCKS                                             NUMBER
 STATUS                                             VARCHAR2(9)
 RELATIVE_FNO                                       NUMBER
 AUTOEXTENSIBLE                                     VARCHAR2(3)
 MAXBYTES                                           NUMBER
 MAXBLOCKS                                          NUMBER
 INCREMENT_BY                                       NUMBER
 USER_BYTES                                         NUMBER
 USER_BLOCKS                                        NUMBER
 ONLINE_STATUS                                      VARCHAR2(7)

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

The columns of interest in this view are TABLESPACE_NAME, BYTES and BLOCKS since we'll sum the last two columns, by tablespace, to obtain the total configured space available.&lt;br /&gt;&lt;br /&gt;

Now it's time to put this all together and return our report.  We'll use subquery factoring to assist in this query (available in 9i and later releases of Oracle):

&lt;pre&gt;&lt;span style="color:#6600CC;"&gt;SQL&gt; with ttlbytes as (
  2     select tablespace_name, sum(bytes) ttl_bytes, sum(blocks) ttl_blocks
  3     from dba_data_files
  4     group by tablespace_name
  5  ),
  6  userquotas as(
  7     select
  8     TABLESPACE_NAME,
  9     USERNAME,
 10     BYTES,
 11     max_bytes,
 12     blocks,
 13     MAX_BLOCKS
 14     from dba_ts_quotas
 15     where username = upper('&amp;&amp;1')
 16  )
 17  select
 18  s.tablespace_name,
 19  nvl(q.username, upper('&amp;&amp;1')) username,
 20  nvl(q.bytes,0) bytes,
 21  case when q.MAX_BYTES = -1 then s.ttl_bytes else nvl(q.max_bytes,0) end max_bytes,
 22  nvl(q.BLOCKS,0) blocks,
 23  case when q.MAX_BLOCKS = -1 then s.ttl_blocks else nvl(q.max_blocks,0) end max_blocks
 24  from userquotas q full outer join ttlbytes s
 25        on (q.tablespace_name = s.tablespace_name)
 26  order by q.username, s.tablespace_name;

TABLESPACE_NAME                USERNAME        BYTES   MAX_BYTES     BLOCKS  MAX_BLOCKS
------------------------------ ---------- ---------- ----------- ---------- -----------
TOOLS                          BING                0    10485760          0        1280  
USERS                          BING         17235968  2899247104       2104      353912
EXAMPLE                        BING                0           0          0           0
STATDATA                       BING                0           0          0           0
SYSAUX                         BING                0           0          0           0
SYSTEM                         BING                0           0          0           0
UNDOTBS1                       BING                0           0          0           0

7 rows selected.

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

Notice the report returns all of the tablespaces in the database, with user information for the tablespaces the given user can utilize.&lt;br /&gt;&lt;br /&gt;

Can this be done for a database release earlier than 9i?  You bet (but, really, you should upgrade to a supported release of Oracle):

&lt;pre&gt;&lt;span style="color:#6600CC;"&gt;SQL&gt; select
  2  s.tablespace_name,
  3  nvl(q.username, upper('&amp;&amp;1')) username,
  4  nvl(q.bytes,0) bytes,
  5  decode(q.MAX_BYTES, -1, s.ttl_bytes, nvl(q.max_bytes,0)) max_bytes,
  6  nvl(q.BLOCKS,0) blocks,
  7  decode(q.MAX_BLOCKS, -1, s.ttl_blocks, nvl(q.max_blocks,0)) max_blocks
  8  from (
  9     select
 10     TABLESPACE_NAME,
 11     USERNAME,
 12     BYTES,
 13          max_bytes,
 14     blocks,
 15     MAX_BLOCKS
 16     from dba_ts_quotas
 17     where username = upper('&amp;&amp;1')
 18  ) q, (
 19     select tablespace_name, sum(bytes) ttl_bytes, sum(blocks) ttl_blocks
 20     from dba_data_files
 21     group by tablespace_name
 22  ) s
 23  where q.tablespace_name (+)= s.tablespace_name
 24  order by q.username, s.tablespace_name;

TABLESPACE_NAME                USERNAME           BYTES   MAX_BYTES     BLOCKS  MAX_BLOCKS
------------------------------ ------------- ---------- ----------- ---------- -----------
TOOLS                          BING                   0    10485760          0        1280
USERS                          BING            17235968  2899247104       2104      353912
EXAMPLE                        BING                   0           0          0           0
STATDATA                       BING                   0           0          0           0
SYSAUX                         BING                   0           0          0           0
SYSTEM                         BING                   0           0          0           0
UNDOTBS1                       BING                   0           0          0           0

7 rows selected.

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

So answering the question listed at the start of this post is fairly easy and straightforward.  And you get even more information than originally requested since you also see the tablespaces for which a given user has no quota assigned.  Pretty slick if you ask me.&lt;br /&gt;&lt;br /&gt;

This report query can be modified to also flag when a user is close to reaching the limit on space in a tablespace.  The 9i and later version becomes:

&lt;pre&gt;&lt;span style="color:#6600CC;"&gt;SQL&gt; with ttlbytes as (
  2     select tablespace_name, sum(bytes) ttl_bytes, sum(blocks) ttl_blocks
  3     from dba_data_files
  4     group by tablespace_name
  5  ),
  6  userquotas as(
  7     select
  8     TABLESPACE_NAME,
  9     USERNAME,
 10     BYTES,
 11          max_bytes,
 12     blocks,
 13     MAX_BLOCKS
 14     from dba_ts_quotas
 15     where username = upper('&amp;&amp;1')
 16  )
 17  select
 18  s.tablespace_name,
 19  nvl(q.username, upper('&amp;&amp;1')) username,
 20  nvl(q.bytes,0) bytes,
 21  case when q.MAX_BYTES = -1 then s.ttl_bytes else nvl(q.max_bytes,0) end max_bytes,
 22  nvl(q.BLOCKS,0) blocks,
 23  case when q.MAX_BLOCKS = -1 then s.ttl_blocks else nvl(q.max_blocks,0) end max_blocks,
 24  case when abs(q.bytes - q.max_bytes) &lt; 10240000 then 'WARNING'
 25       when abs(q.bytes - q.max_bytes) &lt; 1024000 then 'ALERT'
 26       else 'OK' end status
 27  from userquotas q full outer join ttlbytes s
 28        on (q.tablespace_name = s.tablespace_name)
 29  order by q.username, s.tablespace_name;

TABLESPACE_NAME                USERNAME       BYTES   MAX_BYTES     BLOCKS  MAX_BLOCKS STATUS
------------------------------ --------- ---------- ----------- ---------- ----------- -------
TOOLS                          BING               0    10485760          0        1280 OK
USERS                          BING        17235968  2899247104       2104      353912 OK
EXAMPLE                        BING               0           0          0            0 OK
STATDATA                       BING               0           0          0            0 OK
SYSAUX                         BING               0           0          0            0 OK
SYSTEM                         BING               0           0          0            0 OK
UNDOTBS1                       BING               0           0          0            0 OK

7 rows selected.

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

And for releases earlier than 9i:

&lt;pre&gt;&lt;span style="color:#6600CC;"&gt;SQL&gt; select
  2  s.tablespace_name,
  3  nvl(q.username, upper('&amp;&amp;1')) username,
  4  nvl(q.bytes,0) bytes,
  5  decode(q.MAX_BYTES, -1, s.ttl_bytes, nvl(q.max_bytes,0)) max_bytes,
  6  nvl(q.BLOCKS,0) blocks,
  7  decode(q.MAX_BLOCKS, -1, s.ttl_blocks, nvl(q.max_blocks,0)) max_blocks,
  8  decode(sign(decode(decode(q.max_bytes, -1, s.ttl_bytes, q.max_bytes) - q.bytes, 0, -1, 1)), -1,'ALARM', 'OK') status
  9  from (
 10     select
 11     TABLESPACE_NAME,
 12     USERNAME,
 13     BYTES,
 14          max_bytes,
 15     blocks,
 16     MAX_BLOCKS
 17     from dba_ts_quotas
 18     where username = upper('&amp;&amp;1')
 19  ) q, (
 20     select tablespace_name, sum(bytes) ttl_bytes, sum(blocks) ttl_blocks
 21     from dba_data_files
 22     group by tablespace_name
 23  ) s
 24  where q.tablespace_name (+)= s.tablespace_name
 25  order by q.username, s.tablespace_name;

TABLESPACE_NAME                USERNAME         BYTES   MAX_BYTES     BLOCKS  MAX_BLOCKS STATU
------------------------------ ----------- ---------- ----------- ---------- ----------- -----
TOOLS                          BING                 0    10485760          0        1280 OK
USERS                          BING          17235968  2899247104       2104      353912 OK
EXAMPLE                        BING                 0           0          0          0 OK
STATDATA                       BING                 0           0          0          0 OK
SYSAUX                         BING                 0           0          0          0 OK
SYSTEM                         BING                 0           0          0          0 OK
UNDOTBS1                       BING                 0           0          0          0 OK

7 rows selected.

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

Without having CASE the indicator is rudimentary, stating that either there IS space to use or there ISN'T space available.  But, it works as well as it can given the limitations of the 8.1.7 and earlier SQL engines.&lt;br /&gt;&lt;br /&gt;

How much 'wood' can a 'woodchuck' chuck?  That depends upon how much 'wood' is left to chuck.  And you'll know exactly in releases 9.0.1 and later.  And, well, approximately in releases 8.1.7 and earlier (talk about ancient history...), which is, of course, better than not knowing at all.&lt;br /&gt;&lt;br /&gt;

So if you're still on 8.1.7.4 &lt;em&gt;seriously&lt;/em&gt; consider upgrading to 10.2.0 or 11.1.  [If you're still on 7.3 or 8.0, maybe you should open a museum.]  Because you'd really rather know exactly how much space your users have left.&lt;br /&gt;&lt;br /&gt;

You would.&lt;br /&gt;&lt;br /&gt;

Really.&lt;br /&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=VRgoqYqaMm4:KIF2a1Hv_ao:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=VRgoqYqaMm4:KIF2a1Hv_ao:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://oratips-ddf.blogspot.com/feeds/6585975616928746913/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=6585975616928746913" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/6585975616928746913?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/6585975616928746913?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2009/03/how-much-wood-could-woodchuck-chuck.html" title="How Much Wood Could A Woodchuck Chuck ..." /><author><name>David Fitzjarrell</name><uri>https://plus.google.com/102215427046411604711</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-y2hTTVpTe4o/AAAAAAAAAAI/AAAAAAAAAGw/KabdyVeaX9M/s512-c/photo.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DEMFRno-fSp7ImA9WxVVEks.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-5419499264250908784</id><published>2009-03-05T14:21:00.003Z</published><updated>2009-03-05T15:20:17.455Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-03-05T15:20:17.455Z</app:edited><title>Expect The Unexpected</title><content type="html">For years developers have been warned to not rely upon default date formats when writing application code, which is sound advice indeed.  However, maybe that should be expanded to include not relying upon what one might expect when passing partial date strings to TO_DATE since the results might not match the desired outcome.  Let's look at some examples and see where Oracle may not do what you think it should.&lt;br /&gt;&lt;br /&gt;

I think by now most people know that if the time is not supplied to TO_DATE it defaults to midnight:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt;
SQL&gt; --
SQL&gt; -- By default Oracle sets the time to midnight
SQL&gt; -- if the time is not supplied to the
SQL&gt; -- TO_DATE function
SQL&gt; --
SQL&gt; select TO_DATE('03012009','DDMMYYYY') from dual;

TO_DATE('03012009',
-------------------
01-03-2009 00:00:00

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

so this should be expected behaviour.  Let's look at what happens when the day is not supplied:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; --
SQL&gt; -- Oracle defaults to the first day of the month
SQL&gt; -- when the day is not provided to TO_DATE
SQL&gt; --
SQL&gt; select TO_DATE('032009','MMYYYY') from dual;

TO_DATE('032009','M
-------------------
03-01-2009 00:00:00

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

So far this is behaviour to be expected.  What happens when only the year is supplied?  Let's find out:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; --
SQL&gt; -- Oracle defaults to the first day
SQL&gt; -- of the current month when neither month nor day
SQL&gt; -- is specified
SQL&gt; --
SQL&gt; select TO_DATE('1995','YYYY') from dual;

TO_DATE('1995','YYY
-------------------
03-01-1995 00:00:00

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

Hmmm, I would have expected the current month &lt;em&gt;and&lt;/em&gt; day to be returned.  Now let's pass the day and the year to TO_DATE:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; --
SQL&gt; -- Oracle defaults to the current month
SQL&gt; -- when it is not provided
SQL&gt; --
SQL&gt; -- We illustrate this by passing the day and the
SQL&gt; -- year to TO_DATE
SQL&gt; --
SQL&gt; select TO_DATE('092008','DDYYYY') from dual;

TO_DATE('092008','D
-------------------
03-09-2008 00:00:00

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

And that's what I would expect.  Passing in the day and the month causes Oracle to use the current year:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; --
SQL&gt; -- The default is to assume the current year if it
SQL&gt; -- is not provided
SQL&gt; --
SQL&gt; select TO_DATE('0712','MMDD') from dual;

TO_DATE('0712','MMD
-------------------
07-12-2009 00:00:00

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

and that's, again, what I would expect.  Now let's pass in only the day:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; --
SQL&gt; -- Assume the current month and year when only
SQL&gt; -- the day is provided
SQL&gt; --
SQL&gt; select TO_DATE('23','DD') from dual;

TO_DATE('23','DD')
-------------------
03-23-2009 00:00:00

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

Now let's provide only the time:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; --
SQL&gt; -- Strangeness abounds, as when only the time is provided
SQL&gt; -- Oracle defaults to the current month but assumes
SQL&gt; -- the first day of that month
SQL&gt; --
SQL&gt; select to_date('11:00:00','hh24:mi:ss')
  2  from dual;

TO_DATE('11:00:00',
-------------------
03-01-2009 11:00:00

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

I would have thought Oracle would presume the current month and day for that example, but what I think and what Oracle does are two different things.&lt;br /&gt;&lt;br /&gt;

So, Oracle can do the expected, depending upon which part of the date string is supplied.  It can also do the unexpected, and that can be unnerving if you're trying to troubleshoot an application and can't understand why the date arithmetic is off:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; --
SQL&gt; -- This doesn't return the expected result
SQL&gt; -- because Oracle assumes the first day of the
SQL&gt; -- month, not the current day
SQL&gt; --
SQL&gt; select sysdate - to_date('07:00:00','hh24:mi:ss')
  2  from dual;

SYSDATE-TO_DATE('07:00:00','HH24:MI:SS')
----------------------------------------
                              4.08762731

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

Fixing that problem means rewriting the query a bit:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; --
SQL&gt; -- Let's get the result we expected
SQL&gt; --
SQL&gt; select sysdate - to_date(to_char(sysdate, 'MM-DD-RRRR')||' 07:00:00','mm-dd-rrrr hh24:mi:ss')
  2  from dual;

SYSDATE-TO_DATE(TO_CHAR(SYSDATE,'MM-DD-RRRR')||'07:00:00','MM-DD-RRRRHH24:MI:SS'
--------------------------------------------------------------------------------
                                                                      .087627315

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

If you're not certain what Oracle will return from a function call you should test the code before assuming anything as the examples above attest.  We certainly didn't get the second result from the code in the prior example simply because Oracle didn't return the default data as we thought it should.&lt;br /&gt;&lt;br /&gt;

Expect the unexpected, and nothing should be a surprise.  Well, at least not an unpleasant one.&lt;br /&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=hgB9X_Z-Jxk:hlO0z96rx6k:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=hgB9X_Z-Jxk:hlO0z96rx6k:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://oratips-ddf.blogspot.com/feeds/5419499264250908784/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=5419499264250908784" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/5419499264250908784?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/5419499264250908784?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2009/03/expect-unexpected.html" title="Expect The Unexpected" /><author><name>David Fitzjarrell</name><uri>https://plus.google.com/102215427046411604711</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-y2hTTVpTe4o/AAAAAAAAAAI/AAAAAAAAAGw/KabdyVeaX9M/s512-c/photo.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DU4GSXo_cSp7ImA9WhJSFU8.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-5627166215211456159</id><published>2009-01-28T16:08:00.008Z</published><updated>2012-07-05T23:38:48.449+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-07-05T23:38:48.449+01:00</app:edited><title>That Was The Week That Wasn't</title><content type="html">The following question seems simple enough:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;Is there a quick way to convert week number (of the year) to start date of that week?

EX: If I pass week number as 1 then it should return 1/1/2009
    If I pass week number as 7 then it should return 2/8/2009
&lt;/span&gt;&lt;/pre&gt;This, of course, assumes a number of things which may or may not be true, such as the first day of week 1 is January 1 and that week 7 is defined to include, and also begin on, February 8.  Such assumptions depend upon which week numbering 'scheme' one elects to use, and there are two common schemes currently in use by Oracle.  Let's look at both of them and see what differences they contain and how they can throw the listed assumptions 'out of the window'.&lt;br /&gt;
&lt;br /&gt;
The two week numbering systems in use by Oracle are the U.S. week numbering system and the ISO week numbering system.  They ARE different in how they define Week number 1 and that can throw a 'monkey wrench' into any methodology one could implement to answer the above listed question.&lt;br /&gt;
&lt;br /&gt;
If we use the U.S week numbering system we can easily satisfy the first condition listed in the posted question as Week 1 is defined as the week containing January 1:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; with date_wk as (
  2        select to_date('01/01/'||to_char(sysdate, 'RRRR'), 'MM/DD/RRRR') + rownum - 1 dt
  3        from dual
  4        connect by level &lt;= 366
  5  ),
  6  wk_dt as (
  7        select dt, to_number(to_char(dt, 'ww')) wk_of_yr
  8        from date_wk
  9  )
 10  select dt, wk_of_yr
 11  from wk_dt
 12  where wk_of_yr = &amp;&amp;1
 13  /

DT          WK_OF_YR
--------- ----------
&lt;b&gt;&lt;span style="color:#6600CC;"&gt;01-JAN-09 &lt;/span&gt;&lt;/b&gt;         1
02-JAN-09          1
03-JAN-09          1
04-JAN-09          1
05-JAN-09          1
06-JAN-09          1
07-JAN-09          1
01-JAN-10          1

8 rows selected.

SQL&gt;
SQL&gt; with date_wk as (
  2        select to_date('01/01/'||to_char(sysdate, 'RRRR'), 'MM/DD/RRRR') + rownum - 1 dt
  3        from dual
  4        connect by level &lt;= 366
  5  ),
  6  wk_dt as (
  7        select dt, to_number(to_char(dt, 'ww')) wk_of_yr
  8        from date_wk
  9  )
 10  select min(dt)
 11  from wk_dt
 12  where wk_of_yr = &amp;&amp;1
 13  /

MIN(DT)
---------
01-JAN-09

SQL&gt;&lt;/span&gt;&lt;/pre&gt;&lt;span style="color:#009900;"&gt; [In Oracle syntax the format specifier for the U.S. week numbering system is WW, in either upper or lower case.  The first subquery shown generates a list of dates starting with January 1 of the current year and ends 365 days later.  The second subquery takes that list and generates the U.S. week number for each date.  The final query returns results based upon the supplied week number.]&lt;br /&gt;
&lt;br /&gt;
&lt;/span&gt;  But Week 7 of that numbering convention doesn't contain February 8, 2009:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; with date_wk as (
  2        select to_date('01/01/'||to_char(sysdate, 'RRRR'), 'MM/DD/RRRR') + rownum - 1 dt
  3        from dual
  4        connect by level &lt;= 366
  5  ),
  6  wk_dt as (
  7        select dt, to_number(to_char(dt, 'ww')) wk_of_yr
  8        from date_wk
  9  )
 10  select dt, wk_of_yr
 11  from wk_dt
 12  where wk_of_yr = &amp;&amp;1
 13  /

DT          WK_OF_YR
--------- ----------
12-FEB-09          7
13-FEB-09          7
14-FEB-09          7
15-FEB-09          7
16-FEB-09          7
17-FEB-09          7
18-FEB-09          7

7 rows selected.

SQL&gt;
SQL&gt; with date_wk as (
  2        select to_date('01/01/'||to_char(sysdate, 'RRRR'), 'MM/DD/RRRR') + rownum - 1 dt
  3        from dual
  4        connect by level &lt;= 366
  5  ),
  6  wk_dt as (
  7        select dt, to_number(to_char(dt, 'ww')) wk_of_yr
  8        from date_wk
  9  )
 10  select min(dt)
 11  from wk_dt
 12  where wk_of_yr = &amp;&amp;1
 13  /

MIN(DT)
---------
12-FEB-09

SQL&gt;&lt;/span&gt;&lt;/pre&gt;Week 6 does, although it's not the starting date of that week:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; with date_wk as (
  2        select to_date('01/01/'||to_char(sysdate, 'RRRR'), 'MM/DD/RRRR') + rownum - 1 dt
  3        from dual
  4        connect by level &lt;= 366
  5  ),
  6  wk_dt as (
  7        select dt, to_number(to_char(dt, 'ww')) wk_of_yr
  8        from date_wk
  9  )
 10  select dt, wk_of_yr
 11  from wk_dt
 12  where wk_of_yr = &amp;&amp;1
 13  /

DT          WK_OF_YR
--------- ----------
05-FEB-09          6
06-FEB-09          6
07-FEB-09          6
&lt;b&gt;&lt;span style="color:#6600CC;"&gt;08-FEB-09&lt;/span&gt;&lt;/b&gt;          6
09-FEB-09          6
10-FEB-09          6
11-FEB-09          6

7 rows selected.

SQL&gt;
SQL&gt; with date_wk as (
  2        select to_date('01/01/'||to_char(sysdate, 'RRRR'), 'MM/DD/RRRR') + rownum - 1 dt
  3        from dual
  4        connect by level &lt;= 366
  5  ),
  6  wk_dt as (
  7        select dt, to_number(to_char(dt, 'ww')) wk_of_yr
  8        from date_wk
  9  )
 10  select min(dt)
 11  from wk_dt
 12  where wk_of_yr = &amp;&amp;1
 13  /

MIN(DT)
---------
05-FEB-09

SQL&gt;&lt;/span&gt;&lt;/pre&gt;Now, if the ISO week numbering convention is used the first condition of the question won't be satisfied as Week 1 is defined to contain the first Thursday of the calendar year, thus the starting date for ISO Week 1 can be in December, and for 2009 it is:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; with date_wk as (
  2        select to_date('01/01/'||to_char(sysdate, 'RRRR'), 'MM/DD/RRRR') + rownum - 4 dt
  3        from dual
  4        connect by level &lt;= 366
  5  ),
  6  wk_dt as (
  7        select dt, to_number(to_char(dt, 'iw')) wk_of_yr
  8        from date_wk
  9  )
 10  select dt, wk_of_yr
 11  from wk_dt
 12  where wk_of_yr = &amp;&amp;1
 13  /

DT          WK_OF_YR
--------- ----------
29-DEC-08          1
30-DEC-08          1
31-DEC-08          1
&lt;b&gt;&lt;span style="color:#6600CC;"&gt;01-JAN-09  &lt;/span&gt;&lt;/b&gt;        1
02-JAN-09          1
03-JAN-09          1
04-JAN-09          1

7 rows selected.

SQL&gt;
SQL&gt; with date_wk as (
  2        select to_date('01/01/'||to_char(sysdate, 'RRRR'), 'MM/DD/RRRR') + rownum - 4 dt
  3        from dual
  4        connect by level &lt;= 366
  5  ),
  6  wk_dt as (
  7        select dt, to_number(to_char(dt, 'iw')) wk_of_yr
  8        from date_wk
  9  )
 10  select min(dt)
 11  from wk_dt
 12  where wk_of_yr = &amp;&amp;1
 13  /

MIN(DT)
---------
29-DEC-08

SQL&gt;&lt;/span&gt;&lt;/pre&gt;&lt;span style="color:#009900;"&gt; [The Oracle format specifier for the ISO week numbering system is IW, in either upper or lower case.  The change to the format specifier is the only change made to the query posted at the beginning.]&lt;br /&gt;
&lt;br /&gt;
&lt;/span&gt;  ISO Week 7 doesn't answer the second condition, either, since February 8, 2009 is the last day of ISO Week 6:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; with date_wk as (
  2        select to_date('01/01/'||to_char(sysdate, 'RRRR'), 'MM/DD/RRRR') + rownum - 4 dt
  3        from dual
  4        connect by level &lt;= 366
  5  ),
  6  wk_dt as (
  7        select dt, to_number(to_char(dt, 'iw')) wk_of_yr
  8        from date_wk
  9  )
 10  select dt, wk_of_yr
 11  from wk_dt
 12  where wk_of_yr = &amp;&amp;1
 13  /

DT          WK_OF_YR
--------- ----------
09-FEB-09          7
10-FEB-09          7
11-FEB-09          7
12-FEB-09          7
13-FEB-09          7
14-FEB-09          7
15-FEB-09          7

7 rows selected.

SQL&gt;
SQL&gt; with date_wk as (
  2        select to_date('01/01/'||to_char(sysdate, 'RRRR'), 'MM/DD/RRRR') + rownum - 4 dt
  3        from dual
  4        connect by level &lt;= 366
  5  ),
  6  wk_dt as (
  7        select dt, to_number(to_char(dt, 'iw')) wk_of_yr
  8        from date_wk
  9  )
 10  select min(dt)
 11  from wk_dt
 12  where wk_of_yr = &amp;&amp;1
 13  /

MIN(DT)
---------
09-FEB-09

SQL&gt;&lt;/span&gt;&lt;/pre&gt;How, then, is the ISO week defined?  It starts on Monday and ends on Sunday, and ISO Week 1 is defined in the following equivalent terms:&lt;br /&gt;
&lt;br /&gt;
&lt;span style="color:#6600CC;"&gt;the week with the year's first Thursday in it (the ISO 8601 definition) &lt;br /&gt;
the week starting with the Monday which is nearest in time to 1 January &lt;br /&gt;
the week with the year's first working day in it (if Saturdays, Sundays, and 1 January are not working days) &lt;br /&gt;
the week with January 4 in it &lt;br /&gt;
the first week with the majority (four or more) of its days in the starting year &lt;br /&gt;
the week starting with the Monday in the period 29 December - 4 January &lt;br /&gt;
the week with the Thursday in the period 1 - 7 January &lt;br /&gt;
If 1 January is on a Monday, Tuesday, Wednesday or Thursday, it is in week 01. If 1 January is on a Friday, Saturday or Sunday, it is in week 52 or 53 of the previous year. &lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
Given the above definition there are some years where even the first condition of the original question won't be satisfied, like 2010, where the first day of ISO Week 1 is January 4:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; with date_wk as (
  2          select to_date('01/01/2010', 'MM/DD/RRRR') + rownum - 1 dt
  3          from dual
  4          connect by level &lt;= 366
  5  ),
  6  wk_dt as (
  7          select dt, to_number(to_char(dt, 'iw')) wk_of_yr
  8          from date_wk
  9  )
 10  select dt, wk_of_yr
 11  from wk_dt
 12  where wk_of_yr = &amp;&amp;1
 13  /

DT          WK_OF_YR
--------- ----------
04-JAN-10          1
05-JAN-10          1
06-JAN-10          1
07-JAN-10          1
08-JAN-10          1
09-JAN-10          1
10-JAN-10          1

7 rows selected.

SQL&gt;
SQL&gt; with date_wk as (
  2          select to_date('01/01/2010', 'MM/DD/RRRR') + rownum - 1 dt
  3          from dual
  4          connect by level &lt;= 366
  5  ),
  6  wk_dt as (
  7          select dt, to_number(to_char(dt, 'iw')) wk_of_yr
  8          from date_wk
  9  )
 10  select min(dt)
 11  from wk_dt
 12  where wk_of_yr = &amp;&amp;1
 13  /

MIN(DT)
---------
04-JAN-10

SQL&gt;&lt;/span&gt;&lt;/pre&gt;The U.S. week is defined as starting on Sunday and ending on Saturday.  Week number 1 in this convention is defined as the week beginning on January 1, which may be a "partial" week based on the convention that calendar weeks start on Sunday and end on Saturday.  As such the last week of the year in this convention can also be a "partial" week based on the convention stated in the previous sentence.  The full definition of U.S. Week Number 1 is:&lt;br /&gt;
&lt;br /&gt;
The first week of the year contains 1 January, the 1st Saturday and is comprised of days 1-7 of the year.&lt;br /&gt;
&lt;br /&gt;
This allows the first week of the year to start on any day of the conventional week and end six days later; the first week could run from Wednesday to Tuesday rather than from Sunday to Saturday.  So, the question, as posed, relies upon a numbering system which allows partial weeks, the weeks always start on Sunday (so how does a partial week occur?), always end on Saturday and declare Week Number 1 as that week starting with January 1 (a criteria that can run afoul of the Sunday to Saturday, 7 days in a week 'rule').  In such a system February 8, 2009, would be the starting date for Week 7 (because Week 1 only has three days, January 1,2 and 3, a strange occurrence indeed as it contradicts the stated definition of every week starting on a Sunday).  I don't know of a numbering scheme which meets that conflicting criteria.  But, there MIGHT be one in use somewhere which satisfies all of those conditions.  Stranger things have happened, but with the given numbering schemes available the answer to the original question is "No".&lt;br /&gt;
&lt;br /&gt;
And that was the week that wasn't.&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=e1wmeTZSx10:jwnKmm1kllg:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=e1wmeTZSx10:jwnKmm1kllg:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://oratips-ddf.blogspot.com/feeds/5627166215211456159/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=5627166215211456159" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/5627166215211456159?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/5627166215211456159?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2009/01/that-was-week-that-wasnt.html" title="That Was The Week That Wasn't" /><author><name>David Fitzjarrell</name><uri>https://plus.google.com/102215427046411604711</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-y2hTTVpTe4o/AAAAAAAAAAI/AAAAAAAAAGw/KabdyVeaX9M/s512-c/photo.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;C04HRng6cCp7ImA9WxBRF0s.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-6908813183914363739</id><published>2008-12-17T21:33:00.008Z</published><updated>2010-01-06T07:12:17.618Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-01-06T07:12:17.618Z</app:edited><title>It's Moving Day</title><content type="html">This seems to be a typical series of events when things aren't going quite as nicely as originally planned:&lt;br /&gt;&lt;br /&gt;

"The time comes when someone is, well, unhappy with the performace of the database.  For troubleshooting such complaints what tools are available?  The most common, and least intrusive, is Statspack and it's fairly easy to install; simply execute the spcreate.sql script, found in $ORACLE_HOME/rdbms/admin, as SYS and, presuming the installation is successful you're ready to go.  You'll need to set a password and both the default and temporary tablespaces for this user (PERFSTAT), but you're prompted for that information by the script (the log file is created after the password has been submitted, so that display is not included here):

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;
Choose the Default tablespace for the PERFSTAT user
--------------------------------------------------- 
Below is the list of online tablespaces in this database which can
store user data.  Specifying the SYSTEM tablespace for the user's
default tablespace will result in the installation FAILING, as
using SYSTEM for performance data is not supported.

Choose the PERFSTAT users's default tablespace.  This is the tablespace
in which the STATSPACK tables and indexes will be created.

TABLESPACE_NAME                CONTENTS  STATSPACK DEFAULT TABLESPACE           
------------------------------ --------- ----------------------------           
EXAMPLE                        PERMANENT                                        
STATDATA                       PERMANENT                                        
SYSAUX                         PERMANENT *                                      
TOOLS                          PERMANENT                                        
USERS                          PERMANENT                                        

Pressing &lt;return&gt; will result in STATSPACK's recommended default
tablespace (identified by *) being used.

Enter value for default_tablespace: tools

Using tablespace TOOLS as PERFSTAT default tablespace.                          


Choose the Temporary tablespace for the PERFSTAT user
----------------------------------------------------- 
Below is the list of online tablespaces in this database which can
store temporary data (e.g. for sort workareas).  Specifying the SYSTEM
tablespace for the user's temporary tablespace will result in the
installation FAILING, as using SYSTEM for workareas is not supported.

Choose the PERFSTAT user's Temporary tablespace.

TABLESPACE_NAME                CONTENTS  DB DEFAULT TEMP TABLESPACE             
------------------------------ --------- --------------------------             
TEMP                           TEMPORARY *                                      

Pressing &lt;return&gt; will result in the database's default Temporary
tablespace (identified by *) being used.

Enter value for temporary_tablespace: 

Using tablespace TEMP as PERFSTAT temporary tablespace.                         


... Creating PERFSTAT user


... Installing required packages


... Creating views


... Granting privileges

NOTE:
SPCUSR complete. Please check spcusr.lis for any errors.
&lt;/span&gt;&lt;/pre&gt;

Notice that the TOOLS tablespace was chosen for PERFSTAT to use; any tablespace CAN be used for the PERFSTAT user, but I prefer to keep its objects out of the SYSAUX tablespace (the default set by the script).  (But, you don't need to use the TOOLS tablespace; more on that in a bit.)&lt;br /&gt;&lt;br /&gt;

Tables, sequences, synonyms and packages are created to enable you to gather statistics, instance-wide, with a simple command:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; exec statspack.snap

PL/SQL procedure successfully completed.

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

Two or more snapshots are required to generate a report, and it's recommended that a 15-minute interval elapse between snaps.  Once you have sufficient snapshots generating a report is easier than falling off of a log (so to speak):


&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; @?/rdbms/admin/spreport

Current Instance
~~~~~~~~~~~~~~~~

   DB Id    DB Name      Inst Num Instance
----------- ------------ -------- ------------
 1198289520 ORCL                1 orcl



Instances in this Statspack schema
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

   DB Id    Inst Num DB Name      Instance     Host
----------- -------- ------------ ------------ ------------
 1198289520        1 ORCL         orcl         BVL-44B85C84
                                               4D

Using 1198289520 for database Id
Using          1 for instance number


Specify the number of days of snapshots to choose from
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Entering the number of days (n) will result in the most recent
(n) days of snapshots being listed.  Pressing &lt;return&gt; without
specifying a number lists all completed snapshots.



Listing all Completed Snapshots

                                                       Snap
Instance     DB Name        Snap Id   Snap Started    Level Comment
------------ ------------ --------- ----------------- ----- --------------------
orcl         ORCL                 1 17 Dec 2008 14:10     5
                                  2 17 Dec 2008 14:24     5



Specify the Begin and End Snapshot Ids
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Enter value for begin_snap:&lt;/span&gt;&lt;/pre&gt;

Select the snaps to compare, choose a file name if you don't want the default, and, voila!  You have your report."&lt;br /&gt;&lt;br /&gt;

So you've installed Statspack and used it quite a bit, and all of these statistics are stored somewhere (the PERFSTAT schema resides in the default tablespace you assigned to the PERFSTAT user, and in this case it's TOOLS).  But, what if you want to use a &lt;em&gt;different&lt;/em&gt; tablespace for the PERFSTAT tables?  What if you selected a tablespace you &lt;em&gt;thought&lt;/em&gt; was unused by others but discover later the error of your ways?  Or, gee whiz, what if you think a change of scenery for the PERFSTAT tables/indexes might make them happier and perform better?  The task is fairly simple, really; provide the PERFSTAT user access to the new tablespace you want to use [I usually create a tablespace solely for PERFSTAT to use (STATDATA) so granting an unlimited quota on it doesn't create problems later], then move the tables and indexes to that new tablespace:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; connect / as sysdba
Connected.
SQL&gt; alter user perfstat quota unlimited on statdata;

User altered.

SQL&gt; connect perfstat
Password:
Connected.
SQL&gt;&lt;/span&gt;&lt;/pre&gt;

Then generate scripts to move the tables and rebuild the indexes and execute them as PERFSTAT.  A sample index rebuild script is shown:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;
alter index STATS$IDLE_EVENT_PK rebuild tablespace statdata;
alter index STATS$INTERCONNECT_PINGS_PK rebuild tablespace statdata;
alter index STATS$MEMORY_RESIZE_OPS_PK rebuild tablespace statdata;
alter index STATS$MEMORY_DYNAMIC_COMPS_PK rebuild tablespace statdata;
alter index STATS$MEMORY_TARGET_ADVICE_PK rebuild tablespace statdata;
alter index STATS$IOSTAT_FUNCTION_PK rebuild tablespace statdata;
alter index STATS$IOSTAT_FUNCTION_NAME_PK rebuild tablespace statdata;
alter index STATS$DYNAMIC_REM_STATS_PK rebuild tablespace statdata;
alter index STATS$MUTEX_SLEEP_PK rebuild tablespace statdata;
alter index STATS$STREAMS_POOL_ADVICE_PK rebuild tablespace statdata;
alter index STATS$SGA_TARGET_ADVICE_PK rebuild tablespace statdata;
alter index STATS$PROCESS_MEMORY_ROLLUP_PK rebuild tablespace statdata;
alter index STATS$$PROCESS_ROLLUP_PK rebuild tablespace statdata;
alter index STATS$OSSTAT_PK rebuild tablespace statdata;
alter index STATS$OSSSTATNAME_PK rebuild tablespace statdata;
alter index STATS$RULE_SET_PK rebuild tablespace statdata;
alter index STATS$BUFFERED_SUBSCRIBERS_PK rebuild tablespace statdata;
alter index STATS$BUFFERED_QUEUES_PK rebuild tablespace statdata;
alter index STATS$PROPAGATION_RECEIVER_PK rebuild tablespace statdata;
alter index STATS$PROPAGATION_SENDER_PK rebuild tablespace statdata;
alter index STATS$STREAMS_APPLY_SUM_PK rebuild tablespace statdata;
alter index STATS$STREAMS_CAPTURE_PK rebuild tablespace statdata;
alter index STATS$SESS_TIME_MODEL_PK rebuild tablespace statdata;
alter index STATS$SYS_TIME_MODEL_PK rebuild tablespace statdata;
alter index STATS$TIME_MODEL_STATNAME_PK rebuild tablespace statdata;
alter index STATS$EVENT_HISTOGRAM_PK rebuild tablespace statdata;
alter index STATS$FILE_HISTOGRAM_PK rebuild tablespace statdata;
alter index STATS$THREAD_PK rebuild tablespace statdata;
alter index STATS$JAVA_POOL_ADVICE_PK rebuild tablespace statdata;
alter index STATS$PGA_TARGET_ADVICE_PK rebuild tablespace statdata;
alter index STATS$SQL_WORKAREA_HIST_PK rebuild tablespace statdata;
alter index STATS$SHARED_POOL_ADVICE_PK rebuild tablespace statdata;
alter index STATS$STATSPACK_PARAMETER_PK rebuild tablespace statdata;
alter index STATS$INSTANCE_RECOVERY_PK rebuild tablespace statdata;
alter index STATS$PARAMETER_PK rebuild tablespace statdata;
alter index STATS$SQL_PGASTAT_PK rebuild tablespace statdata;
alter index STATS$SEG_STAT_OBJ_PK rebuild tablespace statdata;
alter index STATS$SEG_STAT_PK rebuild tablespace statdata;
alter index STATS$SQL_PLAN_PK rebuild tablespace statdata;
alter index STATS$SQL_PLAN_USAGE_PK rebuild tablespace statdata;
alter index STATS$SQL_PLAN_USAGE_HV rebuild tablespace statdata;
alter index STATS$UNDOSTAT_PK rebuild tablespace statdata;
alter index STATS$INST_CACHE_TRANSFER_PK rebuild tablespace statdata;
alter index STATS$CURRENT_BLOCK_SERVER_PK rebuild tablespace statdata;
alter index STATS$CR_BLOCK_SERVER_PK rebuild tablespace statdata;
alter index STATS$DLM_MISC_PK rebuild tablespace statdata;
alter index STATS$RESOURCE_LIMIT_PK rebuild tablespace statdata;
alter index STATS$SQL_STATISTICS_PK rebuild tablespace statdata;
alter index STATS$SQLTEXT_PK rebuild tablespace statdata;
alter index STATS$SQL_SUMMARY_PK rebuild tablespace statdata;
alter index STATS$ENQUEUE_STATISTICS_PK rebuild tablespace statdata;
alter index STATS$WAITSTAT_PK rebuild tablespace statdata;
alter index STATS$SESSION_EVENT_PK rebuild tablespace statdata;
alter index STATS$SYSTEM_EVENT_PK rebuild tablespace statdata;
alter index STATS$SESSTAT_PK rebuild tablespace statdata;
alter index STATS$SYSSTAT_PK rebuild tablespace statdata;
alter index STATS$SGASTAT_U rebuild tablespace statdata;
alter index STATS$SGA_PK rebuild tablespace statdata;
alter index STATS$ROWCACHE_SUMMARY_PK rebuild tablespace statdata;
alter index STATS$ROLLSTAT_PK rebuild tablespace statdata;
alter index STATS$BUFFER_POOL_STATS_PK rebuild tablespace statdata;
alter index STATS$LIBRARYCACHE_PK rebuild tablespace statdata;
alter index STATS$LATCH_MISSES_SUMMARY_PK rebuild tablespace statdata;
alter index STATS$LATCH_PARENT_PK rebuild tablespace statdata;
alter index STATS$LATCH_CHILDREN_PK rebuild tablespace statdata;
alter index STATS$LATCH_PK rebuild tablespace statdata;
alter index STATS$TEMPSTATXS_PK rebuild tablespace statdata;
alter index STATS$FILESTATXS_PK rebuild tablespace statdata;
alter index STATS$DB_CACHE_ADVICE_PK rebuild tablespace statdata;
alter index STATS$SNAPSHOT_PK rebuild tablespace statdata;
alter index STATS$LEVEL_DESCRIPTION_PK rebuild tablespace statdata;
alter index STATS$DATABASE_INSTANCE_PK rebuild tablespace statdata;
&lt;/span&gt;&lt;/pre&gt;

Generating such scripts is also a fairly simple task -- let SQL write your SQL for you [remember to connect as PERFSTAT before you run such scripts]:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;select 'alter table '||table_name||' move tablespace &amp;lt;tablespacename&amp;gt;;'
from user_tables
where tablespace_name is not null;
&lt;/span&gt;&lt;/pre&gt;

and

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;select 'alter index '||index_name||' rebuild tablespace &amp;lt;tablespacename&amp;gt;;'
from user_indexes;
&lt;/span&gt;&lt;/pre&gt;

Spool the output from each to files and run the resulting scripts; make certain you have enough space in the destination tablespace for the objects else you won't move all of your tables/indexes and you'll need to generate new scripts to finish the tasks.  Presuming all goes well the tables and indexes will be relocated to the desired tablespace and Statspack will remain in working order.&lt;br /&gt;&lt;br /&gt;

So, it isn't really a problem to move the Statspack tables to another location, as long as you're careful and plan ahead.&lt;br /&gt;&lt;br /&gt;

And this move doesn't require a U-Haul.&lt;br /&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=DMumPqu1Znw:HTGqdQ6np9k:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=DMumPqu1Znw:HTGqdQ6np9k:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://oratips-ddf.blogspot.com/feeds/6908813183914363739/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=6908813183914363739" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/6908813183914363739?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/6908813183914363739?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2008/12/its-moving-day.html" title="It's Moving Day" /><author><name>David Fitzjarrell</name><uri>https://plus.google.com/102215427046411604711</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-y2hTTVpTe4o/AAAAAAAAAAI/AAAAAAAAAGw/KabdyVeaX9M/s512-c/photo.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DEUGRHg_eip7ImA9WxRaE0g.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-515814518185893067</id><published>2008-12-10T19:55:00.004Z</published><updated>2008-12-15T15:50:25.642Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-12-15T15:50:25.642Z</app:edited><title>We Have Reservations</title><content type="html">A question was posed to google.com recently which stated:&lt;br /&gt;&lt;br /&gt;

"can we have a row named final in oracle table"&lt;br /&gt;&lt;br /&gt;

To answer that (presuming it was column, and not row, intended in the question) one must make a trip to V$RESERVED_WORDS:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; desc v$reserved_words
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 KEYWORD                                            VARCHAR2(30)
 LENGTH                                             NUMBER
 RESERVED                                           VARCHAR2(1)
 RES_TYPE                                           VARCHAR2(1)
 RES_ATTR                                           VARCHAR2(1)
 RES_SEMI                                           VARCHAR2(1)
 DUPLICATE                                          VARCHAR2(1)
 
SQL&gt;&lt;/span&gt;&lt;/pre&gt;

The two columns of interest are KEYWORD and RESERVED.  Let's look for 'FINAL' in the view and see whether or not it's reserved:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; select keyword, reserved
  2  from v$reserved_words
  3  where keyword like 'F%'
  4  order by 1;
 
KEYWORD                        R
------------------------------ -
FACT                           N
FAILED                         N
FAILED_LOGIN_ATTEMPTS          N
FAILGROUP                      N
FALSE                          N
FAST                           N
FBTSCAN                        N
FIC_CIV                        N
FIC_PIV                        N
FILE                           N
FILTER                         N
 
KEYWORD                        R
------------------------------ -
&lt;span style="color:#990000;"&gt;FINAL                          N&lt;/span&gt;
FINE                           N
FINISH                         N
FIRST                          N
FIRST_ROWS                     N
FLAGGER                        N
FLASHBACK                      N
FLOAT                          Y
FLOB                           N
FLUSH                          N
FOLLOWING                      N
 
KEYWORD                        R
------------------------------ -
FOR                            Y
FORCE                          N
FORCE_XML_QUERY_REWRITE        N
FOREIGN                        N
FREELIST                       N
FREELISTS                      N
FREEPOOLS                      N
FRESH                          N
FROM                           Y
FULL                           N
FUNCTION                       N
 
KEYWORD                        R
------------------------------ -
FUNCTIONS                      N
 
34 rows selected.
 
SQL&gt;&lt;/span&gt;&lt;/pre&gt;

It's in the list of reserved words, but it's not actually reserved, meaning you CAN use it if you REALLY REALLY want to, but it's recommended that you don't.  I wouldn't use it.  But, that's the way I think.&lt;br /&gt;&lt;br /&gt;

What other words shouldn't you use for column names/table names/view names/constraint names/sequence names/type names/...?  Simply look at the list provided in, yes, V$RESERVED_WORDS.  There are 1142 of them in 10gR2, 1733 in 11gR1; they're listed for good reason, in my opinion, and the list should be heeded.  And, yes, many of the words are not actually reserved (as indicated by the N in RESERVED) but it's still a really good idea to &lt;em&gt;&lt;span style="color:#990000;"&gt;not&lt;/span&gt;&lt;/em&gt; use them as, some day, Oracle may decide to actually reserve them and then your scripts won't run:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; create table from(
  2  final varchar2(400)
  3  );
create table from(
             *
ERROR at line 1:
ORA-00903: invalid table name

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

And that would be a shame.&lt;br /&gt;&lt;br /&gt;

Reservations, anyone?&lt;br /&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=TXb6sokEPyM:YOZj3gHSFto:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=TXb6sokEPyM:YOZj3gHSFto:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://oratips-ddf.blogspot.com/feeds/515814518185893067/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=515814518185893067" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/515814518185893067?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/515814518185893067?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2008/12/we-have-reservations.html" title="We Have Reservations" /><author><name>David Fitzjarrell</name><uri>https://plus.google.com/102215427046411604711</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-y2hTTVpTe4o/AAAAAAAAAAI/AAAAAAAAAGw/KabdyVeaX9M/s512-c/photo.jpg" /></author><thr:total>2</thr:total></entry><entry gd:etag="W/&quot;CUIFRnw9eyp7ImA9WhVTFUk.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-3512114719242110516</id><published>2008-11-19T15:32:00.013Z</published><updated>2012-02-29T19:25:17.263Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-02-29T19:25:17.263Z</app:edited><title>'Magical' Indexes</title><content type="html">In a forum I frequent the following question was presented (I have reposted the question in its entirety here):&lt;br /&gt;
&lt;br /&gt;
&lt;span style="color:#3333ff;"&gt;Suppose I have a table structure like T1(C1,C2,C3,C4....). &lt;br /&gt;
I  creates a index indx1 on (C1,C2,C3). I issue three select &lt;br /&gt;
statements like : &lt;br /&gt;
&lt;br /&gt;
1.select * from T1 where C1=&amp;lt;&amp;gt; &lt;br /&gt;
2.select * from T1 where C2=&amp;lt;&amp;gt; &lt;br /&gt;
3.select * from T1 where C3=&amp;lt;&amp;gt; &lt;br /&gt;
4.select * from T1 where C1= &amp;lt;&amp;gt; and  C2= &amp;lt;&amp;gt; and C3 =&amp;lt;&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
How the optimizer will use the index for each statements. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Regards, &lt;br /&gt;
Sanjoy &lt;br /&gt;
&lt;br /&gt;
&lt;/span&gt;  Of course the only way to begin to answer that, since the original poster did not provide any DDL or sample data, is to create an example and execute it (we'll ignore the fact that his &lt;span style="color:#990000;"&gt;&lt;em&gt;three&lt;/em&gt;&lt;/span&gt; questions are numbered 1 through 4).  I've posted the results below, this running on an Oracle 11.1.0.6 instance; the statistics reported by autotrace have been removed as they provided no additional value to the example.  Notice how the plans change as the data 'topography' changes, illustrating that the original question was ambiguous, at best:  &lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt;
SQL&gt; --
SQL&gt; -- Suppose I have a table structure like T1(C1,C2,C3,C4....).
SQL&gt; -- I  creates a index indx1 on (C1,C2,C3). I issue three select
SQL&gt; -- statements like :
SQL&gt; --
SQL&gt; -- 1.select * from T1 where C1=&lt;&gt;
SQL&gt; -- 2.select * from T1 where C2=&lt;&gt;
SQL&gt; -- 3.select * from T1 where C3=&lt;&gt;
SQL&gt; -- 4.select * from T1 where C1= &lt;&gt; and  C2= &lt;&gt; and C3 =&lt;&gt;
SQL&gt; --
SQL&gt; --
SQL&gt; -- How the optimizer will use the index for each statements.
SQL&gt; --
SQL&gt; --
SQL&gt; -- Regards,
SQL&gt; -- Sanjoy
SQL&gt; --
SQL&gt;
SQL&gt; --
SQL&gt; -- Create the table in question
SQL&gt; --
SQL&gt; create table t1(
  2        c1 number,
  3        c2 varchar2(20),
  4        c3 date,
  5        c4 varchar2(10),
  6        c5 number,
  7        c6 number
  8  );

Table created.

SQL&gt;
SQL&gt; --
SQL&gt; -- Create the index specified
SQL&gt; --
SQL&gt; create index indx1
  2  on t1(c1,c2,c3);

Index created.

SQL&gt;
SQL&gt; --
SQL&gt; -- Load test data
SQL&gt; --
SQL&gt;
SQL&gt; --
SQL&gt; -- Data with unique C1, C2 and C3 values
SQL&gt; --
SQL&gt; begin
  2        for i in 1..10101 loop
  3         insert into t1
  4         values (i, 'Testing record '||i, trunc(sysdate+i), 'Filler', mod(i, 734), mod(i, 963));
  5        end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

SQL&gt;
SQL&gt; commit;

Commit complete.

SQL&gt;
SQL&gt; --
SQL&gt; -- 'Standard' statistics
SQL&gt; --
SQL&gt; exec dbms_stats.gather_schema_stats(ownname=&gt;'BING');

PL/SQL procedure successfully completed.

SQL&gt;
SQL&gt; set autotrace on linesize 132
SQL&gt;
SQL&gt; select * From t1 where c1 =433;

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
       433 Testing record 433   26-JAN-10 Filler            433        433


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    46 |     3   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    46 |     3   (0)| 00:00:01 |
&lt;span style="color:#990000;"&gt;|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     2   (0)| 00:00:01 |&lt;/span&gt;
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=433)

SQL&gt;
SQL&gt; select * From t1 where c2 = 'Testing record 7748';

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
      7748 Testing record 7748  05-FEB-30 Filler            408         44


Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |    46 |    22   (0)| 00:00:01 |
&lt;span style="color:#990000;"&gt;|*  1 |  TABLE ACCESS FULL| T1   |     1 |    46 |    22   (0)| 00:00:01 |&lt;/span&gt;
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("C2"='Testing record 7748')

SQL&gt;
SQL&gt; select * from t1 where c3 = trunc(sysdate+9);

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
         9 Testing record 9     28-NOV-08 Filler              9          9


Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |    46 |    22   (0)| 00:00:01 |
&lt;span style="color:#990000;"&gt;|*  1 |  TABLE ACCESS FULL| T1   |     1 |    46 |    22   (0)| 00:00:01 |&lt;/span&gt;
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("C3"=TRUNC(SYSDATE@!+9))

SQL&gt;
SQL&gt; select * From t1 where c1 = 9993 and c2 = 'Testing record 9993' and c3 = trunc(sysdate+9993);

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
      9993 Testing record 9993  30-MAR-36 Filler            451        363


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    46 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    46 |     2   (0)| 00:00:01 |
&lt;span style="color:#990000;"&gt;|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     1   (0)| 00:00:01 |&lt;/span&gt;
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=9993 AND "C2"='Testing record 9993' AND
              "C3"=TRUNC(SYSDATE@!+9993))

SQL&gt;
SQL&gt; set autotrace off
SQL&gt;
SQL&gt; --
SQL&gt; -- Statistics with auto-sized histograms on indexed columns
SQL&gt; --
SQL&gt; exec dbms_stats.gather_schema_stats(ownname=&gt;'BING', method_opt =&gt; 'FOR ALL INDEXED COLUMNS SIZE AUTO');

PL/SQL procedure successfully completed.

SQL&gt;
SQL&gt; set autotrace on linesize 132
SQL&gt;
SQL&gt; select * From t1 where c1 =433;

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
       433 Testing record 433   26-JAN-10 Filler            433        433


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    46 |     3   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    46 |     3   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=433)

SQL&gt;
SQL&gt; select * From t1 where c2 = 'Testing record 7748';

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
      7748 Testing record 7748  05-FEB-30 Filler            408         44


Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |    46 |    22   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T1   |     1 |    46 |    22   (0)| 00:00:01 |
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("C2"='Testing record 7748')

SQL&gt;
SQL&gt; select * from t1 where c3 = trunc(sysdate+9);

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
         9 Testing record 9     28-NOV-08 Filler              9          9


Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |    46 |    22   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T1   |     1 |    46 |    22   (0)| 00:00:01 |
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("C3"=TRUNC(SYSDATE@!+9))

SQL&gt;
SQL&gt; select * From t1 where c1 = 9993 and c2 = 'Testing record 9993' and c3 = trunc(sysdate+9993);

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
      9993 Testing record 9993  30-MAR-36 Filler            451        363


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    46 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    46 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=9993 AND "C2"='Testing record 9993' AND
              "C3"=TRUNC(SYSDATE@!+9993))

SQL&gt;
SQL&gt; set autotrace off
SQL&gt;
SQL&gt; truncate table t1;

Table truncated.

SQL&gt;
SQL&gt; --
SQL&gt; -- Data with unique C2 and C3 values, and a reasonably selective C1
SQL&gt; --
SQL&gt; begin
  2        for i in 1..10101 loop
  3         insert into t1
  4         values (mod(i, 43), 'Testing record '||i, trunc(sysdate+i), 'Filler', mod(i, 734), mod(i, 963));
  5        end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

SQL&gt;
SQL&gt; commit;

Commit complete.

SQL&gt;
SQL&gt; --
SQL&gt; -- 'Standard' statistics
SQL&gt; --
SQL&gt; exec dbms_stats.gather_schema_stats(ownname=&gt;'BING');

PL/SQL procedure successfully completed.

SQL&gt;
SQL&gt; set autotrace on linesize 132
SQL&gt;
SQL&gt; select * From t1 where c1 =433;

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    45 |     3   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    45 |     3   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=433)

SQL&gt;
SQL&gt; select * From t1 where c2 = 'Testing record 7748';

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
         8 Testing record 7748  05-FEB-30 Filler            408         44


Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |    45 |    22   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T1   |     1 |    45 |    22   (0)| 00:00:01 |
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("C2"='Testing record 7748')

SQL&gt;
SQL&gt; select * from t1 where c3 = trunc(sysdate+9);

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
         9 Testing record 9     28-NOV-08 Filler              9          9


Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |    45 |    22   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T1   |     1 |    45 |    22   (0)| 00:00:01 |
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("C3"=TRUNC(SYSDATE@!+9))

SQL&gt;
SQL&gt; select * From t1 where c1 = 9993 and c2 = 'Testing record 9993' and c3 = trunc(sysdate+9993);

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    45 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    45 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=9993 AND "C2"='Testing record 9993' AND
              "C3"=TRUNC(SYSDATE@!+9993))

SQL&gt;
SQL&gt; set autotrace off
SQL&gt;
SQL&gt; --
SQL&gt; -- Statistics with auto-sized histograms on indexed columns
SQL&gt; --
SQL&gt; exec dbms_stats.gather_schema_stats(ownname=&gt;'BING', method_opt =&gt; 'FOR ALL INDEXED COLUMNS SIZE AUTO');

PL/SQL procedure successfully completed.

SQL&gt;
SQL&gt; set autotrace on linesize 132
SQL&gt;
SQL&gt; select * From t1 where c1 =433;

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    45 |     3   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    45 |     3   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=433)

SQL&gt;
SQL&gt; select * From t1 where c2 = 'Testing record 7748';

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
         8 Testing record 7748  05-FEB-30 Filler            408         44


Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |    45 |    22   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T1   |     1 |    45 |    22   (0)| 00:00:01 |
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("C2"='Testing record 7748')

SQL&gt;
SQL&gt; select * from t1 where c3 = trunc(sysdate+9);

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
         9 Testing record 9     28-NOV-08 Filler              9          9


Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |    45 |    22   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T1   |     1 |    45 |    22   (0)| 00:00:01 |
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("C3"=TRUNC(SYSDATE@!+9))

SQL&gt;
SQL&gt; select * From t1 where c1 = 9993 and c2 = 'Testing record 9993' and c3 = trunc(sysdate+9993);

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    45 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    45 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=9993 AND "C2"='Testing record 9993' AND
              "C3"=TRUNC(SYSDATE@!+9993))

SQL&gt;
SQL&gt; set autotrace off
SQL&gt;
SQL&gt; truncate table t1;

Table truncated.

SQL&gt;
SQL&gt; --
SQL&gt; -- Data with unique C2 and C3 values, with a cycling C1 set of values
SQL&gt; --
SQL&gt; begin
  2        for i in 1..10101 loop
  3         insert into t1
  4         values (mod(i, 3), 'Testing record '||i, trunc(sysdate+i), 'Filler', mod(i, 734), mod(i, 963));
  5        end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

SQL&gt;
SQL&gt; commit;

Commit complete.

SQL&gt;
SQL&gt; --
SQL&gt; -- 'Standard' statistics
SQL&gt; --
SQL&gt; exec dbms_stats.gather_schema_stats(ownname=&gt;'BING');

PL/SQL procedure successfully completed.

SQL&gt;
SQL&gt; set autotrace on linesize 132
SQL&gt;
SQL&gt; select * From t1 where c1 =433;

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    45 |     3   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    45 |     3   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=433)

SQL&gt;
SQL&gt; select * From t1 where c2 = 'Testing record 7748';

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
         2 Testing record 7748  05-FEB-30 Filler            408         44


Execution Plan
----------------------------------------------------------
Plan hash value: 3325179317

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    45 |     5   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    45 |     5   (0)| 00:00:01 |
&lt;span style="color:#990000;"&gt;|*  2 |   INDEX SKIP SCAN           | INDX1 |     1 |       |     4   (0)| 00:00:01 |&lt;/span&gt;
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C2"='Testing record 7748')
       filter("C2"='Testing record 7748')

SQL&gt;
SQL&gt; select * from t1 where c3 = trunc(sysdate+9);

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
         0 Testing record 9     28-NOV-08 Filler              9          9


Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |    45 |    22   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T1   |     1 |    45 |    22   (0)| 00:00:01 |
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("C3"=TRUNC(SYSDATE@!+9))

SQL&gt;
SQL&gt; select * From t1 where c1 = 9993 and c2 = 'Testing record 9993' and c3 = trunc(sysdate+9993);

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    45 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    45 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=9993 AND "C2"='Testing record 9993' AND
              "C3"=TRUNC(SYSDATE@!+9993))

SQL&gt;
SQL&gt; set autotrace off
SQL&gt;
SQL&gt; --
SQL&gt; -- Statistics with auto-sized histograms on indexed columns
SQL&gt; --
SQL&gt; exec dbms_stats.gather_schema_stats(ownname=&gt;'BING', method_opt =&gt; 'FOR ALL INDEXED COLUMNS SIZE AUTO');

PL/SQL procedure successfully completed.

SQL&gt;
SQL&gt; set autotrace on linesize 132
SQL&gt;
SQL&gt; select * From t1 where c1 =433;

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    45 |     3   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    45 |     3   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=433)

SQL&gt;
SQL&gt; select * From t1 where c2 = 'Testing record 7748';

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
         2 Testing record 7748  05-FEB-30 Filler            408         44


Execution Plan
----------------------------------------------------------
Plan hash value: 3325179317

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    45 |     5   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    45 |     5   (0)| 00:00:01 |
|*  2 |   INDEX SKIP SCAN           | INDX1 |     1 |       |     4   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C2"='Testing record 7748')
       filter("C2"='Testing record 7748')

SQL&gt;
SQL&gt; select * from t1 where c3 = trunc(sysdate+9);

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
         0 Testing record 9     28-NOV-08 Filler              9          9


Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |    45 |    22   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T1   |     1 |    45 |    22   (0)| 00:00:01 |
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("C3"=TRUNC(SYSDATE@!+9))

SQL&gt;
SQL&gt; select * From t1 where c1 = 9993 and c2 = 'Testing record 9993' and c3 = trunc(sysdate+9993);

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    45 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    45 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=9993 AND "C2"='Testing record 9993' AND
              "C3"=TRUNC(SYSDATE@!+9993))

SQL&gt;
SQL&gt; set autotrace off
SQL&gt;
SQL&gt; truncate table t1;

Table truncated.

SQL&gt;
SQL&gt; --
SQL&gt; -- Data with cycling C1 and C2 values and unique C3 values
SQL&gt; --
SQL&gt; begin
  2        for i in 1..10101 loop
  3         insert into t1
  4         values (mod(i, 3), 'Testing record '||mod(i,3), trunc(sysdate+i), 'Filler', mod(i, 734), mod(i, 963));
  5        end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

SQL&gt;
SQL&gt; commit;

Commit complete.

SQL&gt;
SQL&gt; --
SQL&gt; -- 'Standard' statistics
SQL&gt; --
SQL&gt; exec dbms_stats.gather_schema_stats(ownname=&gt;'BING');

PL/SQL procedure successfully completed.

SQL&gt;
SQL&gt; set autotrace on linesize 132
SQL&gt;
SQL&gt; select * From t1 where c1 =433;

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    42 |     3   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    42 |     3   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=433)

SQL&gt;
SQL&gt; select * From t1 where c2 = 'Testing record 7748';

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 3325179317

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    42 |     5   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    42 |     5   (0)| 00:00:01 |
|*  2 |   INDEX SKIP SCAN           | INDX1 |     1 |       |     4   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C2"='Testing record 7748')
       filter("C2"='Testing record 7748')

SQL&gt;
SQL&gt; select * from t1 where c3 = trunc(sysdate+9);

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
         0 Testing record 0     28-NOV-08 Filler              9          9


Execution Plan
----------------------------------------------------------
Plan hash value: 3325179317

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    42 |     5   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    42 |     5   (0)| 00:00:01 |
&lt;span style="color:#990000;"&gt;|*  2 |   INDEX SKIP SCAN           | INDX1 |     1 |       |     4   (0)| 00:00:01 |&lt;/span&gt;
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C3"=TRUNC(SYSDATE@!+9))
       filter("C3"=TRUNC(SYSDATE@!+9))

SQL&gt;
SQL&gt; select * From t1 where c1 = 9993 and c2 = 'Testing record 9993' and c3 = trunc(sysdate+9993);

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    42 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    42 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=9993 AND "C2"='Testing record 9993' AND
              "C3"=TRUNC(SYSDATE@!+9993))

SQL&gt;
SQL&gt; set autotrace off
SQL&gt;
SQL&gt; --
SQL&gt; -- Statistics with auto-sized histograms on indexed columns
SQL&gt; --
SQL&gt; exec dbms_stats.gather_schema_stats(ownname=&gt;'BING', method_opt =&gt; 'FOR ALL INDEXED COLUMNS SIZE AUTO');

PL/SQL procedure successfully completed.

SQL&gt;
SQL&gt; set autotrace on linesize 132
SQL&gt;
SQL&gt; select * From t1 where c1 =433;

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    42 |     3   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    42 |     3   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=433)

SQL&gt;
SQL&gt; select * From t1 where c2 = 'Testing record 7748';

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 3325179317

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    42 |     5   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    42 |     5   (0)| 00:00:01 |
|*  2 |   INDEX SKIP SCAN           | INDX1 |     1 |       |     4   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C2"='Testing record 7748')
       filter("C2"='Testing record 7748')

SQL&gt;
SQL&gt; select * from t1 where c3 = trunc(sysdate+9);

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
         0 Testing record 0     28-NOV-08 Filler              9          9


Execution Plan
----------------------------------------------------------
Plan hash value: 3325179317

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    42 |     5   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    42 |     5   (0)| 00:00:01 |
|*  2 |   INDEX SKIP SCAN           | INDX1 |     1 |       |     4   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C3"=TRUNC(SYSDATE@!+9))
       filter("C3"=TRUNC(SYSDATE@!+9))

SQL&gt;
SQL&gt; select * From t1 where c1 = 9993 and c2 = 'Testing record 9993' and c3 = trunc(sysdate+9993);

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    42 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    42 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=9993 AND "C2"='Testing record 9993' AND
              "C3"=TRUNC(SYSDATE@!+9993))

SQL&gt;
SQL&gt; set autotrace off
SQL&gt;&lt;/span&gt;&lt;/pre&gt;Note that as the selectivity of the leading columns (C1 and C2) decreases the plans change from TABLE ACCESS FULL to INDEX SKIP SCAN when column C1 is not included in the where clause.  Note also that the queries did NOT change and that histograms made no difference in the plans.&lt;br /&gt;
&lt;br /&gt;
This changes for 11.2.0.2:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt;
SQL&gt; 
SQL&gt; --
SQL&gt; -- I  creates a index indx1 on (C1,C2,C3). I issue three select
SQL&gt; -- statements like :
SQL&gt; --
SQL&gt; -- 1.select * from T1 where C1=&lt;&gt;
SQL&gt; -- 2.select * from T1 where C2=&lt;&gt;
SQL&gt; -- 3.select * from T1 where C3=&lt;&gt;
SQL&gt; -- 4.select * from T1 where C1= &lt;&gt; and  C2= &lt;&gt; and C3 =&lt;&gt;
SQL&gt; --
SQL&gt; --
SQL&gt; --
SQL&gt; --
SQL&gt; -- Regards,
SQL&gt; -- Sanjoy
SQL&gt; --
SQL&gt; 
SQL&gt; --
SQL&gt; -- Create the table in question
SQL&gt; --
SQL&gt; create table t1(
  2      c1 number,
  3      c2 varchar2(20),
  4      c3 date,
  5      c4 varchar2(10),
  6      c5 number,
  7      c6 number
  8  );

Table created.

SQL&gt; 
SQL&gt; 
SQL&gt; --
SQL&gt; -- Create the index specified
SQL&gt; --
SQL&gt; create index indx1
  2  on t1(c1,c2,c3);

Index created.

SQL&gt; 
SQL&gt; 
SQL&gt; --
SQL&gt; -- Load test data
SQL&gt; --
SQL&gt; 
SQL&gt; --
SQL&gt; -- Data with unique C1, C2 and C3 values
SQL&gt; --
SQL&gt; begin
  2      for i in 1..10101 loop
  3       insert into t1
  4       values (i, 'Testing record '||i, trunc(sysdate+i), 'Filler', mod(i, 734), mod(i, 963));
  5      end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; 
SQL&gt; commit;

Commit complete.

SQL&gt; 
SQL&gt; 
SQL&gt; --
SQL&gt; -- 'Standard' statistics
SQL&gt; --
SQL&gt; exec dbms_stats.gather_schema_stats(ownname=&gt;'BING');

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; 
SQL&gt; 
SQL&gt; set autotrace on linesize 132
SQL&gt; 
SQL&gt; select * From t1 where c1 =433;

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
       433 Testing record 433   07-MAY-13 Filler            433        433


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    47 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    47 |     1   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=433)


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
          4  consistent gets
          0  physical reads
          0  redo size
        739  bytes sent via SQL*Net to client
        420  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL&gt; 
SQL&gt; select * From t1 where c2 = 'Testing record 7748';

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
      7748 Testing record 7748  17-MAY-33 Filler            408         44


Execution Plan
----------------------------------------------------------
Plan hash value: 3325179317

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    47 |    11   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    47 |    11   (0)| 00:00:01 |
|*  2 |   INDEX SKIP SCAN           | INDX1 |     1 |       |    11   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C2"='Testing record 7748')
       filter("C2"='Testing record 7748')


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
         59  consistent gets
          0  physical reads
          0  redo size
        739  bytes sent via SQL*Net to client
        420  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL&gt; 
SQL&gt; select * from t1 where c3 = trunc(sysdate+9);

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
         9 Testing record 9     09-MAR-12 Filler              9          9


Execution Plan
----------------------------------------------------------
Plan hash value: 3325179317

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    47 |    11   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    47 |    11   (0)| 00:00:01 |
|*  2 |   INDEX SKIP SCAN           | INDX1 |     1 |       |    11   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C3"=TRUNC(SYSDATE@!+9))
       filter("C3"=TRUNC(SYSDATE@!+9))


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
         59  consistent gets
          0  physical reads
          0  redo size
        734  bytes sent via SQL*Net to client
        420  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL&gt; 
SQL&gt; select * From t1 where c1 = 9993 and c2 = 'Testing record 9993' and c3 = trunc(sysdate+9993);

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
      9993 Testing record 9993  10-JUL-39 Filler            451        363


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    47 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    47 |     1   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=9993 AND "C2"='Testing record 9993' AND
              "C3"=TRUNC(SYSDATE@!+9993))


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
          4  consistent gets
          0  physical reads
          0  redo size
        740  bytes sent via SQL*Net to client
        420  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL&gt; 
SQL&gt; set autotrace off
SQL&gt; 
SQL&gt; --
SQL&gt; -- Statistics with auto-sized histograms on indexed columns
SQL&gt; --
SQL&gt; exec dbms_stats.gather_schema_stats(ownname=&gt;'BING', method_opt =&gt; 'FOR ALL INDEXED COLUMNS SIZE AUTO');

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; set autotrace on linesize 132
SQL&gt; 
SQL&gt; select * From t1 where c1 =433;

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
       433 Testing record 433   07-MAY-13 Filler            433        433


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    47 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    47 |     1   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=433)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          4  consistent gets
          0  physical reads
          0  redo size
        739  bytes sent via SQL*Net to client
        420  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL&gt; 
SQL&gt; select * From t1 where c2 = 'Testing record 7748';

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
      7748 Testing record 7748  17-MAY-33 Filler            408         44


Execution Plan
----------------------------------------------------------
Plan hash value: 3325179317

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    47 |    11   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    47 |    11   (0)| 00:00:01 |
|*  2 |   INDEX SKIP SCAN           | INDX1 |     1 |       |    11   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C2"='Testing record 7748')
       filter("C2"='Testing record 7748')


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         59  consistent gets
          0  physical reads
          0  redo size
        739  bytes sent via SQL*Net to client
        420  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL&gt; 
SQL&gt; select * from t1 where c3 = trunc(sysdate+9);

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
         9 Testing record 9     09-MAR-12 Filler              9          9


Execution Plan
----------------------------------------------------------
Plan hash value: 3325179317

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    47 |    11   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    47 |    11   (0)| 00:00:01 |
|*  2 |   INDEX SKIP SCAN           | INDX1 |     1 |       |    11   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C3"=TRUNC(SYSDATE@!+9))
       filter("C3"=TRUNC(SYSDATE@!+9))


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         59  consistent gets
          0  physical reads
          0  redo size
        734  bytes sent via SQL*Net to client
        420  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL&gt; 
SQL&gt; select * From t1 where c1 = 9993 and c2 = 'Testing record 9993' and c3 = trunc(sysdate+9993);

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
      9993 Testing record 9993  10-JUL-39 Filler            451        363


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    47 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    47 |     1   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=9993 AND "C2"='Testing record 9993' AND
              "C3"=TRUNC(SYSDATE@!+9993))


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          4  consistent gets
          0  physical reads
          0  redo size
        740  bytes sent via SQL*Net to client
        420  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL&gt; 
SQL&gt; set autotrace off
SQL&gt; 
SQL&gt; truncate table t1;

Table truncated.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Data with unique C2 and C3 values, and a reasonably selective C1
SQL&gt; --
SQL&gt; begin
  2      for i in 1..10101 loop
  3       insert into t1
  4       values (mod(i, 43), 'Testing record '||i, trunc(sysdate+i), 'Filler', mod(i, 734), mod(i, 963));
  5      end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; commit;

Commit complete.

SQL&gt; 
SQL&gt; --
SQL&gt; -- 'Standard' statistics
SQL&gt; --
SQL&gt; exec dbms_stats.gather_schema_stats(ownname=&gt;'BING');

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; set autotrace on linesize 132
SQL&gt; 
SQL&gt; select * From t1 where c1 =433;

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    46 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    46 |     1   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=433)


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
          2  consistent gets
          0  physical reads
          0  redo size
        562  bytes sent via SQL*Net to client
        409  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

SQL&gt; 
SQL&gt; select * From t1 where c2 = 'Testing record 7748';

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
         8 Testing record 7748  17-MAY-33 Filler            408         44


Execution Plan
----------------------------------------------------------
Plan hash value: 3325179317

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    46 |     9   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    46 |     9   (0)| 00:00:01 |
|*  2 |   INDEX SKIP SCAN           | INDX1 |     1 |       |     9   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C2"='Testing record 7748')
       filter("C2"='Testing record 7748')


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
         54  consistent gets
          0  physical reads
          0  redo size
        738  bytes sent via SQL*Net to client
        420  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL&gt; 
SQL&gt; select * from t1 where c3 = trunc(sysdate+9);

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
         9 Testing record 9     09-MAR-12 Filler              9          9


Execution Plan
----------------------------------------------------------
Plan hash value: 3325179317

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    46 |    16   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    46 |    16   (0)| 00:00:01 |
|*  2 |   INDEX SKIP SCAN           | INDX1 |     1 |       |    16   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C3"=TRUNC(SYSDATE@!+9))
       filter("C3"=TRUNC(SYSDATE@!+9))


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
         84  consistent gets
          0  physical reads
          0  redo size
        734  bytes sent via SQL*Net to client
        420  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL&gt; 
SQL&gt; select * From t1 where c1 = 9993 and c2 = 'Testing record 9993' and c3 = trunc(sysdate+9993);

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    46 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    46 |     1   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=9993 AND "C2"='Testing record 9993' AND
              "C3"=TRUNC(SYSDATE@!+9993))


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
          2  consistent gets
          0  physical reads
          0  redo size
        562  bytes sent via SQL*Net to client
        409  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

SQL&gt; 
SQL&gt; set autotrace off
SQL&gt; 
SQL&gt; --
SQL&gt; -- Statistics with auto-sized histograms on indexed columns
SQL&gt; --
SQL&gt; exec dbms_stats.gather_schema_stats(ownname=&gt;'BING', method_opt =&gt; 'FOR ALL INDEXED COLUMNS SIZE AUTO');

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; set autotrace on linesize 132
SQL&gt; 
SQL&gt; select * From t1 where c1 =433;

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    46 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    46 |     1   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=433)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          2  consistent gets
          0  physical reads
          0  redo size
        562  bytes sent via SQL*Net to client
        409  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

SQL&gt; 
SQL&gt; select * From t1 where c2 = 'Testing record 7748';

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
         8 Testing record 7748  17-MAY-33 Filler            408         44


Execution Plan
----------------------------------------------------------
Plan hash value: 3325179317

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    46 |     9   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    46 |     9   (0)| 00:00:01 |
|*  2 |   INDEX SKIP SCAN           | INDX1 |     1 |       |     9   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C2"='Testing record 7748')
       filter("C2"='Testing record 7748')


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         54  consistent gets
          0  physical reads
          0  redo size
        738  bytes sent via SQL*Net to client
        420  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL&gt; 
SQL&gt; select * from t1 where c3 = trunc(sysdate+9);

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
         9 Testing record 9     09-MAR-12 Filler              9          9


Execution Plan
----------------------------------------------------------
Plan hash value: 3325179317

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    46 |    16   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    46 |    16   (0)| 00:00:01 |
|*  2 |   INDEX SKIP SCAN           | INDX1 |     1 |       |    16   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C3"=TRUNC(SYSDATE@!+9))
       filter("C3"=TRUNC(SYSDATE@!+9))


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         84  consistent gets
          0  physical reads
          0  redo size
        734  bytes sent via SQL*Net to client
        420  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL&gt; 
SQL&gt; select * From t1 where c1 = 9993 and c2 = 'Testing record 9993' and c3 = trunc(sysdate+9993);

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    46 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    46 |     1   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=9993 AND "C2"='Testing record 9993' AND
              "C3"=TRUNC(SYSDATE@!+9993))


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          2  consistent gets
          0  physical reads
          0  redo size
        562  bytes sent via SQL*Net to client
        409  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

SQL&gt; 
SQL&gt; set autotrace off
SQL&gt; 
SQL&gt; truncate table t1;

Table truncated.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Data with unique C2 and C3 values, with a cycling C1 set of values
SQL&gt; --
SQL&gt; begin
  2      for i in 1..10101 loop
  3       insert into t1
  4       values (mod(i, 3), 'Testing record '||i, trunc(sysdate+i), 'Filler', mod(i, 734), mod(i, 963));
  5      end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; commit;

Commit complete.

SQL&gt; 
SQL&gt; --
SQL&gt; -- 'Standard' statistics
SQL&gt; --
SQL&gt; exec dbms_stats.gather_schema_stats(ownname=&gt;'BING');

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; set autotrace on linesize 132
SQL&gt; 
SQL&gt; select * From t1 where c1 =433;

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    46 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    46 |     1   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=433)


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
          2  consistent gets
          0  physical reads
          0  redo size
        562  bytes sent via SQL*Net to client
        409  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

SQL&gt; 
SQL&gt; select * From t1 where c2 = 'Testing record 7748';

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
         2 Testing record 7748  17-MAY-33 Filler            408         44


Execution Plan
----------------------------------------------------------
Plan hash value: 3325179317

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    46 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    46 |     1   (0)| 00:00:01 |
|*  2 |   INDEX SKIP SCAN           | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C2"='Testing record 7748')
       filter("C2"='Testing record 7748')


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
         11  consistent gets
          0  physical reads
          0  redo size
        738  bytes sent via SQL*Net to client
        420  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL&gt; 
SQL&gt; select * from t1 where c3 = trunc(sysdate+9);

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
         0 Testing record 9     09-MAR-12 Filler              9          9


Execution Plan
----------------------------------------------------------
Plan hash value: 3325179317

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    46 |    20   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    46 |    20   (0)| 00:00:01 |
|*  2 |   INDEX SKIP SCAN           | INDX1 |     1 |       |    19   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C3"=TRUNC(SYSDATE@!+9))
       filter("C3"=TRUNC(SYSDATE@!+9))


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
        100  consistent gets
          0  physical reads
          0  redo size
        733  bytes sent via SQL*Net to client
        420  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL&gt; 
SQL&gt; select * From t1 where c1 = 9993 and c2 = 'Testing record 9993' and c3 = trunc(sysdate+9993);

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    46 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    46 |     1   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=9993 AND "C2"='Testing record 9993' AND
              "C3"=TRUNC(SYSDATE@!+9993))


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
          2  consistent gets
          0  physical reads
          0  redo size
        562  bytes sent via SQL*Net to client
        409  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

SQL&gt; 
SQL&gt; set autotrace off
SQL&gt; 
SQL&gt; --
SQL&gt; -- Statistics with auto-sized histograms on indexed columns
SQL&gt; --
SQL&gt; exec dbms_stats.gather_schema_stats(ownname=&gt;'BING', method_opt =&gt; 'FOR ALL INDEXED COLUMNS SIZE AUTO');

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; set autotrace on linesize 132
SQL&gt; 
SQL&gt; select * From t1 where c1 =433;

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    46 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    46 |     1   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=433)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          2  consistent gets
          0  physical reads
          0  redo size
        562  bytes sent via SQL*Net to client
        409  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

SQL&gt; 
SQL&gt; select * From t1 where c2 = 'Testing record 7748';

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
         2 Testing record 7748  17-MAY-33 Filler            408         44


Execution Plan
----------------------------------------------------------
Plan hash value: 3325179317

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    46 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    46 |     1   (0)| 00:00:01 |
|*  2 |   INDEX SKIP SCAN           | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C2"='Testing record 7748')
       filter("C2"='Testing record 7748')


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         11  consistent gets
          0  physical reads
          0  redo size
        738  bytes sent via SQL*Net to client
        420  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL&gt; 
SQL&gt; select * from t1 where c3 = trunc(sysdate+9);

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
         0 Testing record 9     09-MAR-12 Filler              9          9


Execution Plan
----------------------------------------------------------
Plan hash value: 3325179317

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    46 |    20   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    46 |    20   (0)| 00:00:01 |
|*  2 |   INDEX SKIP SCAN           | INDX1 |     1 |       |    19   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C3"=TRUNC(SYSDATE@!+9))
       filter("C3"=TRUNC(SYSDATE@!+9))


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        100  consistent gets
          0  physical reads
          0  redo size
        733  bytes sent via SQL*Net to client
        420  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL&gt; 
SQL&gt; select * From t1 where c1 = 9993 and c2 = 'Testing record 9993' and c3 = trunc(sysdate+9993);

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    46 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    46 |     1   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=9993 AND "C2"='Testing record 9993' AND
              "C3"=TRUNC(SYSDATE@!+9993))


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          2  consistent gets
          0  physical reads
          0  redo size
        562  bytes sent via SQL*Net to client
        409  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

SQL&gt; 
SQL&gt; set autotrace off
SQL&gt; 
SQL&gt; truncate table t1;

Table truncated.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Data with cycling C1 and C2 values and unique C3 values
SQL&gt; --
SQL&gt; begin
  2      for i in 1..10101 loop
  3       insert into t1
  4       values (mod(i, 3), 'Testing record '||mod(i,3), trunc(sysdate+i), 'Filler', mod(i, 734), mod(i, 963));
  5      end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; commit;

Commit complete.

SQL&gt; 
SQL&gt; --
SQL&gt; -- 'Standard' statistics
SQL&gt; --
SQL&gt; exec dbms_stats.gather_schema_stats(ownname=&gt;'BING');

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; set autotrace on linesize 132
SQL&gt; 
SQL&gt; select * From t1 where c1 =433;

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    43 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    43 |     1   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=433)


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
          2  consistent gets
          0  physical reads
          0  redo size
        562  bytes sent via SQL*Net to client
        409  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

SQL&gt; 
SQL&gt; select * From t1 where c2 = 'Testing record 7748';

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 3325179317

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    43 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    43 |     1   (0)| 00:00:01 |
|*  2 |   INDEX SKIP SCAN           | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C2"='Testing record 7748')
       filter("C2"='Testing record 7748')


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
          5  consistent gets
          0  physical reads
          0  redo size
        562  bytes sent via SQL*Net to client
        409  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

SQL&gt; 
SQL&gt; select * from t1 where c3 = trunc(sysdate+9);

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
         0 Testing record 0     09-MAR-12 Filler              9          9


Execution Plan
----------------------------------------------------------
Plan hash value: 3325179317

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    43 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    43 |     1   (0)| 00:00:01 |
|*  2 |   INDEX SKIP SCAN           | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C3"=TRUNC(SYSDATE@!+9))
       filter("C3"=TRUNC(SYSDATE@!+9))


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
          8  consistent gets
          0  physical reads
          0  redo size
        733  bytes sent via SQL*Net to client
        420  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL&gt; 
SQL&gt; select * From t1 where c1 = 9993 and c2 = 'Testing record 9993' and c3 = trunc(sysdate+9993);

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    43 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    43 |     1   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=9993 AND "C2"='Testing record 9993' AND
              "C3"=TRUNC(SYSDATE@!+9993))


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
          2  consistent gets
          0  physical reads
          0  redo size
        562  bytes sent via SQL*Net to client
        409  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

SQL&gt; 
SQL&gt; set autotrace off
SQL&gt; 
SQL&gt; --
SQL&gt; -- Statistics with auto-sized histograms on indexed columns
SQL&gt; --
SQL&gt; exec dbms_stats.gather_schema_stats(ownname=&gt;'BING', method_opt =&gt; 'FOR ALL INDEXED COLUMNS SIZE AUTO');

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; set autotrace on linesize 132
SQL&gt; 
SQL&gt; select * From t1 where c1 =433;

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    43 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    43 |     1   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=433)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          2  consistent gets
          0  physical reads
          0  redo size
        562  bytes sent via SQL*Net to client
        409  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

SQL&gt; 
SQL&gt; select * From t1 where c2 = 'Testing record 7748';

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 3325179317

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    43 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    43 |     1   (0)| 00:00:01 |
|*  2 |   INDEX SKIP SCAN           | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C2"='Testing record 7748')
       filter("C2"='Testing record 7748')


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          5  consistent gets
          0  physical reads
          0  redo size
        562  bytes sent via SQL*Net to client
        409  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

SQL&gt; 
SQL&gt; select * from t1 where c3 = trunc(sysdate+9);

        C1 C2                   C3        C4                 C5         C6
---------- -------------------- --------- ---------- ---------- ----------
         0 Testing record 0     09-MAR-12 Filler              9          9


Execution Plan
----------------------------------------------------------
Plan hash value: 3325179317

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    43 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    43 |     1   (0)| 00:00:01 |
|*  2 |   INDEX SKIP SCAN           | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C3"=TRUNC(SYSDATE@!+9))
       filter("C3"=TRUNC(SYSDATE@!+9))


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          8  consistent gets
          0  physical reads
          0  redo size
        733  bytes sent via SQL*Net to client
        420  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL&gt; 
SQL&gt; select * From t1 where c1 = 9993 and c2 = 'Testing record 9993' and c3 = trunc(sysdate+9993);

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 552572096

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |    43 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1    |     1 |    43 |     1   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | INDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("C1"=9993 AND "C2"='Testing record 9993' AND
              "C3"=TRUNC(SYSDATE@!+9993))


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          2  consistent gets
          0  physical reads
          0  redo size
        562  bytes sent via SQL*Net to client
        409  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

SQL&gt; 
SQL&gt; set autotrace off
SQL&gt; 
SQL&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;
Notice that in the later release INDEX SKIP SCAN is chosen by the optimizer when any indexed column other than the leading column is used in the where clause.&lt;br /&gt;
&lt;br /&gt;
So how does Oracle treat a 'select * from t1 where c3 = trunc(sysdate+9)' query when the index is built on columns (c1,c2,c3)?  That depends entirely upon the data and how skewed (or not) it may be.  [It also depends upon the Oracle version in use, as releases after 8.1.7.4 implemented changes in the available query plans and how indexes could be used.  Again note the differences between how 11.1.0.6 and 11.2.0.2 behave.]  Because of deletes/inserts a plan can change even though the query has not, so there is no 'definitive' answer to the question as written.  The conditions are simply too vague to produce repeatable results.&lt;br /&gt;
&lt;br /&gt;
And that's a definite maybe.&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=thFsbxz63z4:0ZYD6TtZ7nw:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=thFsbxz63z4:0ZYD6TtZ7nw:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://oratips-ddf.blogspot.com/feeds/3512114719242110516/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=3512114719242110516" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/3512114719242110516?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/3512114719242110516?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2008/11/magical-indexes.html" title="'Magical' Indexes" /><author><name>David Fitzjarrell</name><uri>https://plus.google.com/102215427046411604711</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-y2hTTVpTe4o/AAAAAAAAAAI/AAAAAAAAAGw/KabdyVeaX9M/s512-c/photo.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;CkYFRHw7cSp7ImA9WxVXE0U.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-6278159802603211582</id><published>2008-10-31T13:42:00.004Z</published><updated>2009-02-11T19:21:55.209Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-02-11T19:21:55.209Z</app:edited><title>Workin' In The Mines</title><content type="html">Flashback query, available since Oracle 9i, can really be a lifesaver when a need arises to resurrect data.  But, what if the UNDO has been overwritten by another process?  To the rescue comes DBMS_LOGMNR, the LogMiner package.  This utility dredges through the redo logs (and archive logs, if they are still available) to return both the SQL statements to redo the transactions and SQL statements to undo those same transactions.  Since Oracle will automatically replay any in-doubt transactions occuring at the time of a crash retrieving the redo SQL is usually not necessary.  Returning the undo SQL, however, may be useful when flashback query cannot be used.&lt;br /&gt;&lt;br /&gt;

DBMS_LOGMNR has several procedures available, of which we'll use three in this example:  ADD_LOGFILE, START_LOGMNR and END_LOGMNR.  You can start up LogMiner then add the logfiles you wish to 'mine', or add the logfiles then start the utility.  I prefer the latter method, which is illustrated here.  We'll start by updating the EMP table:

&lt;pre&gt;&lt;span style="color:#009900;"&gt;SQL&gt; update emp set comm = 999 where comm = 1000;

10 rows updated.

SQL&gt; commit;

Commit complete.

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

Now let's mine the redo logs and see if we can undo that change:

&lt;pre&gt;&lt;span style="color:#009900;"&gt;SQL&gt; --
SQL&gt; -- Add every redo log to the 'mix' so LogMiner can
SQL&gt; -- use them
SQL&gt; --
SQL&gt; select 'exec dbms_logmnr.add_logfile('''||member||''')'
  2  from v$logfile
  3
SQL&gt;
SQL&gt; spool add_logfiles.sql
SQL&gt; /

'EXECDBMS_LOGMNR.ADD_LOGFILE('''||MEMBER||''')'
-------------------------------------------------------------------------------------
exec dbms_logmnr.add_logfile('/zing/flork/dapplenap/redo01.log')
exec dbms_logmnr.add_logfile('/zang/flork/dapplenap/redo02.log')
exec dbms_logmnr.add_logfile('/zong/flork/dapplenap/redo03.log')

SQL&gt; spool off
SQL&gt;
SQL&gt;
SQL&gt; @add_logfiles
SQL&gt; exec dbms_logmnr.add_logfile('/zing/flork/dapplenap/redo01.log')

PL/SQL procedure successfully completed.

SQL&gt; exec dbms_logmnr.add_logfile('/zang/flork/dapplenap/redo02.log')

PL/SQL procedure successfully completed.

SQL&gt; exec dbms_logmnr.add_logfile('/zong/flork/dapplenap/redo03.log')

PL/SQL procedure successfully completed.

SQL&gt;
SQL&gt; --
SQL&gt; -- Fire up LogMiner
SQL&gt; --
SQL&gt; exec dbms_logmnr.start_logmnr(options =&gt; DBMS_LOGMNR.DICT_FROM_ONLINE_CATALOG)

PL/SQL procedure successfully completed.

SQL&gt;
SQL&gt;
SQL&gt; --
SQL&gt; -- Prepare the environment for the output
SQL&gt; --
SQL&gt; set linesize 5000 trimspool on
SQL&gt;
SQL&gt;
SQL&gt; --
SQL&gt; -- Retrieve the SQL statements to 'undo' the
SQL&gt; -- committed changes
SQL&gt; --
SQL&gt; select sql_undo
  2  from v$logmnr_contents
  3  where seg_owner = upper('&amp;1')
  4
SQL&gt;
SQL&gt; spool undo_committed_changes.sql
SQL&gt; /
Enter value for 1: ortofon
old   3: where seg_owner = upper('&amp;1')
new   3: where seg_owner = upper('bing')

SQL_UNDO
-------------------------------------------------------------------------------------------------------------
update "BING"."EMP" set "COMM" = '1000' where "COMM" = '999' and ROWID = 'AAAanJAAEAAAAIvAAA';
update "BING"."EMP" set "COMM" = '1000' where "COMM" = '999' and ROWID = 'AAAanJAAEAAAAIvAAD';
update "BING"."EMP" set "COMM" = '1000' where "COMM" = '999' and ROWID = 'AAAanJAAEAAAAIvAAF';
update "BING"."EMP" set "COMM" = '1000' where "COMM" = '999' and ROWID = 'AAAanJAAEAAAAIvAAG';
update "BING"."EMP" set "COMM" = '1000' where "COMM" = '999' and ROWID = 'AAAanJAAEAAAAIvAAH';
update "BING"."EMP" set "COMM" = '1000' where "COMM" = '999' and ROWID = 'AAAanJAAEAAAAIvAAI';
update "BING"."EMP" set "COMM" = '1000' where "COMM" = '999' and ROWID = 'AAAanJAAEAAAAIvAAK';
update "BING"."EMP" set "COMM" = '1000' where "COMM" = '999' and ROWID = 'AAAanJAAEAAAAIvAAL';
update "BING"."EMP" set "COMM" = '1000' where "COMM" = '999' and ROWID = 'AAAanJAAEAAAAIvAAM';
update "BING"."EMP" set "COMM" = '1000' where "COMM" = '999' and ROWID = 'AAAanJAAEAAAAIvAAN';

SQL&gt; spool off
SQL&gt;
SQL&gt;
SQL&gt; --
SQL&gt; -- Shut down LogMiner
SQL&gt; --
SQL&gt; exec dbms_logmnr.end_logmnr

PL/SQL procedure successfully completed.

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

And we have displayed the statements necessary to undo the updates made to the EMP table earlier.  This won't work if the table is created NOLOGGING as no redo entries will be written for such transactions.  Also notice that the original update was one statement, and the undo (from the redo logs) generates 10 statements, one for each row updated.&lt;br /&gt;&lt;br /&gt;

Redo logs were used in this example, however you can also use archivelogs as well (as noted earlier, they must still be available on the server).  And you can ask Oracle to add redo logs and archivelogs as necessary; the CONTINUOUS_MINE option provides that functionality, requiring only that the first redo log be added via ADD_LOGFILE or the starting SCN for the transactions of interest be provided:

&lt;pre&gt;&lt;span style="color:#009900;"&gt;SQL&gt; --
SQL&gt; -- Add one redo log to the 'mix'
SQL&gt; --
SQL&gt; select 'exec dbms_logmnr.add_logfile('''||member||''')'
  2  from v$logfile
  3  where rownum = 1
  4
SQL&gt;
SQL&gt; spool add_logfiles.sql
SQL&gt; /

'EXECDBMS_LOGMNR.ADD_LOGFILE('''||MEMBER||''')'
-------------------------------------------------------------------------------------exec dbms_logmnr.add_logfile('/zing/flork/dapplenap/redo01.log')

SQL&gt; spool off
SQL&gt;
SQL&gt;
SQL&gt; @add_logfiles
SQL&gt; exec dbms_logmnr.add_logfile('/zing/flork/dapplenap/redo01.log')

PL/SQL procedure successfully completed.

SQL&gt;
SQL&gt; --
SQL&gt; -- Fire up LogMiner
SQL&gt; --
SQL&gt; -- The CONTINUOUS_MINE option cannot be used if the database 
SQL&gt; -- is not running in ARCHIVELOG mode
SQL&gt; --
SQL&gt; -- But, hey, we are, so we're good to go
SQL&gt; --
SQL&gt; exec dbms_logmnr.start_logmnr(options =&gt; DBMS_LOGMNR.DICT_FROM_ONLINE_CATALOG + DBMS_LOGMNR.CONTINUOUS_MINE)

PL/SQL procedure successfully completed.

SQL&gt;
SQL&gt;
SQL&gt; --
SQL&gt; -- Prepare the environment for the output
SQL&gt; --
SQL&gt; set linesize 5000 trimspool on
SQL&gt;
SQL&gt;
SQL&gt; --
SQL&gt; -- Retrieve the SQL statements to 'undo' the
SQL&gt; -- committed changes
SQL&gt; --
SQL&gt; select sql_undo
  2  from v$logmnr_contents
  3  where seg_owner = upper('&amp;1')
  4
SQL&gt;
SQL&gt; spool undo_committed_changes.sql
SQL&gt; /
Enter value for 1: ortofon
old   3: where seg_owner = upper('&amp;1')
new   3: where seg_owner = upper('bing')

SQL_UNDO
-------------------------------------------------------------------------------------------------------------
update "BING"."EMP" set "COMM" = '1000' where "COMM" = '999' and ROWID = 'AAAanJAAEAAAAIvAAA';
update "BING"."EMP" set "COMM" = '1000' where "COMM" = '999' and ROWID = 'AAAanJAAEAAAAIvAAD';
update "BING"."EMP" set "COMM" = '1000' where "COMM" = '999' and ROWID = 'AAAanJAAEAAAAIvAAF';
update "BING"."EMP" set "COMM" = '1000' where "COMM" = '999' and ROWID = 'AAAanJAAEAAAAIvAAG';
update "BING"."EMP" set "COMM" = '1000' where "COMM" = '999' and ROWID = 'AAAanJAAEAAAAIvAAH';
update "BING"."EMP" set "COMM" = '1000' where "COMM" = '999' and ROWID = 'AAAanJAAEAAAAIvAAI';
update "BING"."EMP" set "COMM" = '1000' where "COMM" = '999' and ROWID = 'AAAanJAAEAAAAIvAAK';
update "BING"."EMP" set "COMM" = '1000' where "COMM" = '999' and ROWID = 'AAAanJAAEAAAAIvAAL';
update "BING"."EMP" set "COMM" = '1000' where "COMM" = '999' and ROWID = 'AAAanJAAEAAAAIvAAM';
update "BING"."EMP" set "COMM" = '1000' where "COMM" = '999' and ROWID = 'AAAanJAAEAAAAIvAAN';

SQL&gt; spool off
SQL&gt;
SQL&gt;
SQL&gt; --
SQL&gt; -- Shut down LogMiner
SQL&gt; --
SQL&gt; exec dbms_logmnr.end_logmnr

PL/SQL procedure successfully completed.

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

We, of course, found the same records as in the prior example, but we didn't need to include every log file in the database to get this to work; the CONTINUOUS_MINE option kept adding logs to the mix to find the information we requested.  Yes, we supplied more than one option to the options parameter; we simply added the values together and DBMS_LOGMNR was able to know we wanted both options enabled.&lt;br /&gt;&lt;br /&gt;

LogMiner won't solve every data resurrection problem, nor will flashback query, however knowing these options are available may make your life as a DBA a bit less hectic and stressful.&lt;br /&gt;&lt;br /&gt;

Well, we can dream.&lt;br /&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=rhB4CLhcBLs:ZCV9x_fjYd4:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=rhB4CLhcBLs:ZCV9x_fjYd4:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://oratips-ddf.blogspot.com/feeds/6278159802603211582/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=6278159802603211582" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/6278159802603211582?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/6278159802603211582?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2008/10/workin-in-mines.html" title="Workin' In The Mines" /><author><name>David Fitzjarrell</name><uri>https://plus.google.com/102215427046411604711</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-y2hTTVpTe4o/AAAAAAAAAAI/AAAAAAAAAGw/KabdyVeaX9M/s512-c/photo.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;CUIBQnY_cCp7ImA9WxRWE0o.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-3275376405394935054</id><published>2008-10-20T18:16:00.003+01:00</published><updated>2008-10-30T13:32:33.848Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-10-30T13:32:33.848Z</app:edited><title>Lock 'Em Up</title><content type="html">There has been, recently, a large number of requests to google.com about Oracle database locks.  I've blogged before on locks and how to see who has which object locked, but I haven't said much as to the nature and intent of locks.  I think it's about time that topic was addressed.&lt;br /&gt;&lt;br /&gt;

Oracle, in the on-line documentation, states:&lt;br /&gt;&lt;br /&gt;

"Locks are mechanisms that prevent destructive interaction between transactions accessing the same resource — either user objects such as tables and rows or system objects not visible to users, such as shared data structures in memory and data dictionary rows."&lt;br /&gt;&lt;br /&gt;

&lt;span style="color:#009900;"&gt;[To get a bit picky here, locks on shared data structures in memory are usually called latches, but they perform the same task so we'll carry on with the discussion.]&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;

So a lock, when taken, prevents 'destructive interaction' between transactions.  What DOES that mean?  Possibly an example between two sessions trying to modify data in the same table will illustrate (this would be a 'blocking' lock, and querying V$LOCK where block &lt;&gt; 0 would show session #2 blocked by session #1).  Session #1 locks and modifies data in the EMP table by taking both a TX (transaction row-level) and a TM (row exclusive) lock:

&lt;pre&gt;&lt;span style="color:#009900;"&gt;SQL&gt; select empno, ename, sal
  2  from emp;

     EMPNO ENAME             SAL
---------- ---------- ----------
      7935 SMITH             900
      7369 SMYTHE            800
      7499 ALLEN            1600
      7521 WARD             1250
      7566 JONES            2975
      7654 MARTIN           1250
      7698 BLAKE            2850
      7782 CLARK            2450
      7788 SCOTT            3000
      7839 KING             5000
      7844 TURNER           1500

     EMPNO ENAME             SAL
---------- ---------- ----------
      7876 ADAMS            1100
      7900 JAMES             950
      7902 FORD             3000
      7934 MILLER           1300
      7955 SMITH             900
      7956 SMYTHE            800
      7957 ALLEN            1600
      7958 WARD             1250
      7959 JONES            2975
      7960 MARTIN           1250
      7961 BLAKE            2850

     EMPNO ENAME             SAL
---------- ---------- ----------
      7962 CLARK            2450
      7963 SCOTT            3000
      7964 KING             5000
      7965 TURNER           1500
      7966 ADAMS            1100
      7967 JAMES             950
      7968 FORD             3000
      7969 MILLER           1300
      8000 SMITH             900
      8001 SMYTHE         1382.4
      8002 ALLEN          2764.8

     EMPNO ENAME             SAL
---------- ---------- ----------
      8003 WARD             2160
      8004 JONES          5140.8
      8005 MARTIN           2160
      8006 BLAKE          4924.8
      8007 CLARK          4233.6
      8008 SCOTT            5184
      8009 KING             8640
      8010 TURNER           2592
      8011 ADAMS          1900.8
      8012 JAMES          1641.6
      8013 FORD             5184

     EMPNO ENAME             SAL
---------- ---------- ----------
      8014 MILLER         2246.4
      8015 SMITH          1555.2
      8016 SMYTHE         1382.4
      8017 ALLEN          2764.8
      8018 WARD             2160
      8019 JONES          5140.8
      8020 MARTIN           2160
      8021 BLAKE          4924.8
      8022 CLARK          4233.6
      8023 SCOTT            5184
      8024 KING             8640

     EMPNO ENAME             SAL
---------- ---------- ----------
      8025 TURNER           2592
      8026 ADAMS          1900.8
      8027 JAMES          1641.6
      8028 FORD             5184
      8029 MILLER         2246.4

60 rows selected.

SQL&gt;
SQL&gt; update emp
  2  set sal = sal *.985
  3  where empno &lt;= 8000;

31 rows updated.

SQL&gt;
SQL&gt; exec dbms_lock.sleep(60)
&lt;/span&gt;&lt;/pre&gt;

At this point we start session #2 and try to modify the same data in the same table:

&lt;pre&gt;&lt;span style="color:#009900;"&gt;SQL&gt; select empno, ename, sal
  2  from emp;

     EMPNO ENAME             SAL
---------- ---------- ----------
      7935 SMITH             900
      7369 SMYTHE            800
      7499 ALLEN            1600
      7521 WARD             1250
      7566 JONES            2975
      7654 MARTIN           1250
      7698 BLAKE            2850
      7782 CLARK            2450
      7788 SCOTT            3000
      7839 KING             5000
      7844 TURNER           1500

     EMPNO ENAME             SAL
---------- ---------- ----------
      7876 ADAMS            1100
      7900 JAMES             950
      7902 FORD             3000
      7934 MILLER           1300
      7955 SMITH             900
      7956 SMYTHE            800
      7957 ALLEN            1600
      7958 WARD             1250
      7959 JONES            2975
      7960 MARTIN           1250
      7961 BLAKE            2850

     EMPNO ENAME             SAL
---------- ---------- ----------
      7962 CLARK            2450
      7963 SCOTT            3000
      7964 KING             5000
      7965 TURNER           1500
      7966 ADAMS            1100
      7967 JAMES             950
      7968 FORD             3000
      7969 MILLER           1300
      8000 SMITH             900
      8001 SMYTHE         1382.4
      8002 ALLEN          2764.8

     EMPNO ENAME             SAL
---------- ---------- ----------
      8003 WARD             2160
      8004 JONES          5140.8
      8005 MARTIN           2160
      8006 BLAKE          4924.8
      8007 CLARK          4233.6
      8008 SCOTT            5184
      8009 KING             8640
      8010 TURNER           2592
      8011 ADAMS          1900.8
      8012 JAMES          1641.6
      8013 FORD             5184

     EMPNO ENAME             SAL
---------- ---------- ----------
      8014 MILLER         2246.4
      8015 SMITH          1555.2
      8016 SMYTHE         1382.4
      8017 ALLEN          2764.8
      8018 WARD             2160
      8019 JONES          5140.8
      8020 MARTIN           2160
      8021 BLAKE          4924.8
      8022 CLARK          4233.6
      8023 SCOTT            5184
      8024 KING             8640

     EMPNO ENAME             SAL
---------- ---------- ----------
      8025 TURNER           2592
      8026 ADAMS          1900.8
      8027 JAMES          1641.6
      8028 FORD             5184
      8029 MILLER         2246.4

60 rows selected.

SQL&gt;
SQL&gt; update emp
  2  set sal = sal * 1.005
  3  where empno &lt;= 8000;
&lt;/span&gt;&lt;/pre&gt;

The update process stops here, waiting for session #1 to commit its changes.  Session #1 has an exclusive lock on the data we want to modify in session #2, so that session needs to wait until the lock is cleared (by a commit or a rollback) to effect any changes.  As we continue on in both sessions we see that session #1 has ended its waiting period and committed its changes:

&lt;pre&gt;&lt;span style="color:#009900;"&gt;PL/SQL procedure successfully completed.

SQL&gt;
SQL&gt; commit;

Commit complete.

SQL&gt;
SQL&gt; select empno, ename, sal
  2  from emp;

     EMPNO ENAME             SAL
---------- ---------- ----------
      7935 SMITH           886.5
      7369 SMYTHE            788
      7499 ALLEN            1576
      7521 WARD          1231.25
      7566 JONES         2930.38
      7654 MARTIN        1231.25
      7698 BLAKE         2807.25
      7782 CLARK         2413.25
      7788 SCOTT            2955
      7839 KING             4925
      7844 TURNER         1477.5

     EMPNO ENAME             SAL
---------- ---------- ----------
      7876 ADAMS          1083.5
      7900 JAMES          935.75
      7902 FORD             2955
      7934 MILLER         1280.5
      7955 SMITH           886.5
      7956 SMYTHE            788
      7957 ALLEN            1576
      7958 WARD          1231.25
      7959 JONES         2930.38
      7960 MARTIN        1231.25
      7961 BLAKE         2807.25

     EMPNO ENAME             SAL
---------- ---------- ----------
      7962 CLARK         2413.25
      7963 SCOTT            2955
      7964 KING             4925
      7965 TURNER         1477.5
      7966 ADAMS          1083.5
      7967 JAMES          935.75
      7968 FORD             2955
      7969 MILLER         1280.5
      8000 SMITH           886.5
      8001 SMYTHE         1382.4
      8002 ALLEN          2764.8

     EMPNO ENAME             SAL
---------- ---------- ----------
      8003 WARD             2160
      8004 JONES          5140.8
      8005 MARTIN           2160
      8006 BLAKE          4924.8
      8007 CLARK          4233.6
      8008 SCOTT            5184
      8009 KING             8640
      8010 TURNER           2592
      8011 ADAMS          1900.8
      8012 JAMES          1641.6
      8013 FORD             5184

     EMPNO ENAME             SAL
---------- ---------- ----------
      8014 MILLER         2246.4
      8015 SMITH          1555.2
      8016 SMYTHE         1382.4
      8017 ALLEN          2764.8
      8018 WARD             2160
      8019 JONES          5140.8
      8020 MARTIN           2160
      8021 BLAKE          4924.8
      8022 CLARK          4233.6
      8023 SCOTT            5184
      8024 KING             8640

     EMPNO ENAME             SAL
---------- ---------- ----------
      8025 TURNER           2592
      8026 ADAMS          1900.8
      8027 JAMES          1641.6
      8028 FORD             5184
      8029 MILLER         2246.4

60 rows selected.

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

We can see that the only changes to see are those committed by session #1.  Session #2 now has 'free reign' over that same data, and implements its changes:

&lt;pre&gt;&lt;span style="color:#009900;"&gt;
31 rows updated.

SQL&gt;
SQL&gt; exec dbms_lock.sleep(60)

PL/SQL procedure successfully completed.

SQL&gt;
SQL&gt; commit;

Commit complete.

SQL&gt;
SQL&gt; select empno, ename, sal
  2  from emp;

     EMPNO ENAME             SAL
---------- ---------- ----------
      7935 SMITH          890.93
      7369 SMYTHE         791.94
      7499 ALLEN         1583.88
      7521 WARD          1237.41
      7566 JONES         2945.03
      7654 MARTIN        1237.41
      7698 BLAKE         2821.29
      7782 CLARK         2425.32
      7788 SCOTT         2969.78
      7839 KING          4949.63
      7844 TURNER        1484.89

     EMPNO ENAME             SAL
---------- ---------- ----------
      7876 ADAMS         1088.92
      7900 JAMES          940.43
      7902 FORD          2969.78
      7934 MILLER         1286.9
      7955 SMITH          890.93
      7956 SMYTHE         791.94
      7957 ALLEN         1583.88
      7958 WARD          1237.41
      7959 JONES         2945.03
      7960 MARTIN        1237.41
      7961 BLAKE         2821.29

     EMPNO ENAME             SAL
---------- ---------- ----------
      7962 CLARK         2425.32
      7963 SCOTT         2969.78
      7964 KING          4949.63
      7965 TURNER        1484.89
      7966 ADAMS         1088.92
      7967 JAMES          940.43
      7968 FORD          2969.78
      7969 MILLER         1286.9
      8000 SMITH          890.93
      8001 SMYTHE         1382.4
      8002 ALLEN          2764.8

     EMPNO ENAME             SAL
---------- ---------- ----------
      8003 WARD             2160
      8004 JONES          5140.8
      8005 MARTIN           2160
      8006 BLAKE          4924.8
      8007 CLARK          4233.6
      8008 SCOTT            5184
      8009 KING             8640
      8010 TURNER           2592
      8011 ADAMS          1900.8
      8012 JAMES          1641.6
      8013 FORD             5184

     EMPNO ENAME             SAL
---------- ---------- ----------
      8014 MILLER         2246.4
      8015 SMITH          1555.2
      8016 SMYTHE         1382.4
      8017 ALLEN          2764.8
      8018 WARD             2160
      8019 JONES          5140.8
      8020 MARTIN           2160
      8021 BLAKE          4924.8
      8022 CLARK          4233.6
      8023 SCOTT            5184
      8024 KING             8640

     EMPNO ENAME             SAL
---------- ---------- ----------
      8025 TURNER           2592
      8026 ADAMS          1900.8
      8027 JAMES          1641.6
      8028 FORD             5184
      8029 MILLER         2246.4

60 rows selected.

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

No destructive interaction between the sessions could occur; session #2 could not update the data locked by session #1 until that lock was released.  Once the first lock was gone a second lock, by another session, could be taken so further updates could be processed.  Were there a third session attempting to modify this same data it, too, would be locked as session #2 was before, preventing multiple sessions from updating the same data immediately on top of, and interfering with, the current transaction.&lt;br /&gt;&lt;br /&gt;

Locks can be a problem, however, especially when one session has locked data another session needs, and the waiting session has locked data the first session wants to modify.  This is known as a deadlock, and later releases of Oracle detect such conditions and issue a rollback on the 'newer' transaction (here 'newer' means the transaction which didn't lock the data its waiting upon but has data a transaction higher up the queue needs to access or modify).  To give a representation of this:&lt;br /&gt;&lt;br /&gt;

-- Transaction A modifies data in the EMP table&lt;br /&gt;
-- Transaction B modifies data in the DEPT table&lt;br /&gt;
-- Transaction A now needs to modify records in the DEPT table,&lt;br /&gt;
   but transaction B has those records locked&lt;br /&gt;
-- Transaction B now wants to modify records in the EMP table&lt;br /&gt; 
   that transaction A has locked&lt;br /&gt;
-- Oracle will 'rollback' transaction B as it's in the queue after&lt;br /&gt;
   transaction A and holds data that transaction A needs to complete&lt;br /&gt;
   its work&lt;br /&gt;&lt;br /&gt;

Older releases of Oracle (pre-9.0) will let a deadlock continue forever, so the DBA will need to handle such occurrences in 8.1.7.4 and earlier versions.&lt;br /&gt;&lt;br /&gt;

Some locks don't affect anyone, such as shared row locks (taken by plain old SELECT statements); others, such as those taken by DDL statements, prevent any action against the affected object.  Additionally, since DDL transactions implicitly commit before and after the statement execution the exclusive object lock is only active for a short period of time, which in many cases won't noticeably affect other sessions.&lt;br /&gt;&lt;br /&gt;

Locks may be considered by some to be inconvenient, but they are necessary to preserve data integrity and provide transaction isolation (see the example above).  And transaction isolation and data integrity are important if your data has any worth at all (and everyone's data is worth something to their user community).&lt;br /&gt;&lt;br /&gt;

"Lock 'em up, Sheriff!"&lt;br /&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=dG9JqYysvsQ:ZUcpZPaoAJg:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=dG9JqYysvsQ:ZUcpZPaoAJg:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://oratips-ddf.blogspot.com/feeds/3275376405394935054/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=3275376405394935054" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/3275376405394935054?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/3275376405394935054?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2008/10/lock-em-up.html" title="Lock 'Em Up" /><author><name>David Fitzjarrell</name><uri>https://plus.google.com/102215427046411604711</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-y2hTTVpTe4o/AAAAAAAAAAI/AAAAAAAAAGw/KabdyVeaX9M/s512-c/photo.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DEYERHo8cCp7ImA9WxRXEEw.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-1573408144992830875</id><published>2008-10-14T19:14:00.002+01:00</published><updated>2008-10-14T21:28:25.478+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-10-14T21:28:25.478+01:00</app:edited><title>Is Anybody There?</title><content type="html">It's interesting to see what people submit to google.com; lately this query came to my attention:&lt;br /&gt;&lt;br /&gt;

"how to see when the table is populated in oracle"&lt;br /&gt;&lt;br /&gt;

which, on the face of it, seems to be a rather silly question, with a somewhat obvious answer:&lt;br /&gt;&lt;br /&gt;

"select count(*) from [insert table name  here];"&lt;br /&gt;&lt;br /&gt;

But, is that a truly reliable way to take the inventory of a table's contents?  Let's investigate this further and see what might not be so obvious from a cursory glance.&lt;br /&gt;&lt;br /&gt;

I've discussed, in more than one post, how Oracle provides a consistent image of the data for the point in time when a query begins.  And I've proven you can't rely upon a select statement to provide a repeatable and usable result to manually generate sequential numbers.  This same mechanism can wreak havoc on determining whether a table is populated or not because at the time you may be querying the table's contents someone else may be deleting those same records that appear before your very eyes.  Absent a commit in the 'offending' session you'll never know that Mercantile Flabbenjammitz just obliterated all of that data because, through the miracle of the UNDO records, you can still see every byte that used to be stored in that table.  Along comes a commit and -- whoosh! -- the data you saw just a moment earlier now vanishes.&lt;br /&gt;&lt;br /&gt;

So how DOES one know when a table is populated?  Popular 'wisdom' provides several possible answers:&lt;br /&gt;&lt;br /&gt;

Table statistics&lt;br /&gt;
The High-Water Mark (HWM)&lt;br /&gt;
The aforementioned "select count(*) from ..."&lt;br /&gt;
Querying DBA_EXTENTS&lt;br /&gt;&lt;br /&gt;

How many of those are reliable?  Let's find out.&lt;br /&gt;&lt;br /&gt;

Table statistics are a really good indicator of whether or not a table is populated, until, well, it isn't populated any more:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; create table blarzenflotz(
  2        glerm   number,
  3        snangul varchar2(40),
  4        borm    number
  5  );

Table created.

SQL&gt;
SQL&gt; begin
  2        for i in 1..1000000 loop
  3         insert into blarzenflotz
  4         values(i, 'Schnerkenporf vasul '||i, mod(i*47, 3));
  5        end loop;
  6
  7        commit;
  8
  9  end;
 10  /

PL/SQL procedure successfully completed.

SQL&gt;
SQL&gt; begin
  2        for i in 1..1000000 loop
  3         insert into blarzenflotz
  4         values(i, 'Schnerkenporf vasul '||i, mod(i*47, 3));
  5        end loop;
  6
  7        commit;
  8
  9  end;
 10  /

PL/SQL procedure successfully completed.

SQL&gt;
SQL&gt; exec dbms_stats.gather_table_stats(ownname=&gt;null, tabname=&gt;'BLARZENFLOTZ', estimate_percent =&gt; 100)

PL/SQL procedure successfully completed.

SQL&gt;
SQL&gt; select count(*)
  2  from blarzenflotz;

  COUNT(*)
----------
   2000000

SQL&gt;
SQL&gt; select num_rows
  2  from user_tables
  3  where table_name = 'BLARZENFLOTZ';

  NUM_ROWS
----------
   2000000

SQL&gt;
SQL&gt; delete from blarzenflotz;

2000000 rows deleted.

SQL&gt;
SQL&gt; select count(*)
  2  from blarzenflotz;

  COUNT(*)
----------
         0

SQL&gt;
SQL&gt; select num_rows
  2  from user_tables
  3  where table_name = 'BLARZENFLOTZ';

  NUM_ROWS
----------
   2000000

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

Without gathering new statistics after such an operation (and that's not likely to occur since statistics gathering is usually a scheduled task) you can see how deceiving they can be.  There are no rows in the table, yet the statistics state otherwise.  Hmmmm.  A similar fate befalls you if someone else, in another session, is deleting rows from the table of interest and has yet to commit the deed.  You'll go happily along, 'knowing' there are two-million rows in the table, blissfully unaware that Morgenstern Slapdragon has just deleted every last one of them.  Your 'select count(*) from ...' query returns the expected 2000000 as a result, and old Morgenstern, running the same query, reports that nothing is left of that data.  Sneak in a 'commit' and now both queries return what Morgenstern knew five minutes ago -- the data in that table is gone.&lt;br /&gt;&lt;br /&gt;

Will querying DBA_EXTENTS tell you anything of worth with respect to the actual data population?  I'm afraid not, as that view will only tell you that X extents have been allocated to the object/segment; there is no information in that view to report if those extents are actually populated.&lt;br /&gt;&lt;br /&gt;

The high-water mark won't tell you anything, either, as that is the end of the blocks which have or did have data in them.  The high-water mark is not reset by deletes, only by a truncate, so deleting all of the data in a table won't move the HWM, and using that to indicate data population is as reliable as the statistics or the 'select count(*) from ...' query in a multi-user environment.  And even using ROWID is suspect, as those are retrieved from the UNDO blocks to return a consistent data set regardless of the state or number of uncommitted transactions against the table or tables queried.&lt;br /&gt;&lt;br /&gt;

So how DO you know when a table is populated?  You play your cards and you take your chances.  Odds are in your favor such tools as described here will provide a good idea whether a table is populated or not.  Just remember that because of Oracle's read consistency mechanism sometimes those results could be wrong.&lt;br /&gt;&lt;br /&gt;

But the odds of the results being correct are far better than any you can get in Las Vegas.&lt;br /&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=TuIGafaEPC4:gxwQnKti-bw:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=TuIGafaEPC4:gxwQnKti-bw:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/blogspot/oratips?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://oratips-ddf.blogspot.com/feeds/1573408144992830875/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=1573408144992830875" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/1573408144992830875?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/1573408144992830875?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2008/10/is-anybody-there.html" title="Is Anybody There?" /><author><name>David Fitzjarrell</name><uri>https://plus.google.com/102215427046411604711</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-y2hTTVpTe4o/AAAAAAAAAAI/AAAAAAAAAGw/KabdyVeaX9M/s512-c/photo.jpg" /></author><thr:total>0</thr:total></entry></feed>
