<?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: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;CkYDSHg5fyp7ImA9WhRaEks.&quot;"><id>tag:blogger.com,1999:blog-28935478</id><updated>2012-02-14T22:56:19.627Z</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>65</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;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="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28935478-3719870654690949776?l=oratips-ddf.blogspot.com' alt='' /&gt;&lt;/div&gt;&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="1 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>d_d_f</name><uri>http://www.blogger.com/profile/03203479596583639865</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="24" src="http://2.bp.blogspot.com/-89TposiQsMQ/Tu-5pfRb_rI/AAAAAAAAADE/h82gy5lsV80/s220/david.fitzjarrell.JPG" /></author><thr:total>1</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="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28935478-6283232057305635570?l=oratips-ddf.blogspot.com' alt='' /&gt;&lt;/div&gt;&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>d_d_f</name><uri>http://www.blogger.com/profile/03203479596583639865</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="24" src="http://2.bp.blogspot.com/-89TposiQsMQ/Tu-5pfRb_rI/AAAAAAAAADE/h82gy5lsV80/s220/david.fitzjarrell.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="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28935478-5289398491509978769?l=oratips-ddf.blogspot.com' alt='' /&gt;&lt;/div&gt;&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>d_d_f</name><uri>http://www.blogger.com/profile/03203479596583639865</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="24" src="http://2.bp.blogspot.com/-89TposiQsMQ/Tu-5pfRb_rI/AAAAAAAAADE/h82gy5lsV80/s220/david.fitzjarrell.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="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28935478-7784616094228893840?l=oratips-ddf.blogspot.com' alt='' /&gt;&lt;/div&gt;&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>d_d_f</name><uri>http://www.blogger.com/profile/03203479596583639865</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="24" src="http://2.bp.blogspot.com/-89TposiQsMQ/Tu-5pfRb_rI/AAAAAAAAADE/h82gy5lsV80/s220/david.fitzjarrell.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="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28935478-5503420324870865832?l=oratips-ddf.blogspot.com' alt='' /&gt;&lt;/div&gt;&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>d_d_f</name><uri>http://www.blogger.com/profile/03203479596583639865</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="24" src="http://2.bp.blogspot.com/-89TposiQsMQ/Tu-5pfRb_rI/AAAAAAAAADE/h82gy5lsV80/s220/david.fitzjarrell.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="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28935478-2715615392346839461?l=oratips-ddf.blogspot.com' alt='' /&gt;&lt;/div&gt;&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>d_d_f</name><uri>http://www.blogger.com/profile/03203479596583639865</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="24" src="http://2.bp.blogspot.com/-89TposiQsMQ/Tu-5pfRb_rI/AAAAAAAAADE/h82gy5lsV80/s220/david.fitzjarrell.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="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28935478-9152247335654387714?l=oratips-ddf.blogspot.com' alt='' /&gt;&lt;/div&gt;&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>d_d_f</name><uri>http://www.blogger.com/profile/03203479596583639865</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="24" src="http://2.bp.blogspot.com/-89TposiQsMQ/Tu-5pfRb_rI/AAAAAAAAADE/h82gy5lsV80/s220/david.fitzjarrell.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="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28935478-2372915015565874699?l=oratips-ddf.blogspot.com' alt='' /&gt;&lt;/div&gt;&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>d_d_f</name><uri>http://www.blogger.com/profile/03203479596583639865</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="24" src="http://2.bp.blogspot.com/-89TposiQsMQ/Tu-5pfRb_rI/AAAAAAAAADE/h82gy5lsV80/s220/david.fitzjarrell.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="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28935478-6406506822378559399?l=oratips-ddf.blogspot.com' alt='' /&gt;&lt;/div&gt;&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>d_d_f</name><uri>http://www.blogger.com/profile/03203479596583639865</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="24" src="http://2.bp.blogspot.com/-89TposiQsMQ/Tu-5pfRb_rI/AAAAAAAAADE/h82gy5lsV80/s220/david.fitzjarrell.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="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28935478-1543361888944659066?l=oratips-ddf.blogspot.com' alt='' /&gt;&lt;/div&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>d_d_f</name><uri>http://www.blogger.com/profile/03203479596583639865</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="24" src="http://2.bp.blogspot.com/-89TposiQsMQ/Tu-5pfRb_rI/AAAAAAAAADE/h82gy5lsV80/s220/david.fitzjarrell.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="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28935478-7686442430763410908?l=oratips-ddf.blogspot.com' alt='' /&gt;&lt;/div&gt;&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>d_d_f</name><uri>http://www.blogger.com/profile/03203479596583639865</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="24" src="http://2.bp.blogspot.com/-89TposiQsMQ/Tu-5pfRb_rI/AAAAAAAAADE/h82gy5lsV80/s220/david.fitzjarrell.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="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28935478-6262276016578663152?l=oratips-ddf.blogspot.com' alt='' /&gt;&lt;/div&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>d_d_f</name><uri>http://www.blogger.com/profile/03203479596583639865</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="24" src="http://2.bp.blogspot.com/-89TposiQsMQ/Tu-5pfRb_rI/AAAAAAAAADE/h82gy5lsV80/s220/david.fitzjarrell.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="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28935478-8261598810235438202?l=oratips-ddf.blogspot.com' alt='' /&gt;&lt;/div&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>d_d_f</name><uri>http://www.blogger.com/profile/03203479596583639865</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="24" src="http://2.bp.blogspot.com/-89TposiQsMQ/Tu-5pfRb_rI/AAAAAAAAADE/h82gy5lsV80/s220/david.fitzjarrell.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="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28935478-6585975616928746913?l=oratips-ddf.blogspot.com' alt='' /&gt;&lt;/div&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>d_d_f</name><uri>http://www.blogger.com/profile/03203479596583639865</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="24" src="http://2.bp.blogspot.com/-89TposiQsMQ/Tu-5pfRb_rI/AAAAAAAAADE/h82gy5lsV80/s220/david.fitzjarrell.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="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28935478-5419499264250908784?l=oratips-ddf.blogspot.com' alt='' /&gt;&lt;/div&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>d_d_f</name><uri>http://www.blogger.com/profile/03203479596583639865</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="24" src="http://2.bp.blogspot.com/-89TposiQsMQ/Tu-5pfRb_rI/AAAAAAAAADE/h82gy5lsV80/s220/david.fitzjarrell.JPG" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;Ak4NSHs8fip7ImA9Wx9RGEo.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-5627166215211456159</id><published>2009-01-28T16:08:00.008Z</published><updated>2010-12-20T21:03:19.576Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-12-20T21:03:19.576Z</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.&lt;br /&gt;
&lt;br /&gt;
And that was the week that wasn't.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28935478-5627166215211456159?l=oratips-ddf.blogspot.com' alt='' /&gt;&lt;/div&gt;&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>d_d_f</name><uri>http://www.blogger.com/profile/03203479596583639865</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="24" src="http://2.bp.blogspot.com/-89TposiQsMQ/Tu-5pfRb_rI/AAAAAAAAADE/h82gy5lsV80/s220/david.fitzjarrell.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="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28935478-6908813183914363739?l=oratips-ddf.blogspot.com' alt='' /&gt;&lt;/div&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>d_d_f</name><uri>http://www.blogger.com/profile/03203479596583639865</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="24" src="http://2.bp.blogspot.com/-89TposiQsMQ/Tu-5pfRb_rI/AAAAAAAAADE/h82gy5lsV80/s220/david.fitzjarrell.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="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28935478-515814518185893067?l=oratips-ddf.blogspot.com' alt='' /&gt;&lt;/div&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>d_d_f</name><uri>http://www.blogger.com/profile/03203479596583639865</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="24" src="http://2.bp.blogspot.com/-89TposiQsMQ/Tu-5pfRb_rI/AAAAAAAAADE/h82gy5lsV80/s220/david.fitzjarrell.JPG" /></author><thr:total>2</thr:total></entry><entry gd:etag="W/&quot;Ck8HSX8-fCp7ImA9WxRbE04.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-3512114719242110516</id><published>2008-11-19T15:32:00.012Z</published><updated>2008-12-03T19:00:38.154Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-12-03T19:00:38.154Z</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;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;

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.]  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;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28935478-3512114719242110516?l=oratips-ddf.blogspot.com' alt='' /&gt;&lt;/div&gt;&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>d_d_f</name><uri>http://www.blogger.com/profile/03203479596583639865</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="24" src="http://2.bp.blogspot.com/-89TposiQsMQ/Tu-5pfRb_rI/AAAAAAAAADE/h82gy5lsV80/s220/david.fitzjarrell.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="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28935478-6278159802603211582?l=oratips-ddf.blogspot.com' alt='' /&gt;&lt;/div&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>d_d_f</name><uri>http://www.blogger.com/profile/03203479596583639865</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="24" src="http://2.bp.blogspot.com/-89TposiQsMQ/Tu-5pfRb_rI/AAAAAAAAADE/h82gy5lsV80/s220/david.fitzjarrell.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="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28935478-3275376405394935054?l=oratips-ddf.blogspot.com' alt='' /&gt;&lt;/div&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>d_d_f</name><uri>http://www.blogger.com/profile/03203479596583639865</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="24" src="http://2.bp.blogspot.com/-89TposiQsMQ/Tu-5pfRb_rI/AAAAAAAAADE/h82gy5lsV80/s220/david.fitzjarrell.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="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28935478-1573408144992830875?l=oratips-ddf.blogspot.com' alt='' /&gt;&lt;/div&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>d_d_f</name><uri>http://www.blogger.com/profile/03203479596583639865</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="24" src="http://2.bp.blogspot.com/-89TposiQsMQ/Tu-5pfRb_rI/AAAAAAAAADE/h82gy5lsV80/s220/david.fitzjarrell.JPG" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DUAHSX44fSp7ImA9WxRQEUs.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-9148207676513547412</id><published>2008-10-01T14:10:00.010+01:00</published><updated>2008-10-05T01:48:58.035+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-10-05T01:48:58.035+01:00</app:edited><title>I Need More Stuff</title><content type="html">Careful coding is a must in application development but, occasionally, the best laid plans go astray.  Enter the ORA-00947 error, caused by queries and insert statements having too few values.  Let's look at a few examples of how this can happen.&lt;br /&gt;&lt;br /&gt;

A simple query can become a nightmare if the subquery doesn't return the proper number of values:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; select e.empno, e.ename, d.loc, m.ename
  2  from emp e , emp m, dept d
  3  where m.empno = e.mgr
  4  and d.deptno = e.deptno
  5  and e.deptno = m.deptno
  6  and (d.deptno, d.dname) in (select deptno from emp)
  7  /
and (d.deptno, d.dname) in (select deptno from emp)
                            *
ERROR at line 6:
&lt;span style="color:#990000;"&gt;ORA-00947: not enough values&lt;/span&gt; 

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

Yes, this was an obvious example, but it does illustrate the point.  Insert statements are also not immune to returning such an error, through the values clause:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; create table yingsplut(
  2        hargen number,
  3        neebo  number,
  4        snerm  varchar2(75)
  5  );

Table created.

SQL&gt; 
SQL&gt; insert into yingsplut
  2  values (17, 3)
  3  /
insert into yingsplut
            *
ERROR at line 1:
&lt;span style="color:#990000;"&gt;ORA-00947: not enough values &lt;/span&gt;


SQL&gt;&lt;/span&gt;&lt;/pre&gt;

Or through a select statement:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; insert into yingsplut
  2  select empno, sal from emp
  3  /
insert into yingsplut
            *
ERROR at line 1:
&lt;span style="color:#990000;"&gt;ORA-00947: not enough values &lt;/span&gt;


SQL&gt; &lt;/span&gt;&lt;/pre&gt;

Materialized view refreshes can also be affected if the materialized view was created with 'select * from ...' and the source table definition has changed since the materialized view was created.  The solution here is to recreate the materialized view using a query listing all of the desired columns explicitly.  Yes, in some cases that won't stop base table definition changes from 'hosing' your refresh, but simply adding columns to a base table definition won't result in the refresh job generating an error.&lt;br /&gt;&lt;br /&gt;

Normally this error wouldn't arise from such simple code; having a table with a large number of columns, though, can easily provide insert statements that miss the mark by one or more values simply because the developer lost count of default or NULL values:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; create table valtest(
  2        val_key number,
  3        val1    varchar2(12),
  4        val2    varchar2(12),
  5        val3    varchar2(12),
  6        val4    varchar2(12),
  7        val5    varchar2(12),
  8        val6    varchar2(12),
  9        val7    varchar2(12),
 10        val8    varchar2(12),
 11        val9    varchar2(12),
 12        val10   varchar2(12),
 13        val11   varchar2(12),
 14        val12   varchar2(12),
 15        val13   varchar2(12),
 16        val14   varchar2(12),
 17        val15   varchar2(12),
 18        val16   varchar2(12),
 19        val17   varchar2(12),
 20        val18   varchar2(12),
 21        val19   varchar2(12),
 22        val20   varchar2(12),
 23        val21   varchar2(12),
 24        val22   varchar2(12),
 25        val23   varchar2(12),
 26        val24   varchar2(12),
 27        val25   varchar2(12),
 28        val26   varchar2(12),
 29        val27   varchar2(12),
 30        val28   varchar2(12),
 31        val29   varchar2(12),
 32        val30   varchar2(12),
 33        val31   varchar2(12),
 34        val32   varchar2(12),
 35        val33   varchar2(12),
 36        val34   varchar2(12),
 37        val35   varchar2(12),
 38        val36   varchar2(12),
 39        val37   varchar2(12)
 40  );

Table created.

SQL&gt; 
SQL&gt; insert into valtest
  2  values (1,
  3        'This',
  4        'is',
  5        'the',
  6        'first',
  7        'record',
  8        'inserted',
  9        'into',
 10        'my',
 11        'table',
 12        'and',
 13        'it''s',
 14        'a',
 15        'really',
 16        'long',
 17        'one',
 18        'because',
 19        'I',
 20        'wanted',
 21        'to',
 22        'be',
 23        'as',
 24        'difficult',
 25        'as',
 26        'possible',
 27        NULL,
 28        NULL,
 29        NULL,
 30        NULL,
 31        NULL);
insert into valtest
            *
ERROR at line 1:
&lt;span style="color:#990000;"&gt;ORA-00947: not enough values &lt;/span&gt;


SQL&gt; &lt;/span&gt;&lt;/pre&gt;

PL/SQL blocks are not immune to this, either, when using BULK COLLECT if the collection is defined absent a column or two:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; create table lotsa_data(
  2        data_id number,
  3        data_set number,
  4        data_val varchar2(40),
  5        proc_dt  date
  6  );

Table created.

SQL&gt; 
SQL&gt; create table less_data(
  2        data_id number,
  3        data_set number,
  4        data_val varchar2(40)
  5  );

Table created.

SQL&gt; 
SQL&gt; create table ref_data(
  2        data_id number,
  3        data_set number
  4  );

Table created.

SQL&gt; 
SQL&gt; begin
  2        for i in 1..10000 loop
  3         insert into lotsa_data
  4         values(i, mod(i, 17), 'Test data statement '||i, sysdate);
  5         insert into ref_data
  6         values(i, mod(i,17));
  7        end loop;
  8  
  9        commit;
 10  
 11  end;
 12  /

PL/SQL procedure successfully completed.

SQL&gt; 
SQL&gt; declare
  2        type ldat_tab_typ is table of less_data%rowtype index by binary_integer;
  3  
  4        d_tab ldat_tab_typ;
  5  begin
  6        update lotsa_data
  7        set data_id = data_id + 10
  8        where data_set = 16
  9        returning data_id, data_set, data_val, proc_dt bulk collect into d_tab;
 10  
 11        for i in 1..d_tab.count loop
 12         dbms_output.put_line(d_tab(i).data_id||'  '||d_tab(i).data_set||' '||d_tab(i).data_val);
 13        end loop;
 14  end;
 15  /
        returning data_id, data_set, data_val, proc_dt bulk collect into d_tab;
                                                                              *
ERROR at line 9:
ORA-06550: line 9, column 79: 
PL/SQL: &lt;span style="color:#990000;"&gt;ORA-00947: not enough values &lt;/span&gt;
ORA-06550: line 6, column 9: 
PL/SQL: SQL Statement ignored 


SQL&gt; &lt;/span&gt;&lt;/pre&gt;

&lt;span style="color:#990000;"&gt;[If you go the other way, and have too many columns in your collection an ORA-00913 appears:

&lt;pre&gt;&lt;span style="color:#990000;"&gt;SQL&gt; declare
  2        type ldat_tab_typ is table of lotsamore_data%rowtype index by binary_integer;
  3  
  4        d_tab ldat_tab_typ;
  5  begin
  6        update lotsa_data
  7        set data_id = data_id + 10
  8        where data_set = 16
  9        returning data_id, data_set, data_val, proc_dt bulk collect into d_tab;
 10  
 11        for i in 1..d_tab.count loop
 12         dbms_output.put_line(d_tab(i).data_id||'  '||d_tab(i).data_set||' '||d_tab(i).data_val);
 13        end loop;
 14  end;
 15  /
        returning data_id, data_set, data_val, proc_dt bulk collect into d_tab;
                                                                              *
ERROR at line 9:
ORA-06550: line 9, column 79: 
PL/SQL: ORA-00913: too many values 
ORA-06550: line 6, column 9: 
PL/SQL: SQL Statement ignored 


SQL&gt; &lt;/span&gt;&lt;/pre&gt;

The solution to both conditions is the same, so read on.]&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;

Troubleshooting such code is a time-consuming process, as the offending VALUES statements, SELECT queries or collection variables need to be carefully examined and the argument count/column count verified with the number of columns in the inserted table.  Let's take the very first example and fix it:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; create table ref_dept
  2  as select deptno, dname, loc
  3  from dept
  4  where deptno in (10, 30, 70);

Table created.

SQL&gt; 
SQL&gt; select e.empno, e.ename, d.loc, m.ename
  2  from emp e , emp m, dept d
  3  where m.empno = e.mgr
  4  and d.deptno = e.deptno
  5  and e.deptno = m.deptno
  6  and (d.deptno, d.dname) in (select deptno, dname from ref_dept)
  7  /

     EMPNO ENAME      LOC           ENAME     
---------- ---------- ------------- ----------
      7934 MILLER     NEW YORK      CLARK     
      7782 CLARK      NEW YORK      KING      
      7499 ALLEN      CHICAGO       BLAKE     
      7654 MARTIN     CHICAGO       BLAKE     
      7900 JAMES      CHICAGO       BLAKE     
      7844 TURNER     CHICAGO       BLAKE     
      7521 WARD       CHICAGO       BLAKE    

7 rows selected.

SQL&gt; &lt;/span&gt;&lt;/pre&gt;

Notice we created a new reference table, and populated it with the desired values.  The reference table was then used in the subquery to return the proper number of values, along with the correct department numbers and names, to return the expected result set.  Sharp eyes will note that we didn't need the reference table as we could have queried the DEPT table again with the qualifying WHERE clause:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; select e.empno, e.ename, d.loc, m.ename
  2  from emp e , emp m, dept d
  3  where m.empno = e.mgr
  4  and d.deptno = e.deptno
  5  and e.deptno = m.deptno
  6  and (d.deptno, d.dname) in (select deptno, dname from dept where deptno in (10,30,70))
  7  /

     EMPNO ENAME      LOC           ENAME     
---------- ---------- ------------- ----------
      7934 MILLER     NEW YORK      CLARK 
      7782 CLARK      NEW YORK      KING  
      7499 ALLEN      CHICAGO       BLAKE 
      7654 MARTIN     CHICAGO       BLAKE
      7900 JAMES      CHICAGO       BLAKE 
      7844 TURNER     CHICAGO       BLAKE 
      7521 WARD       CHICAGO       BLAKE 

7 rows selected.

SQL&gt; &lt;/span&gt;&lt;/pre&gt;

Correcting the INSERT statement takes a bit more time, since the column count and value count must match.  But time and patience are rewarded:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; insert into valtest
  2  values (1,
  3        'This',
  4        'is',
  5        'the',
  6        'first',
  7        'record',
  8        'inserted',
  9        'into',
 10        'my',
 11        'table',
 12        'and',
 13        'it''s',
 14        'a',
 15        'really',
 16        'long',
 17        'one',
 18        'because',
 19        'I',
 20        'wanted',
 21        'to',
 22        'be',
 23        'as',
 24        'difficult',
 25        'as',
 26        'possible',
 27        'since',
 28        'my',
 29        'goldfish',
 30        'can''t',
 31        'dance',
 32        'after',
 33        'thirteen',
 34        'cups',
 35        'of',
 36        'grape',
 37        'Kool-Aid',
 38        'from',
 39        'Duluth');

1 row created.

SQL&gt; &lt;/span&gt;&lt;/pre&gt;

Fixing the last example takes nothing more than changing the PL/SQL table to contain all of the referenced columns:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; declare
  2        type ldat_tab_typ is table of lotsa_data%rowtype index by binary_integer;
  3
  4        d_tab ldat_tab_typ;
  5  begin
  6        update lotsa_data
  7        set data_id = data_id + 10
  8        where data_set = 16
  9        returning data_id, data_set, data_val, proc_dt bulk collect into d_tab;
 10
 11        for i in 1..d_tab.count loop
 12         dbms_output.put_line(d_tab(i).data_id||'  '||d_tab(i).data_set||' '||d_tab(i).data_val);
 13        end loop;
 14  end;
 15  /
349  16  Test data statement 339
366  16  Test data statement 356
383  16  Test data statement 373
400  16  Test data statement 390
[... much more output here ...]
9393  16  Test data statement 9383
9410  16  Test data statement 9400
9427  16  Test data statement 9417
9444  16  Test data statement 9434
9461  16  Test data statement 9451
9478  16  Test data statement 9468
9495  16  Test data statement 9485
9512  16  Test data statement 9502
9529  16  Test data statement 9519

PL/SQL procedure successfully completed.

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

There is nothing more satisfying than the joy of a job well done.  And there's nothing more frustrating than having an error like an ORA-00947 which somehow eludes correction.  Sometimes it's best to let another pair of eyes peruse the code, as a fresh viewpoint can help reveal mistakes often overlooked by the original programmer, and can be of great assistance in fixing any problematic code.  I'm not afraid to ask for help.  You shouldn't be, either.&lt;br /&gt;&lt;br /&gt;

Now, let's code!&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28935478-9148207676513547412?l=oratips-ddf.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=qWp6nFNFiMQ:nrA5a920D-Q: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=qWp6nFNFiMQ:nrA5a920D-Q: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/9148207676513547412/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=9148207676513547412" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/9148207676513547412?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/9148207676513547412?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2008/10/i-need-more-stuff.html" title="I Need More Stuff" /><author><name>d_d_f</name><uri>http://www.blogger.com/profile/03203479596583639865</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="24" src="http://2.bp.blogspot.com/-89TposiQsMQ/Tu-5pfRb_rI/AAAAAAAAADE/h82gy5lsV80/s220/david.fitzjarrell.JPG" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DEAHQH0zfip7ImA9Wx5XFE4.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-8200621470470452579</id><published>2008-09-29T18:40:00.009+01:00</published><updated>2010-09-14T04:58:51.386+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-09-14T04:58:51.386+01:00</app:edited><title>It's Real Refreshment</title><content type="html">For those who suffer from endless curiosity the prospect of poking around the data dictionary is considered fun, and in the quest of such fun one can run across an interesting view or two.  DBA_REFRESH_CHILDREN could be one of those views, and for the inquisitive an examination is in order.&lt;br /&gt;
&lt;br /&gt;
DBA_REFRESH_CHILDREN lists all of the objects affected by every refresh group configured in the given database. &lt;span style="color:#3333ff;"&gt;[There are also views which are a bit more restricted: ALL_REFRESH_CHILDREN (listing all refresh groups and affected objects accessible by the connected user) and USER_REFRESH_CHILDREN (listing all refresh groups and affected objects owned by the connected user).]&lt;/span&gt;  Of course it provides more information, such as the associated job number, the rollback/undo segment the group uses, the interval between refreshes and the date for the next refresh (among other details).  The view description is as follows:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; desc dba_refresh_children
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 OWNER                                     NOT NULL VARCHAR2(30)
 NAME                                      NOT NULL VARCHAR2(30)
 TYPE                                               VARCHAR2(30)
 ROWNER                                    NOT NULL VARCHAR2(30)
 RNAME                                     NOT NULL VARCHAR2(30)
 REFGROUP                                           NUMBER
 IMPLICIT_DESTROY                                   VARCHAR2(1)
 PUSH_DEFERRED_RPC                                  VARCHAR2(1)
 REFRESH_AFTER_ERRORS                               VARCHAR2(1)
 ROLLBACK_SEG                                       VARCHAR2(30)
 JOB                                                NUMBER
 NEXT_DATE                                          DATE
 INTERVAL                                           VARCHAR2(200)
 BROKEN                                             VARCHAR2(1)
 PURGE_OPTION                                       NUMBER(38)
 PARALLELISM                                        NUMBER(38)
 HEAP_SIZE                                          NUMBER(38)
 
SQL&gt;&lt;/span&gt;&lt;/pre&gt;The PUSH_DEFERRED_RPC column indicates, for updatable materialized views, whether or not to push any changes made to the snapshot data to the master table or master materialized view before the refresh begins.  The valid values are Y and N, where Y indicates Oracle will push the changes from the snapshot to the master and N (the default) indicates Oracle will not.&lt;br /&gt;
&lt;br /&gt;
It's fairly straightforward to extract information from the view, as most of the columns names aren't ambigous; a generalized report might look like this:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; select owner, name, type, refgroup, job, next_date, interval
  2  from dba_refresh_children;
 
OWNER      NAME                        TYPE       REFGROUP  JOB  NEXT_DATE INTERVAL
---------- --------------------------- --------- --------- ---- ---------- --------------------------
NARBOW     YARN_ORDS_PENDING_MV        SNAPSHOT         13   95  30-SEP-08 TRUNC(SYSDATE + 1) + 4/24
NARBOW     YARN_ORDS_BACKORD_MV        SNAPSHOT         14   96  30-SEP-08 TRUNC(SYSDATE + 1) + 5/24
NARBOW     DISCONTINUED_STOCK_NOS_MV   SNAPSHOT         53  134  30-SEP-08 TRUNC(SYSDATE+1)+5/24
BORTUST    RAW_MATL_BACKORD_MV         SNAPSHOT         11  414  01-JAN-00 sysdate+365
 
SQL&gt;&lt;/span&gt;&lt;/pre&gt;Job 414 in refresh group 11 is broken.  It's broken because the next run date is Jan 1, 4000, the default date Oracle uses for jobs which shouldn't run:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; select owner, name, type, refgroup, job, to_char(next_date, 'DD-MON-RRRR') next_date, interval, broken
  2  from dba_refresh_children
  3  where refgroup = 11;
 
OWNER      NAME                  TYPE        REFGROUP  JOB   NEXT_DATE INTERVAL                   B
---------- --------------------- ---------- --------- ---- ----------- -------------------------- -
BORTUST    RAW_MATL_BACKORD_MV   SNAPSHOT          11  414 01-JAN-4000 sysdate+365                Y
 
SQL&gt;&lt;/span&gt;&lt;/pre&gt;The DBA_REFRESH_CHILDREN view is a good 'one-stop shop' for information which can report on the health of your refresh jobs.  You can find the broken jobs:  &lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; select owner, name, job, refgroup
  2  from dba_refresh_children
  3  where  broken = 'Y'
  4  /
 
OWNER      NAME                                 JOB REFGROUP
---------- ----------------------------------- ---- --------
BORTUST    RAW_MATL_BACKORD_MV                  414       11
 
SQL&gt;&lt;/span&gt;&lt;/pre&gt;and it's nice to see there is only one.  Of course discovering WHY the job is broken is another task; it's quite likely the source table or view has changed and no longer matches the destination definition, and the insert operation fails with either an ORA-00913 (too many values), an ORA-00947 (not enough values) or an error stating a data type mismatch.  A search of the alert log may provide the answer; it may not, and the source code for the materialized view will be necessary to understand which local or remote objects were involved.  And, in a large shop with a number of DBAs it may be as simple as asking a question.  Make certain you're not 'spinning your wheels', though, as the users may not need the view or the job anymore and any effort to fix it would be effort wasted.  Again, a quick question to the right people may save you hours of unnecessary work.&lt;br /&gt;
&lt;br /&gt;
The PURGE_OPTION column is probably the most ambiguous of the bunch, and it refers to the method of purging the transaction queue after each 'push' (refresh); 1 indicates a quick purge, and 2 indicates a precise purge.  These apply to deferred transactions (which can be used to refresh materialized views).  A 'quick' purge is less costly in resources, but may cause deferred transaction records to remain visible for a period of time after the purge.  A 'precise' purge consumes more resources but it does offer the benefit of a complete queue flush leaving no lingering traces.&lt;br /&gt;
&lt;br /&gt;
I've said this before, and I'll say it again: having the proper tools for the job at hand can make that job so much easier; when dealing with snapshot/materialized view refreshes the DBA_REFRESH_CHILDREN view can save you time and effort in monitoring jobs and diagnosing problems.&lt;br /&gt;
&lt;br /&gt;
Now, that's refreshing!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28935478-8200621470470452579?l=oratips-ddf.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=-FK83ZrCBzU:i-63_FLBiqk: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=-FK83ZrCBzU:i-63_FLBiqk: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/8200621470470452579/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=8200621470470452579" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/8200621470470452579?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/8200621470470452579?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2008/09/its-real-refreshment.html" title="It's Real Refreshment" /><author><name>d_d_f</name><uri>http://www.blogger.com/profile/03203479596583639865</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="24" src="http://2.bp.blogspot.com/-89TposiQsMQ/Tu-5pfRb_rI/AAAAAAAAADE/h82gy5lsV80/s220/david.fitzjarrell.JPG" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DU8HQ3Y-cSp7ImA9WxJQGUg.&quot;"><id>tag:blogger.com,1999:blog-28935478.post-2207342584145182765</id><published>2008-09-26T19:58:00.006+01:00</published><updated>2009-06-02T16:50:32.859+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-06-02T16:50:32.859+01:00</app:edited><title>How Dynamic</title><content type="html">Passing a list of values to a function or procedure should be, well, simple, and it is, really, unless you have a dynamic list of unknown length.  Simply trying to use the supplied string, as-is, can be disappointing:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; --
SQL&gt; -- Let's try this the simple way
SQL&gt; --
SQL&gt; create or replace function instring_list_test(subtype_list varchar2)
  2  return number
  3  is
  4        lv_ct number;
  5  
  6        cursor get_empinfo is
  7        select count(*)
  8        from emp
  9        where deptno in subtype_list;
 10  
 11  begin
 12  
 13    open get_empinfo;
 14    fetch get_empinfo into lv_ct;
 15    close get_empinfo;
 16  
 17    return lv_ct;
 18  end;
 19  /

Function created.

SQL&gt; 
SQL&gt; show errors
No errors.
SQL&gt; 
SQL&gt; --
SQL&gt; -- The function created without error
SQL&gt; --
SQL&gt; -- Let's see if it works
SQL&gt; --
SQL&gt; select instring_list_test('10,20,30') from dual;
select instring_list_test('10,20,30') from dual
       *
ERROR at line 1:
ORA-01722: invalid number 
ORA-06512: at "BING.INSTRING_LIST_TEST", line 14 


SQL&gt; 
SQL&gt; --
SQL&gt; -- That's silly, it should work ...
SQL&gt; --&lt;/span&gt;&lt;/pre&gt;

Since '10,20,30' isn't a number, and Oracle can't magically separate the individual values the function call fails.  It would succeed were there one value in this dynamic list, but real-life situations usually aren't that simple and straightforward.  Whatever shall we do?  We need to 'get dirty' and actually code a way for Oracle to separate the values, make them numbers and populate a table, dynamically, so we can select from that table and generate a usable list.  So, let's try this again and see if we can get this to do what we want:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; --
SQL&gt; -- Let's try this again
SQL&gt; --
SQL&gt; -- We'll create a table type first
SQL&gt; --
SQL&gt; create or replace type InNumTab is table of number;
  2  /

Type created.

SQL&gt; 
SQL&gt; --
SQL&gt; -- Now we'll use that table type to massage
SQL&gt; -- the supplied string into a usable list
SQL&gt; --
SQL&gt; create or replace function instring_list_test(subtype_list varchar2)
  2  return number
  3  is
  4        --
  5        -- The parsed value
  6        --
  7        lv_subtyp number;
  8        --
  9        -- The table we'll populate
 10        --
 11        lv_sublist InNumTab := InNumTab();
 12        --
 13        -- A place for the result
 14        --
 15        lv_ct number;
 16        --
 17        -- A variable so we can 'walk' the string
 18        --
 19        startpos number:=1;
 20        --
 21        -- Record counter to extend the table
 22        --
 23        rec     number:=1;
 24  
 25        --
 26        -- Query using the dynamic IN list
 27        --
 28        cursor get_empinfo (enums InNumTab) is
 29        select count(*)
 30        from emp
 31        where deptno in (select column_value from table(cast(enums as InNumTab)));
 32  
 33  begin
 34    --
 35    -- Extend the table so we can start populating it
 36    --
 37    lv_sublist.extend(rec);
 38  
 39    --
 40    -- 'Walk' the provided string
 41    -- The loop exits when no value separator is found
 42    -- We expect the value separator to be a comma
 43    --
 44    loop
 45        exit when instr(subtype_list, ',', startpos) = 0;
 46        lv_subtyp := substr(subtype_list, startpos, instr(subtype_list,',', 1)-1);
 47        lv_sublist(rec) := lv_subtyp;
 48        startpos := instr(subtype_list, ',', startpos)+1;
 49        rec := rec+1;
 50        --
 51        -- After each addition we extend the table
 52        --
 53        lv_sublist.extend(rec);
 54    end loop;
 55  
 56    --
 57    -- We extend the table one more time to hold our last value
 58    --
 59    rec := rec+1;
 60    lv_sublist.extend(rec);
 61    lv_subtyp := substr(subtype_list, startpos);
 62    lv_sublist(rec) := lv_subtyp;
 63  
 64    --
 65    -- Get the count
 66    --
 67    open get_empinfo(lv_sublist);
 68    fetch get_empinfo into lv_ct;
 69    close get_empinfo;
 70  
 71    --
 72    -- Return the value to the caller
 73    --
 74    return lv_ct;
 75  end;
 76  /

Function created.

SQL&gt; 
SQL&gt; show errors
No errors.
SQL&gt; 
SQL&gt; --
SQL&gt; -- Let's test again, this time with our modified function
SQL&gt; --
SQL&gt; -- We'll find it works
SQL&gt; --
SQL&gt; select instring_list_test('10,20,30') from dual;

INSTRING_LIST_TEST('10,20,30')
------------------------------
                            27

SQL&gt; select instring_list_test('10,20') from dual;

INSTRING_LIST_TEST('10,20')
---------------------------
                         12

SQL&gt; select instring_list_test('10') from dual;

INSTRING_LIST_TEST('10')
------------------------
                       3

SQL&gt; select instring_list_test('10,20,30,40') from dual;

INSTRING_LIST_TEST('10,20,30,40')
---------------------------------
                               42

SQL&gt; select instring_list_test('10,20,30,40,50,60') from dual;

INSTRING_LIST_TEST('10,20,30,40,50,60')
---------------------------------------
                                     60

SQL&gt; select instring_list_test('10,20,30,40,50') from dual;

INSTRING_LIST_TEST('10,20,30,40,50')
------------------------------------
                                  54

SQL&gt; &lt;/span&gt;&lt;/pre&gt;

Notice that the length of the list is immaterial as the loop keeps running until the desired record separator is no longer present; we need to code one additional parse of the supplied string to extract the last value and place it in our dynamic table.  We then use the CAST and TABLE functions when we query this 'table';  the result is a list of values, rather than the original string, which makes IN very happy indeed.&lt;br /&gt;&lt;br /&gt;

So what if you don't want to always use a comma to separate your data values?  Don't panic, we can re-write the function to accept a second parameter, the record separator:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt; --
SQL&gt; -- We'll try this another way, passing in the desired record separator value
SQL&gt; --
SQL&gt; create or replace function instring_list_test(subtype_list varchar2, recsep varchar2)
  2  return number
  3  is
  4        --
  5        -- The parsed value
  6        --
  7        lv_subtyp number;
  8        --
  9        -- The table we'll populate
 10        --
 11        lv_sublist InNumTab := InNumTab();
 12        --
 13        -- A place for the result
 14        --
 15        lv_ct number;
 16        --
 17        -- A variable so we can 'walk' the string
 18        --
 19        startpos number:=1;
 20        --
 21        -- Record counter to extend the table
 22        --
 23        rec     number:=1;
 24
 25        --
 26        -- Query using the dynamic IN list
 27        --
 28        cursor get_empinfo (enums InNumTab) is
 29        select count(*)
 30        from emp
 31        where deptno in (select column_value from table(cast(enums as InNumTab)));
 32
 33  begin
 34    --
 35    -- Extend the table so we can start populating it
 36    --
 37    lv_sublist.extend(rec);
 38
 39    --
 40    -- 'Walk' the provided string
 41    --
 42    -- We provide the desired record separator
 43    --
 44    loop
 45        exit when instr(subtype_list, recsep, startpos) = 0;
 46        lv_subtyp := substr(subtype_list, startpos, instr(subtype_list,recsep, 1)-1);
 47        lv_sublist(rec) := lv_subtyp;
 48        startpos := instr(subtype_list, recsep, startpos)+1;
 49        rec := rec+1;
 50        --
 51        -- After each addition we extend the table
 52        --
 53        lv_sublist.extend(rec);
 54    end loop;
 55
 56    --
 57    -- We extend the table one more time to hold our last value
 58    --
 59    rec := rec+1;
 60    lv_sublist.extend(rec);
 61    lv_subtyp := substr(subtype_list, startpos);
 62    lv_sublist(rec) := lv_subtyp;
 63
 64    --
 65    -- Get the count
 66    --
 67    open get_empinfo(lv_sublist);
 68    fetch get_empinfo into lv_ct;
 69    close get_empinfo;
 70
 71    --
 72    -- Return the value to the caller
 73    --
 74    return lv_ct;
 75  end;
 76  /

Function created.

SQL&gt;
SQL&gt; show errors
No errors.
SQL&gt;
SQL&gt; --
SQL&gt; -- Let's test again, this time with our modified function
SQL&gt; --
SQL&gt; -- We'll find it works
SQL&gt; --
SQL&gt; select instring_list_test('10,20,30',',') from dual;

INSTRING_LIST_TEST('10,20,30',',')
----------------------------------
                                27

SQL&gt; select instring_list_test('10;20',';') from dual;

INSTRING_LIST_TEST('10;20',';')
-------------------------------
                             12

SQL&gt; select instring_list_test('10',',') from dual;

INSTRING_LIST_TEST('10',',')
----------------------------
                           3

SQL&gt; select instring_list_test('10:20:30:40',':') from dual;

INSTRING_LIST_TEST('10:20:30:40',':')
-------------------------------------
                                   42

SQL&gt; select instring_list_test('10-20-30-40-50-60','-') from dual;

INSTRING_LIST_TEST('10-20-30-40-50-60','-')
-------------------------------------------
                                         60

SQL&gt; select instring_list_test('10,20,30,40,50',',') from dual;

INSTRING_LIST_TEST('10,20,30,40,50',',')
----------------------------------------
                                      54

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

And it works like a charm, returning correct values for the counts requested.  Is that cool, or what?&lt;br /&gt;&lt;br /&gt;

In Oracle 10g and later releases the MEMBER OF operator can be used in place of the table function used in the prior two examples:

&lt;pre&gt;&lt;span style="color:#3333ff;"&gt;SQL&gt;
SQL&gt; --
SQL&gt; -- We'll try this yet another way
SQL&gt; --
SQL&gt; create or replace function instring_list_test(subtype_list varchar2, recsep varchar2)
  2  return number
  3  is
  4          --
  5          -- The parsed value
  6          --
  7          lv_subtyp number;
  8          --
  9          -- The table we'll populate
 10          --
 11          lv_sublist InNumTab := InNumTab();
 12          --
 13          -- A place for the result
 14          --
 15          lv_ct number;
 16          --
 17          -- A variable so we can 'walk' the string
 18          --
 19          startpos number:=1;
 20          --
 21          -- Record counter to extend the table
 22          --
 23          rec     number:=1;
 24
 25          --
 26          -- Query using the dynamic IN list
 27          --
 28          cursor get_empinfo (enums InNumTab) is
 29          select count(*)
 30          from emp
 31          where deptno &lt;b&gt;member of&lt;/b&gt; enums;
 32
 33  begin
 34      --
 35      -- Extend the table so we can start populating it
 36      --
 37      lv_sublist.extend(rec);
 38
 39      --
 40      -- 'Walk' the provided string
 41      --
 42      -- We expect the value separator to be a comma
 43      --
 44      loop
 45          exit when instr(subtype_list, recsep, startpos) = 0;
 46          lv_subtyp := substr(subtype_list, startpos, instr(subtype_list,recsep, 1)-1);
 47          lv_sublist(rec) := lv_subtyp;
 48          startpos := instr(subtype_list, recsep, startpos)+1;
 49          rec := rec+1;
 50          --
 51          -- After each addition we extend the table
 52          --
 53          lv_sublist.extend(rec);
 54      end loop;
 55
 56      --
 57      -- We extend the table one more time to hold our last value
 58      --
 59      rec := rec+1;
 60      lv_sublist.extend(rec);
 61      lv_subtyp := substr(subtype_list, startpos);
 62      lv_sublist(rec) := lv_subtyp;
 63
 64      --
 65      -- Get the count
 66      --
 67      open get_empinfo(lv_sublist);
 68      fetch get_empinfo into lv_ct;
 69      close get_empinfo;
 70
 71      --
 72      -- Return the value to the caller
 73      --
 74      return lv_ct;
 75  end;
 76  /

Function created.

SQL&gt;
SQL&gt; show errors
No errors.
SQL&gt;
SQL&gt; --
SQL&gt; -- Let's test again, this time with our modified function
SQL&gt; --
SQL&gt; -- We'll find it works
SQL&gt; --
SQL&gt; select instring_list_test('10,20,30',',') from dual;

INSTRING_LIST_TEST('10,20,30',',')
----------------------------------
                                27

SQL&gt; select instring_list_test('10;20',';') from dual;

INSTRING_LIST_TEST('10;20',';')
-------------------------------
                             12

SQL&gt; select instring_list_test('10',',') from dual;

INSTRING_LIST_TEST('10',',')
----------------------------
                           3

SQL&gt; select instring_list_test('10:20:30:40',':') from dual;

INSTRING_LIST_TEST('10:20:30:40',':')
-------------------------------------
                                   42

SQL&gt; select instring_list_test('10-20-30-40-50-60','-') from dual;

INSTRING_LIST_TEST('10-20-30-40-50-60','-')
-------------------------------------------
                                         60

SQL&gt; select instring_list_test('10,20,30,40,50',',') from dual;

INSTRING_LIST_TEST('10,20,30,40,50',',')
----------------------------------------
                                      54

SQL&gt;&lt;/span&gt;&lt;/pre&gt;

The problem, though simple to state, isn't quite as simple to solve, as proven above.  That's because Oracle is a database, it isn't your brain, so it can't draw on prior experience and know that '10,20,30' is glorified shorthand for 10,20,30.  And because it's software (really, it is) running on a computer, it does what you tell it to do, whether or not those instructions provide the output you'd intended.  Since it can't think like you do you'll have to start 'thinking' like it does, and that may throw a wrench into your logical picture of the situation.  Knowing what to do with that wrench is half of the battle.&lt;br /&gt;&lt;br /&gt;

Passing dynamic lists to procedures and functions is possible, it just isn't as easy as you'd like to think, especially the first time through.  But, knowing how to navigate such a situation makes life a bit easier in the IT realm, which may make you pretty nifty.&lt;br /&gt;&lt;br /&gt;

Of course such knowledge is no substitute for the ultimate cool of driving a Lamborghini to the dollar store ...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28935478-2207342584145182765?l=oratips-ddf.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/blogspot/oratips?a=wF093MZHD6I:56aa-k3LLSA: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=wF093MZHD6I:56aa-k3LLSA: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/2207342584145182765/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=28935478&amp;postID=2207342584145182765" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/2207342584145182765?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/28935478/posts/default/2207342584145182765?v=2" /><link rel="alternate" type="text/html" href="http://oratips-ddf.blogspot.com/2008/09/how-dynamic.html" title="How Dynamic" /><author><name>d_d_f</name><uri>http://www.blogger.com/profile/03203479596583639865</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="24" src="http://2.bp.blogspot.com/-89TposiQsMQ/Tu-5pfRb_rI/AAAAAAAAADE/h82gy5lsV80/s220/david.fitzjarrell.JPG" /></author><thr:total>1</thr:total></entry></feed>

